From e7ea643a3654ec9ce3ecb65ed107c2df431a791b Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 29 Oct 2021 12:48:46 +0200 Subject: [PATCH] Add python linting (#80) --- .github/workflows/lint.yml | 70 ++++++++++++ .github/workflows/matchers/flake8.json | 30 +++++ .github/workflows/matchers/isort.json | 14 +++ .github/workflows/matchers/pylint.json | 32 ++++++ esphomeflasher/__main__.py | 146 ++++++++++++++---------- esphomeflasher/common.py | 126 +++++++++++++-------- esphomeflasher/const.py | 16 ++- esphomeflasher/gui.py | 151 +++++++++++++++---------- esphomeflasher/helpers.py | 9 +- pyproject.toml | 24 ++++ requirements_test.txt | 4 + setup.cfg | 14 +++ 12 files changed, 458 insertions(+), 178 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/matchers/flake8.json create mode 100644 .github/workflows/matchers/isort.json create mode 100644 .github/workflows/matchers/pylint.json create mode 100644 pyproject.toml create mode 100644 requirements_test.txt create mode 100644 setup.cfg diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..d52009a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,70 @@ +name: Lint workflow + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + ci: + name: ${{ matrix.name }} + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + include: + - id: flake8 + name: Lint with flake8 + - id: pylint + name: Lint with pylint + - id: black + name: Check formatting with black + - id: isort + name: Check import order with isort + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + id: python + with: + python-version: '3.7' + - name: Install apt dependencies + run: | + sudo apt install libgtk-3-dev libnotify-dev libsdl2-dev + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Restore PIP cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements.txt', 'requirements_test.txt') }} + restore-keys: | + pip-${{ steps.python.outputs.python-version }}- + - name: Set up Python environment + run: | + pip3 install -U \ + -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 \ + wxPython + pip3 install -r requirements.txt -r requirements_test.txt + pip3 install -e . + + - name: Register problem matchers + run: | + echo "::add-matcher::.github/workflows/matchers/flake8.json" + echo "::add-matcher::.github/workflows/matchers/pylint.json" + echo "::add-matcher::.github/workflows/matchers/isort.json" + + - run: flake8 esphomeflasher + if: ${{ matrix.id == 'flake8' }} + - run: pylint esphomeflasher + if: ${{ matrix.id == 'pylint' }} + - run: black --check --diff --color esphomeflasher + if: ${{ matrix.id == 'black' }} + - run: isort --check --diff esphomeflasher + if: ${{ matrix.id == 'isort' }} diff --git a/.github/workflows/matchers/flake8.json b/.github/workflows/matchers/flake8.json new file mode 100644 index 0000000..e059a1c --- /dev/null +++ b/.github/workflows/matchers/flake8.json @@ -0,0 +1,30 @@ +{ + "problemMatcher": [ + { + "owner": "flake8-error", + "severity": "error", + "pattern": [ + { + "regexp": "^(.*):(\\d+):(\\d+):\\s([EF]\\d{3}\\s.*)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + } + ] + }, + { + "owner": "flake8-warning", + "severity": "warning", + "pattern": [ + { + "regexp": "^(.*):(\\d+):(\\d+):\\s([CDNW]\\d{3}\\s.*)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + } + ] + } + ] +} diff --git a/.github/workflows/matchers/isort.json b/.github/workflows/matchers/isort.json new file mode 100644 index 0000000..83802b7 --- /dev/null +++ b/.github/workflows/matchers/isort.json @@ -0,0 +1,14 @@ +{ + "problemMatcher": [ + { + "owner": "isort", + "pattern": [ + { + "regexp": "^ERROR:\\s+(.+)\\s+(.+)$", + "file": 1, + "message": 2 + } + ] + } + ] +} diff --git a/.github/workflows/matchers/pylint.json b/.github/workflows/matchers/pylint.json new file mode 100644 index 0000000..5624ca6 --- /dev/null +++ b/.github/workflows/matchers/pylint.json @@ -0,0 +1,32 @@ +{ + "problemMatcher": [ + { + "owner": "pylint-error", + "severity": "error", + "pattern": [ + { + "regexp": "^(.+):(\\d+):(\\d+):\\s(([EF]\\d{4}):\\s.+)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4, + "code": 5 + } + ] + }, + { + "owner": "pylint-warning", + "severity": "warning", + "pattern": [ + { + "regexp": "^(.+):(\\d+):(\\d+):\\s(([CRW]\\d{4}):\\s.+)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4, + "code": 5 + } + ] + } + ] +} diff --git a/esphomeflasher/__main__.py b/esphomeflasher/__main__.py index cc7c09d..04c0432 100644 --- a/esphomeflasher/__main__.py +++ b/esphomeflasher/__main__.py @@ -1,51 +1,70 @@ from __future__ import print_function import argparse -from datetime import datetime import sys import time +from datetime import datetime import esptool import serial from esphomeflasher import const -from esphomeflasher.common import ESP32ChipInfo, EsphomeflasherError, chip_run_stub, \ - configure_write_flash_args, detect_chip, detect_flash_size, read_chip_info -from esphomeflasher.const import ESP32_DEFAULT_BOOTLOADER_FORMAT, ESP32_DEFAULT_OTA_DATA, \ - ESP32_DEFAULT_PARTITIONS +from esphomeflasher.common import ( + ESP32ChipInfo, + EsphomeflasherError, + chip_run_stub, + configure_write_flash_args, + detect_chip, + detect_flash_size, + read_chip_info, +) +from esphomeflasher.const import ( + ESP32_DEFAULT_BOOTLOADER_FORMAT, + ESP32_DEFAULT_OTA_DATA, + ESP32_DEFAULT_PARTITIONS, +) from esphomeflasher.helpers import list_serial_ports def parse_args(argv): - parser = argparse.ArgumentParser(prog='esphomeflasher {}'.format(const.__version__)) - parser.add_argument('-p', '--port', - help="Select the USB/COM port for uploading.") + parser = argparse.ArgumentParser(prog=f"esphomeflasher {const.__version__}") + parser.add_argument("-p", "--port", help="Select the USB/COM port for uploading.") group = parser.add_mutually_exclusive_group(required=False) - group.add_argument('--esp8266', action='store_true') - group.add_argument('--esp32', action='store_true') - group.add_argument('--upload-baud-rate', type=int, default=460800, - help="Baud rate to upload with (not for logging)") - parser.add_argument('--bootloader', - help="(ESP32-only) The bootloader to flash.", - default=ESP32_DEFAULT_BOOTLOADER_FORMAT) - parser.add_argument('--partitions', - help="(ESP32-only) The partitions to flash.", - default=ESP32_DEFAULT_PARTITIONS) - parser.add_argument('--otadata', - help="(ESP32-only) The otadata file to flash.", - default=ESP32_DEFAULT_OTA_DATA) - parser.add_argument('--no-erase', - help="Do not erase flash before flashing", - action='store_true') - parser.add_argument('--show-logs', help="Only show logs", action='store_true') - parser.add_argument('binary', help="The binary image to flash.") + group.add_argument("--esp8266", action="store_true") + group.add_argument("--esp32", action="store_true") + group.add_argument( + "--upload-baud-rate", + type=int, + default=460800, + help="Baud rate to upload with (not for logging)", + ) + parser.add_argument( + "--bootloader", + help="(ESP32-only) The bootloader to flash.", + default=ESP32_DEFAULT_BOOTLOADER_FORMAT, + ) + parser.add_argument( + "--partitions", + help="(ESP32-only) The partitions to flash.", + default=ESP32_DEFAULT_PARTITIONS, + ) + parser.add_argument( + "--otadata", + help="(ESP32-only) The otadata file to flash.", + default=ESP32_DEFAULT_OTA_DATA, + ) + parser.add_argument( + "--no-erase", help="Do not erase flash before flashing", action="store_true" + ) + parser.add_argument("--show-logs", help="Only show logs", action="store_true") + parser.add_argument("binary", help="The binary image to flash.") return parser.parse_args(argv[1:]) def select_port(args): if args.port is not None: - print(u"Using '{}' as serial port.".format(args.port)) + print(f"Using '{args.port}' as serial port.") return args.port ports = list_serial_ports() if not ports: @@ -53,10 +72,10 @@ def select_port(args): if len(ports) != 1: print("Found more than one serial port:") for port, desc in ports: - print(u" * {} ({})".format(port, desc)) + print(f" * {port} ({desc})") print("Please choose one with the --port argument.") raise EsphomeflasherError - print(u"Auto-detected serial port: {}".format(ports[0][0])) + print(f"Auto-detected serial port: {ports[0][0]}") return ports[0][0] @@ -69,14 +88,14 @@ def show_logs(serial_port): except serial.SerialException: print("Serial port closed!") return - text = raw.decode(errors='ignore') - line = text.replace('\r', '').replace('\n', '') - time = datetime.now().time().strftime('[%H:%M:%S]') - message = time + line + text = raw.decode(errors="ignore") + line = text.replace("\r", "").replace("\n", "") + time_ = datetime.now().time().strftime("[%H:%M:%S]") + message = time_ + line try: print(message) except UnicodeEncodeError: - print(message.encode('ascii', 'backslashreplace')) + print(message.encode("ascii", "backslashreplace")) def run_esphomeflasher(argv): @@ -89,27 +108,29 @@ def run_esphomeflasher(argv): return try: - firmware = open(args.binary, 'rb') + # pylint: disable=consider-using-with + firmware = open(args.binary, "rb") except IOError as err: - raise EsphomeflasherError("Error opening binary: {}".format(err)) + raise EsphomeflasherError(f"Error opening binary: {err}") from err chip = detect_chip(port, args.esp8266, args.esp32) info = read_chip_info(chip) print() print("Chip Info:") - print(" - Chip Family: {}".format(info.family)) - print(" - Chip Model: {}".format(info.model)) + print(f" - Chip Family: {info.family}") + print(f" - Chip Model: {info.model}") if isinstance(info, ESP32ChipInfo): - print(" - Number of Cores: {}".format(info.num_cores)) - print(" - Max CPU Frequency: {}".format(info.cpu_frequency)) - print(" - Has Bluetooth: {}".format('YES' if info.has_bluetooth else 'NO')) - print(" - Has Embedded Flash: {}".format('YES' if info.has_embedded_flash else 'NO')) - print(" - Has Factory-Calibrated ADC: {}".format( - 'YES' if info.has_factory_calibrated_adc else 'NO')) + print(f" - Number of Cores: {info.num_cores}") + print(f" - Max CPU Frequency: {info.cpu_frequency}") + print(f" - Has Bluetooth: {'YES' if info.has_bluetooth else 'NO'}") + print(f" - Has Embedded Flash: {'YES' if info.has_embedded_flash else 'NO'}") + print( + f" - Has Factory-Calibrated ADC: {'YES' if info.has_factory_calibrated_adc else 'NO'}" + ) else: - print(" - Chip ID: {:08X}".format(info.chip_id)) + print(f" - Chip ID: {info.chip_id:08X}") - print(" - MAC Address: {}".format(info.mac)) + print(f" - MAC Address: {info.mac}") stub_chip = chip_run_stub(chip) flash_size = None @@ -118,14 +139,19 @@ def run_esphomeflasher(argv): try: stub_chip.change_baud(args.upload_baud_rate) except esptool.FatalError as err: - raise EsphomeflasherError("Error changing ESP upload baud rate: {}".format(err)) + raise EsphomeflasherError( + f"Error changing ESP upload baud rate: {err}" + ) from err # Check if the higher baud rate works try: flash_size = detect_flash_size(stub_chip) - except EsphomeflasherError as err: + except EsphomeflasherError: # Go back to old baud rate by recreating chip instance - print("Chip does not support baud rate {}, changing to 115200".format(args.upload_baud_rate)) + print( + f"Chip does not support baud rate {args.upload_baud_rate}, changing to 115200" + ) + # pylint: disable=protected-access stub_chip._port.close() chip = detect_chip(port, args.esp8266, args.esp32) stub_chip = chip_run_stub(chip) @@ -133,31 +159,30 @@ def run_esphomeflasher(argv): if flash_size is None: flash_size = detect_flash_size(stub_chip) + print(f" - Flash Size: {flash_size}") - print(" - Flash Size: {}".format(flash_size)) + mock_args = configure_write_flash_args( + info, firmware, flash_size, args.bootloader, args.partitions, args.otadata + ) - mock_args = configure_write_flash_args(info, firmware, flash_size, - args.bootloader, args.partitions, - args.otadata) - - print(" - Flash Mode: {}".format(mock_args.flash_mode)) - print(" - Flash Frequency: {}Hz".format(mock_args.flash_freq.upper())) + print(f" - Flash Mode: {mock_args.flash_mode}") + print(f" - Flash Frequency: {mock_args.flash_freq.upper()}Hz") try: stub_chip.flash_set_parameters(esptool.flash_size_bytes(flash_size)) except esptool.FatalError as err: - raise EsphomeflasherError("Error setting flash parameters: {}".format(err)) + raise EsphomeflasherError(f"Error setting flash parameters: {err}") from err if not args.no_erase: try: esptool.erase_flash(stub_chip, mock_args) except esptool.FatalError as err: - raise EsphomeflasherError("Error while erasing flash: {}".format(err)) + raise EsphomeflasherError(f"Error while erasing flash: {err}") from err try: esptool.write_flash(stub_chip, mock_args) except esptool.FatalError as err: - raise EsphomeflasherError("Error while writing flash: {}".format(err)) + raise EsphomeflasherError(f"Error while writing flash: {err}") from err print("Hard Resetting...") stub_chip.hard_reset() @@ -166,10 +191,13 @@ def run_esphomeflasher(argv): print() if args.upload_baud_rate != 115200: + # pylint: disable=protected-access stub_chip._port.baudrate = 115200 time.sleep(0.05) # get rid of crap sent during baud rate change + # pylint: disable=protected-access stub_chip._port.flushInput() + # pylint: disable=protected-access show_logs(stub_chip._port) diff --git a/esphomeflasher/common.py b/esphomeflasher/common.py index 4addf77..786fd99 100644 --- a/esphomeflasher/common.py +++ b/esphomeflasher/common.py @@ -11,7 +11,7 @@ class EsphomeflasherError(Exception): pass -class MockEsptoolArgs(object): +class MockEsptoolArgs: def __init__(self, flash_size, addr_filename, flash_mode, flash_freq): self.compress = True self.no_compress = False @@ -26,7 +26,7 @@ class MockEsptoolArgs(object): self.encrypt_files = None -class ChipInfo(object): +class ChipInfo: def __init__(self, family, model, mac): self.family = family self.model = model @@ -35,17 +35,25 @@ class ChipInfo(object): def as_dict(self): return { - 'family': self.family, - 'model': self.model, - 'mac': self.mac, - 'is_esp32': self.is_esp32, + "family": self.family, + "model": self.model, + "mac": self.mac, + "is_esp32": self.is_esp32, } class ESP32ChipInfo(ChipInfo): - def __init__(self, model, mac, num_cores, cpu_frequency, has_bluetooth, has_embedded_flash, - has_factory_calibrated_adc): - super(ESP32ChipInfo, self).__init__("ESP32", model, mac) + def __init__( + self, + model, + mac, + num_cores, + cpu_frequency, + has_bluetooth, + has_embedded_flash, + has_factory_calibrated_adc, + ): + super().__init__("ESP32", model, mac) self.num_cores = num_cores self.cpu_frequency = cpu_frequency self.has_bluetooth = has_bluetooth @@ -54,26 +62,30 @@ class ESP32ChipInfo(ChipInfo): def as_dict(self): data = ChipInfo.as_dict(self) - data.update({ - 'num_cores': self.num_cores, - 'cpu_frequency': self.cpu_frequency, - 'has_bluetooth': self.has_bluetooth, - 'has_embedded_flash': self.has_embedded_flash, - 'has_factory_calibrated_adc': self.has_factory_calibrated_adc, - }) + data.update( + { + "num_cores": self.num_cores, + "cpu_frequency": self.cpu_frequency, + "has_bluetooth": self.has_bluetooth, + "has_embedded_flash": self.has_embedded_flash, + "has_factory_calibrated_adc": self.has_factory_calibrated_adc, + } + ) return data class ESP8266ChipInfo(ChipInfo): def __init__(self, model, mac, chip_id): - super(ESP8266ChipInfo, self).__init__("ESP8266", model, mac) + super().__init__("ESP8266", model, mac) self.chip_id = chip_id def as_dict(self): data = ChipInfo.as_dict(self) - data.update({ - 'chip_id': self.chip_id, - }) + data.update( + { + "chip_id": self.chip_id, + } + ) return data @@ -81,38 +93,47 @@ def read_chip_property(func, *args, **kwargs): try: return prevent_print(func, *args, **kwargs) except esptool.FatalError as err: - raise EsphomeflasherError("Reading chip details failed: {}".format(err)) + raise EsphomeflasherError(f"Reading chip details failed: {err}") from err def read_chip_info(chip): - mac = ':'.join('{:02X}'.format(x) for x in read_chip_property(chip.read_mac)) + mac = ":".join(f"{x:02X}" for x in read_chip_property(chip.read_mac)) if isinstance(chip, esptool.ESP32ROM): model = read_chip_property(chip.get_chip_description) features = read_chip_property(chip.get_chip_features) - num_cores = 2 if 'Dual Core' in features else 1 - frequency = next((x for x in ('160MHz', '240MHz') if x in features), '80MHz') - has_bluetooth = 'BT' in features - has_embedded_flash = 'Embedded Flash' in features - has_factory_calibrated_adc = 'VRef calibration in efuse' in features - return ESP32ChipInfo(model, mac, num_cores, frequency, has_bluetooth, - has_embedded_flash, has_factory_calibrated_adc) - elif isinstance(chip, esptool.ESP8266ROM): + num_cores = 2 if "Dual Core" in features else 1 + frequency = next((x for x in ("160MHz", "240MHz") if x in features), "80MHz") + has_bluetooth = "BT" in features + has_embedded_flash = "Embedded Flash" in features + has_factory_calibrated_adc = "VRef calibration in efuse" in features + return ESP32ChipInfo( + model, + mac, + num_cores, + frequency, + has_bluetooth, + has_embedded_flash, + has_factory_calibrated_adc, + ) + if isinstance(chip, esptool.ESP8266ROM): model = read_chip_property(chip.get_chip_description) chip_id = read_chip_property(chip.chip_id) return ESP8266ChipInfo(model, mac, chip_id) - raise EsphomeflasherError("Unknown chip type {}".format(type(chip))) + raise EsphomeflasherError(f"Unknown chip type {type(chip)}") def chip_run_stub(chip): try: return chip.run_stub() except esptool.FatalError as err: - raise EsphomeflasherError("Error putting ESP in stub flash mode: {}".format(err)) + raise EsphomeflasherError( + f"Error putting ESP in stub flash mode: {err}" + ) from err def detect_flash_size(stub_chip): flash_id = read_chip_property(stub_chip.flash_id) - return esptool.DETECTED_FLASH_SIZES.get(flash_id >> 16, '4MB') + return esptool.DETECTED_FLASH_SIZES.get(flash_id >> 16, "4MB") def read_firmware_info(firmware): @@ -122,16 +143,16 @@ def read_firmware_info(firmware): magic, _, flash_mode_raw, flash_size_freq = struct.unpack("BBBB", header) if magic != esptool.ESPLoader.ESP_IMAGE_MAGIC: raise EsphomeflasherError( - "The firmware binary is invalid (magic byte={:02X}, should be {:02X})" - "".format(magic, esptool.ESPLoader.ESP_IMAGE_MAGIC)) + f"The firmware binary is invalid (magic byte={magic:02X}, should be {esptool.ESPLoader.ESP_IMAGE_MAGIC:02X})" + ) flash_freq_raw = flash_size_freq & 0x0F - flash_mode = {0: 'qio', 1: 'qout', 2: 'dio', 3: 'dout'}.get(flash_mode_raw) - flash_freq = {0: '40m', 1: '26m', 2: '20m', 0xF: '80m'}.get(flash_freq_raw) + flash_mode = {0: "qio", 1: "qout", 2: "dio", 3: "dout"}.get(flash_mode_raw) + flash_freq = {0: "40m", 1: "26m", 2: "20m", 0xF: "80m"}.get(flash_freq_raw) return flash_mode, flash_freq def open_downloadable_binary(path): - if hasattr(path, 'seek'): + if hasattr(path, "seek"): path.seek(0) return path @@ -143,10 +164,12 @@ def open_downloadable_binary(path): response.raise_for_status() except requests.exceptions.Timeout as err: raise EsphomeflasherError( - "Timeout while retrieving firmware file '{}': {}".format(path, err)) + f"Timeout while retrieving firmware file '{path}': {err}" + ) from err except requests.exceptions.RequestException as err: raise EsphomeflasherError( - "Error while retrieving firmware file '{}': {}".format(path, err)) + f"Error while retrieving firmware file '{path}': {err}" + ) from err binary = io.BytesIO() binary.write(response.content) @@ -154,26 +177,29 @@ def open_downloadable_binary(path): return binary try: - return open(path, 'rb') + return open(path, "rb") except IOError as err: - raise EsphomeflasherError("Error opening binary '{}': {}".format(path, err)) + raise EsphomeflasherError(f"Error opening binary '{path}': {err}") from err def format_bootloader_path(path, flash_mode, flash_freq): - return path.replace('$FLASH_MODE$', flash_mode).replace('$FLASH_FREQ$', flash_freq) + return path.replace("$FLASH_MODE$", flash_mode).replace("$FLASH_FREQ$", flash_freq) -def configure_write_flash_args(info, firmware_path, flash_size, - bootloader_path, partitions_path, otadata_path): +def configure_write_flash_args( + info, firmware_path, flash_size, bootloader_path, partitions_path, otadata_path +): addr_filename = [] firmware = open_downloadable_binary(firmware_path) flash_mode, flash_freq = read_firmware_info(firmware) if isinstance(info, ESP32ChipInfo): - if flash_freq in ('26m', '20m'): + if flash_freq in ("26m", "20m"): raise EsphomeflasherError( - "No bootloader available for flash frequency {}".format(flash_freq)) + f"No bootloader available for flash frequency {flash_freq}" + ) bootloader = open_downloadable_binary( - format_bootloader_path(bootloader_path, flash_mode, flash_freq)) + format_bootloader_path(bootloader_path, flash_mode, flash_freq) + ) partitions = open_downloadable_binary(partitions_path) otadata = open_downloadable_binary(otadata_path) @@ -197,12 +223,12 @@ def detect_chip(port, force_esp8266=False, force_esp32=False): if "Wrong boot mode detected" in str(err): msg = "ESP is not in flash boot mode. If your board has a flashing pin, try again while keeping it pressed." else: - msg = "ESP Chip Auto-Detection failed: {}".format(err) + msg = f"ESP Chip Auto-Detection failed: {err}" raise EsphomeflasherError(msg) from err try: chip.connect() except esptool.FatalError as err: - raise EsphomeflasherError("Error connecting to ESP: {}".format(err)) + raise EsphomeflasherError(f"Error connecting to ESP: {err}") from err return chip diff --git a/esphomeflasher/const.py b/esphomeflasher/const.py index b647373..b9c53bc 100644 --- a/esphomeflasher/const.py +++ b/esphomeflasher/const.py @@ -2,10 +2,16 @@ import re __version__ = "1.3.1" -ESP32_DEFAULT_OTA_DATA = 'https://raw.githubusercontent.com/espressif/arduino-esp32/1.0.0/tools/partitions/boot_app0.bin' -ESP32_DEFAULT_BOOTLOADER_FORMAT = 'https://raw.githubusercontent.com/espressif/arduino-esp32/' \ - '1.0.4/tools/sdk/bin/bootloader_$FLASH_MODE$_$FLASH_FREQ$.bin' -ESP32_DEFAULT_PARTITIONS = 'https://raw.githubusercontent.com/esphome/esphomeflasher/main/partitions.bin' +ESP32_DEFAULT_OTA_DATA = "https://raw.githubusercontent.com/espressif/arduino-esp32/1.0.0/tools/partitions/boot_app0.bin" +ESP32_DEFAULT_BOOTLOADER_FORMAT = ( + "https://raw.githubusercontent.com/espressif/arduino-esp32/" + "1.0.4/tools/sdk/bin/bootloader_$FLASH_MODE$_$FLASH_FREQ$.bin" +) +ESP32_DEFAULT_PARTITIONS = ( + "https://raw.githubusercontent.com/esphome/esphomeflasher/main/partitions.bin" +) # https://stackoverflow.com/a/3809435/8924614 -HTTP_REGEX = re.compile(r'https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)') +HTTP_REGEX = re.compile( + r"https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)" +) diff --git a/esphomeflasher/gui.py b/esphomeflasher/gui.py index 8675927..dfe7c9b 100644 --- a/esphomeflasher/gui.py +++ b/esphomeflasher/gui.py @@ -1,28 +1,29 @@ # This GUI is a fork of the brilliant https://github.com/marcelstoer/nodemcu-pyflasher -from io import TextIOBase import re import sys import threading +from io import TextIOBase import wx import wx.adv -from wx.lib.embeddedimage import PyEmbeddedImage import wx.lib.inspection import wx.lib.mixins.inspection +from wx.lib.embeddedimage import PyEmbeddedImage from esphomeflasher.helpers import list_serial_ports +# pylint: disable=no-member -COLOR_RE = re.compile(r'(?:\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))') +COLOR_RE = re.compile(r"(?:\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))") COLORS = { - 'black': wx.BLACK, - 'red': wx.RED, - 'green': wx.GREEN, - 'yellow': wx.YELLOW, - 'blue': wx.BLUE, - 'magenta': wx.Colour(255, 0, 255), - 'cyan': wx.CYAN, - 'white': wx.WHITE, + "black": wx.BLACK, + "red": wx.RED, + "green": wx.GREEN, + "yellow": wx.YELLOW, + "blue": wx.BLUE, + "magenta": wx.Colour(255, 0, 255), + "cyan": wx.CYAN, + "white": wx.WHITE, } FORE_COLORS = {**COLORS, None: wx.WHITE} BACK_COLORS = {**COLORS, None: wx.BLACK} @@ -31,9 +32,10 @@ BACK_COLORS = {**COLORS, None: wx.BLACK} # See discussion at http://stackoverflow.com/q/41101897/131929 class RedirectText(TextIOBase): def __init__(self, text_ctrl): + super().__init__() self._out = text_ctrl self._i = 0 - self._line = '' + self._line = "" self._bold = False self._italic = False self._underline = False @@ -61,7 +63,7 @@ class RedirectText(TextIOBase): self._add_content(self._line[pos:j]) pos = match.end() - for code in match.group(1).split(';'): + for code in match.group(1).split(";"): code = int(code) if code == 0: self._bold = False @@ -87,58 +89,59 @@ class RedirectText(TextIOBase): elif code == 24: self._underline = False elif code == 30: - self._foreground = 'black' + self._foreground = "black" elif code == 31: - self._foreground = 'red' + self._foreground = "red" elif code == 32: - self._foreground = 'green' + self._foreground = "green" elif code == 33: - self._foreground = 'yellow' + self._foreground = "yellow" elif code == 34: - self._foreground = 'blue' + self._foreground = "blue" elif code == 35: - self._foreground = 'magenta' + self._foreground = "magenta" elif code == 36: - self._foreground = 'cyan' + self._foreground = "cyan" elif code == 37: - self._foreground = 'white' + self._foreground = "white" elif code == 39: self._foreground = None elif code == 40: - self._background = 'black' + self._background = "black" elif code == 41: - self._background = 'red' + self._background = "red" elif code == 42: - self._background = 'green' + self._background = "green" elif code == 43: - self._background = 'yellow' + self._background = "yellow" elif code == 44: - self._background = 'blue' + self._background = "blue" elif code == 45: - self._background = 'magenta' + self._background = "magenta" elif code == 46: - self._background = 'cyan' + self._background = "cyan" elif code == 47: - self._background = 'white' + self._background = "white" elif code == 49: self._background = None self._add_content(self._line[pos:]) def write(self, string): + # pylint: disable=invalid-name for s in string: - if s == '\r': + if s == "\r": current_value = self._out.GetValue() last_newline = current_value.rfind("\n") wx.CallAfter(self._out.Remove, last_newline + 1, len(current_value)) # self._line += '\n' self._write_line() - self._line = '' + self._line = "" continue self._line += s - if s == '\n': + if s == "\n": self._write_line() - self._line = '' + self._line = "" continue def writable(self): @@ -161,19 +164,25 @@ class FlashingThread(threading.Thread): try: from esphomeflasher.__main__ import run_esphomeflasher - argv = ['esphomeflasher', '--port', self._port, self._firmware] + argv = ["esphomeflasher", "--port", self._port, self._firmware] if self._show_logs: - argv.append('--show-logs') + argv.append("--show-logs") run_esphomeflasher(argv) - except Exception as e: - print("Unexpected error: {}".format(e)) + except Exception as err: + print(f"Unexpected error: {err}") raise class MainFrame(wx.Frame): def __init__(self, parent, title): - wx.Frame.__init__(self, parent, -1, title, size=(725, 650), - style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE) + wx.Frame.__init__( + self, + parent, + -1, + title, + size=(725, 650), + style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE, + ) self._firmware = None self._port = None @@ -197,7 +206,7 @@ class MainFrame(wx.Frame): def on_logs_clicked(event): self.console_ctrl.SetValue("") - worker = FlashingThread(self, 'dummy', self._port, show_logs=True) + worker = FlashingThread(self, "dummy", self._port, show_logs=True) worker.start() def on_select_port(event): @@ -216,8 +225,12 @@ class MainFrame(wx.Frame): self.choice = wx.Choice(panel, choices=self._get_serial_ports()) self.choice.Bind(wx.EVT_CHOICE, on_select_port) bmp = Reload.GetBitmap() - reload_button = wx.BitmapButton(panel, id=wx.ID_ANY, bitmap=bmp, - size=(bmp.GetWidth() + 7, bmp.GetHeight() + 7)) + reload_button = wx.BitmapButton( + panel, + id=wx.ID_ANY, + bitmap=bmp, + size=(bmp.GetWidth() + 7, bmp.GetHeight() + 7), + ) reload_button.Bind(wx.EVT_BUTTON, on_reload) reload_button.SetToolTip("Reload serial device list") @@ -235,9 +248,17 @@ class MainFrame(wx.Frame): logs_button = wx.Button(panel, -1, "View Logs") logs_button.Bind(wx.EVT_BUTTON, on_logs_clicked) - self.console_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL) - self.console_ctrl.SetFont(wx.Font((0, 13), wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, - wx.FONTWEIGHT_NORMAL)) + self.console_ctrl = wx.TextCtrl( + panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL + ) + self.console_ctrl.SetFont( + wx.Font( + (0, 13), + wx.FONTFAMILY_TELETYPE, + wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_NORMAL, + ) + ) self.console_ctrl.SetBackgroundColour(wx.BLACK) self.console_ctrl.SetForegroundColour(wx.WHITE) self.console_ctrl.SetDefaultStyle(wx.TextAttr(wx.WHITE)) @@ -247,18 +268,25 @@ class MainFrame(wx.Frame): console_label = wx.StaticText(panel, label="Console") - fgs.AddMany([ - # Port selection row - port_label, (serial_boxsizer, 1, wx.EXPAND), - # Firmware selection row (growable) - file_label, (file_picker, 1, wx.EXPAND), - # Flash ESP button - (wx.StaticText(panel, label="")), (button, 1, wx.EXPAND), - # View Logs button - (wx.StaticText(panel, label="")), (logs_button, 1, wx.EXPAND), - # Console View (growable) - (console_label, 1, wx.EXPAND), (self.console_ctrl, 1, wx.EXPAND), - ]) + fgs.AddMany( + [ + # Port selection row + port_label, + (serial_boxsizer, 1, wx.EXPAND), + # Firmware selection row (growable) + file_label, + (file_picker, 1, wx.EXPAND), + # Flash ESP button + (wx.StaticText(panel, label="")), + (button, 1, wx.EXPAND), + # View Logs button + (wx.StaticText(panel, label="")), + (logs_button, 1, wx.EXPAND), + # Console View (growable) + (console_label, 1, wx.EXPAND), + (self.console_ctrl, 1, wx.EXPAND), + ] + ) fgs.AddGrowableRow(4, 1) fgs.AddGrowableCol(1, 1) hbox.Add(fgs, proportion=2, flag=wx.ALL | wx.EXPAND, border=15) @@ -266,7 +294,7 @@ class MainFrame(wx.Frame): def _get_serial_ports(self): ports = [] - for port, desc in list_serial_ports(): + for port, _ in list_serial_ports(): ports.append(port) if not self._port and ports: self._port = ports[0] @@ -283,6 +311,7 @@ class MainFrame(wx.Frame): class App(wx.App, wx.lib.mixins.inspection.InspectionMixin): + # pylint: disable=invalid-name def OnInit(self): wx.SystemOptions.SetOption("mac.window-plain-transition", 1) self.SetAppName("esphome-flasher (Based on NodeMCU PyFlasher)") @@ -312,7 +341,8 @@ Exit = PyEmbeddedImage( "3Rl+LVvOwG1syMBrYcbwfetmhmsOdgy/795iuMXEwnDh89c2oJ7jIL0AAQR2wQRgXvgKNAfo" "qRIlJfk2NR42Rj5gEmb5+4/h35+/DJ+/fmd4DUyNN4B+v/DlWwcwcTWzA9PXQqBegACCGwAK" "ERD+zsBgwszOXirEwe7OzvCP5y/QCx/+/v/26vfv/R///O0GOvkII1AdKxCDDAAIIEZKszNA" - "gAEA1sFjF+2KokIAAAAASUVORK5CYII=") + "gAEA1sFjF+2KokIAAAAASUVORK5CYII=" +) Reload = PyEmbeddedImage( "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGOfPtRkwAAACBj" @@ -332,7 +362,8 @@ Reload = PyEmbeddedImage( "wS9NKRdXQr6kgeuBfwEbWdzTvan9igAAADV0RVh0Y29tbWVudABSZWZyZXNoIGZyb20gSWNv" "biBHYWxsZXJ5IGh0dHA6Ly9pY29uZ2FsLmNvbS/RLzdIAAAAJXRFWHRkYXRlOmNyZWF0ZQAy" "MDExLTA4LTIxVDE0OjAxOjU2LTA2OjAwdNJAnQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0w" - "OC0yMVQxNDowMTo1Ni0wNjowMAWP+CEAAAAASUVORK5CYII=") + "OC0yMVQxNDowMTo1Ni0wNjowMAWP+CEAAAAASUVORK5CYII=" +) def main(): diff --git a/esphomeflasher/helpers.py b/esphomeflasher/helpers.py index f1973d8..e99c14e 100644 --- a/esphomeflasher/helpers.py +++ b/esphomeflasher/helpers.py @@ -5,17 +5,19 @@ import sys import serial -DEVNULL = open(os.devnull, 'w') +# pylint: disable=unspecified-encoding,consider-using-with +DEVNULL = open(os.devnull, "w") def list_serial_ports(): # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py from serial.tools.list_ports import comports + result = [] for port, desc, info in comports(): if not port or "VID:PID" not in info: continue - split_desc = desc.split(' - ') + split_desc = desc.split(" - ") if len(split_desc) == 2 and split_desc[0] == split_desc[1]: desc = split_desc[0] result.append((port, desc)) @@ -31,7 +33,6 @@ def prevent_print(func, *args, **kwargs): except serial.SerialException as err: from esphomeflasher.common import EsphomeflasherError - raise EsphomeflasherError("Serial port closed: {}".format(err)) + raise EsphomeflasherError(f"Serial port closed: {err}") from err finally: sys.stdout = orig_sys_stdout - pass diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2a30ada --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.isort] +profile = "black" +multi_line_output = 3 + +[tool.black] +target-version = ['py37'] + +[tool.pylint.MASTER] +reports = 'no' +disable = [ + "too-many-branches", + "missing-function-docstring", + "missing-module-docstring", + "too-many-statements", + "import-outside-toplevel", + "line-too-long", + "missing-class-docstring", + "too-few-public-methods", + "too-many-arguments", + "too-many-instance-attributes", + "too-many-locals", + "unused-argument", + "cyclic-import", +] diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..86756c5 --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,4 @@ +pylint==2.11.1 +black==21.9b0 +flake8==4.0.1 +isort==5.9.3 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..0636dd3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,14 @@ +[flake8] +max-line-length = 120 +# Following 4 for black compatibility +# E501: line too long +# W503: Line break occurred before a binary operator +# E203: Whitespace before ':' +# D202 No blank lines allowed after function docstring + +ignore = + E501, + W503, + E203, + D202, +