Add python linting (#80)

This commit is contained in:
Otto Winter 2021-10-29 12:48:46 +02:00 committed by GitHub
parent b9dd803e1a
commit e7ea643a36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 458 additions and 178 deletions

70
.github/workflows/lint.yml vendored Normal file
View File

@ -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' }}

30
.github/workflows/matchers/flake8.json vendored Normal file
View File

@ -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
}
]
}
]
}

14
.github/workflows/matchers/isort.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"problemMatcher": [
{
"owner": "isort",
"pattern": [
{
"regexp": "^ERROR:\\s+(.+)\\s+(.+)$",
"file": 1,
"message": 2
}
]
}
]
}

32
.github/workflows/matchers/pylint.json vendored Normal file
View File

@ -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
}
]
}
]
}

View File

@ -1,51 +1,70 @@
from __future__ import print_function from __future__ import print_function
import argparse import argparse
from datetime import datetime
import sys import sys
import time import time
from datetime import datetime
import esptool import esptool
import serial import serial
from esphomeflasher import const from esphomeflasher import const
from esphomeflasher.common import ESP32ChipInfo, EsphomeflasherError, chip_run_stub, \ from esphomeflasher.common import (
configure_write_flash_args, detect_chip, detect_flash_size, read_chip_info ESP32ChipInfo,
from esphomeflasher.const import ESP32_DEFAULT_BOOTLOADER_FORMAT, ESP32_DEFAULT_OTA_DATA, \ EsphomeflasherError,
ESP32_DEFAULT_PARTITIONS 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 from esphomeflasher.helpers import list_serial_ports
def parse_args(argv): def parse_args(argv):
parser = argparse.ArgumentParser(prog='esphomeflasher {}'.format(const.__version__)) parser = argparse.ArgumentParser(prog=f"esphomeflasher {const.__version__}")
parser.add_argument('-p', '--port', parser.add_argument("-p", "--port", help="Select the USB/COM port for uploading.")
help="Select the USB/COM port for uploading.")
group = parser.add_mutually_exclusive_group(required=False) group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('--esp8266', action='store_true') group.add_argument("--esp8266", action="store_true")
group.add_argument('--esp32', action='store_true') group.add_argument("--esp32", action="store_true")
group.add_argument('--upload-baud-rate', type=int, default=460800, group.add_argument(
help="Baud rate to upload with (not for logging)") "--upload-baud-rate",
parser.add_argument('--bootloader', type=int,
help="(ESP32-only) The bootloader to flash.", default=460800,
default=ESP32_DEFAULT_BOOTLOADER_FORMAT) help="Baud rate to upload with (not for logging)",
parser.add_argument('--partitions', )
help="(ESP32-only) The partitions to flash.", parser.add_argument(
default=ESP32_DEFAULT_PARTITIONS) "--bootloader",
parser.add_argument('--otadata', help="(ESP32-only) The bootloader to flash.",
help="(ESP32-only) The otadata file to flash.", default=ESP32_DEFAULT_BOOTLOADER_FORMAT,
default=ESP32_DEFAULT_OTA_DATA) )
parser.add_argument('--no-erase', parser.add_argument(
help="Do not erase flash before flashing", "--partitions",
action='store_true') help="(ESP32-only) The partitions to flash.",
parser.add_argument('--show-logs', help="Only show logs", action='store_true') default=ESP32_DEFAULT_PARTITIONS,
parser.add_argument('binary', help="The binary image to flash.") )
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:]) return parser.parse_args(argv[1:])
def select_port(args): def select_port(args):
if args.port is not None: 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 return args.port
ports = list_serial_ports() ports = list_serial_ports()
if not ports: if not ports:
@ -53,10 +72,10 @@ def select_port(args):
if len(ports) != 1: if len(ports) != 1:
print("Found more than one serial port:") print("Found more than one serial port:")
for port, desc in ports: for port, desc in ports:
print(u" * {} ({})".format(port, desc)) print(f" * {port} ({desc})")
print("Please choose one with the --port argument.") print("Please choose one with the --port argument.")
raise EsphomeflasherError 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] return ports[0][0]
@ -69,14 +88,14 @@ def show_logs(serial_port):
except serial.SerialException: except serial.SerialException:
print("Serial port closed!") print("Serial port closed!")
return return
text = raw.decode(errors='ignore') text = raw.decode(errors="ignore")
line = text.replace('\r', '').replace('\n', '') line = text.replace("\r", "").replace("\n", "")
time = datetime.now().time().strftime('[%H:%M:%S]') time_ = datetime.now().time().strftime("[%H:%M:%S]")
message = time + line message = time_ + line
try: try:
print(message) print(message)
except UnicodeEncodeError: except UnicodeEncodeError:
print(message.encode('ascii', 'backslashreplace')) print(message.encode("ascii", "backslashreplace"))
def run_esphomeflasher(argv): def run_esphomeflasher(argv):
@ -89,27 +108,29 @@ def run_esphomeflasher(argv):
return return
try: try:
firmware = open(args.binary, 'rb') # pylint: disable=consider-using-with
firmware = open(args.binary, "rb")
except IOError as err: 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) chip = detect_chip(port, args.esp8266, args.esp32)
info = read_chip_info(chip) info = read_chip_info(chip)
print() print()
print("Chip Info:") print("Chip Info:")
print(" - Chip Family: {}".format(info.family)) print(f" - Chip Family: {info.family}")
print(" - Chip Model: {}".format(info.model)) print(f" - Chip Model: {info.model}")
if isinstance(info, ESP32ChipInfo): if isinstance(info, ESP32ChipInfo):
print(" - Number of Cores: {}".format(info.num_cores)) print(f" - Number of Cores: {info.num_cores}")
print(" - Max CPU Frequency: {}".format(info.cpu_frequency)) print(f" - Max CPU Frequency: {info.cpu_frequency}")
print(" - Has Bluetooth: {}".format('YES' if info.has_bluetooth else 'NO')) print(f" - Has Bluetooth: {'YES' if info.has_bluetooth else 'NO'}")
print(" - Has Embedded Flash: {}".format('YES' if info.has_embedded_flash else 'NO')) print(f" - Has Embedded Flash: {'YES' if info.has_embedded_flash else 'NO'}")
print(" - Has Factory-Calibrated ADC: {}".format( print(
'YES' if info.has_factory_calibrated_adc else 'NO')) f" - Has Factory-Calibrated ADC: {'YES' if info.has_factory_calibrated_adc else 'NO'}"
)
else: 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) stub_chip = chip_run_stub(chip)
flash_size = None flash_size = None
@ -118,14 +139,19 @@ def run_esphomeflasher(argv):
try: try:
stub_chip.change_baud(args.upload_baud_rate) stub_chip.change_baud(args.upload_baud_rate)
except esptool.FatalError as err: 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 # Check if the higher baud rate works
try: try:
flash_size = detect_flash_size(stub_chip) flash_size = detect_flash_size(stub_chip)
except EsphomeflasherError as err: except EsphomeflasherError:
# Go back to old baud rate by recreating chip instance # 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() stub_chip._port.close()
chip = detect_chip(port, args.esp8266, args.esp32) chip = detect_chip(port, args.esp8266, args.esp32)
stub_chip = chip_run_stub(chip) stub_chip = chip_run_stub(chip)
@ -133,31 +159,30 @@ def run_esphomeflasher(argv):
if flash_size is None: if flash_size is None:
flash_size = detect_flash_size(stub_chip) 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, print(f" - Flash Mode: {mock_args.flash_mode}")
args.bootloader, args.partitions, print(f" - Flash Frequency: {mock_args.flash_freq.upper()}Hz")
args.otadata)
print(" - Flash Mode: {}".format(mock_args.flash_mode))
print(" - Flash Frequency: {}Hz".format(mock_args.flash_freq.upper()))
try: try:
stub_chip.flash_set_parameters(esptool.flash_size_bytes(flash_size)) stub_chip.flash_set_parameters(esptool.flash_size_bytes(flash_size))
except esptool.FatalError as err: 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: if not args.no_erase:
try: try:
esptool.erase_flash(stub_chip, mock_args) esptool.erase_flash(stub_chip, mock_args)
except esptool.FatalError as err: except esptool.FatalError as err:
raise EsphomeflasherError("Error while erasing flash: {}".format(err)) raise EsphomeflasherError(f"Error while erasing flash: {err}") from err
try: try:
esptool.write_flash(stub_chip, mock_args) esptool.write_flash(stub_chip, mock_args)
except esptool.FatalError as err: 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...") print("Hard Resetting...")
stub_chip.hard_reset() stub_chip.hard_reset()
@ -166,10 +191,13 @@ def run_esphomeflasher(argv):
print() print()
if args.upload_baud_rate != 115200: if args.upload_baud_rate != 115200:
# pylint: disable=protected-access
stub_chip._port.baudrate = 115200 stub_chip._port.baudrate = 115200
time.sleep(0.05) # get rid of crap sent during baud rate change time.sleep(0.05) # get rid of crap sent during baud rate change
# pylint: disable=protected-access
stub_chip._port.flushInput() stub_chip._port.flushInput()
# pylint: disable=protected-access
show_logs(stub_chip._port) show_logs(stub_chip._port)

View File

@ -11,7 +11,7 @@ class EsphomeflasherError(Exception):
pass pass
class MockEsptoolArgs(object): class MockEsptoolArgs:
def __init__(self, flash_size, addr_filename, flash_mode, flash_freq): def __init__(self, flash_size, addr_filename, flash_mode, flash_freq):
self.compress = True self.compress = True
self.no_compress = False self.no_compress = False
@ -26,7 +26,7 @@ class MockEsptoolArgs(object):
self.encrypt_files = None self.encrypt_files = None
class ChipInfo(object): class ChipInfo:
def __init__(self, family, model, mac): def __init__(self, family, model, mac):
self.family = family self.family = family
self.model = model self.model = model
@ -35,17 +35,25 @@ class ChipInfo(object):
def as_dict(self): def as_dict(self):
return { return {
'family': self.family, "family": self.family,
'model': self.model, "model": self.model,
'mac': self.mac, "mac": self.mac,
'is_esp32': self.is_esp32, "is_esp32": self.is_esp32,
} }
class ESP32ChipInfo(ChipInfo): class ESP32ChipInfo(ChipInfo):
def __init__(self, model, mac, num_cores, cpu_frequency, has_bluetooth, has_embedded_flash, def __init__(
has_factory_calibrated_adc): self,
super(ESP32ChipInfo, self).__init__("ESP32", model, mac) 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.num_cores = num_cores
self.cpu_frequency = cpu_frequency self.cpu_frequency = cpu_frequency
self.has_bluetooth = has_bluetooth self.has_bluetooth = has_bluetooth
@ -54,26 +62,30 @@ class ESP32ChipInfo(ChipInfo):
def as_dict(self): def as_dict(self):
data = ChipInfo.as_dict(self) data = ChipInfo.as_dict(self)
data.update({ data.update(
'num_cores': self.num_cores, {
'cpu_frequency': self.cpu_frequency, "num_cores": self.num_cores,
'has_bluetooth': self.has_bluetooth, "cpu_frequency": self.cpu_frequency,
'has_embedded_flash': self.has_embedded_flash, "has_bluetooth": self.has_bluetooth,
'has_factory_calibrated_adc': self.has_factory_calibrated_adc, "has_embedded_flash": self.has_embedded_flash,
}) "has_factory_calibrated_adc": self.has_factory_calibrated_adc,
}
)
return data return data
class ESP8266ChipInfo(ChipInfo): class ESP8266ChipInfo(ChipInfo):
def __init__(self, model, mac, chip_id): def __init__(self, model, mac, chip_id):
super(ESP8266ChipInfo, self).__init__("ESP8266", model, mac) super().__init__("ESP8266", model, mac)
self.chip_id = chip_id self.chip_id = chip_id
def as_dict(self): def as_dict(self):
data = ChipInfo.as_dict(self) data = ChipInfo.as_dict(self)
data.update({ data.update(
'chip_id': self.chip_id, {
}) "chip_id": self.chip_id,
}
)
return data return data
@ -81,38 +93,47 @@ def read_chip_property(func, *args, **kwargs):
try: try:
return prevent_print(func, *args, **kwargs) return prevent_print(func, *args, **kwargs)
except esptool.FatalError as err: 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): 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): if isinstance(chip, esptool.ESP32ROM):
model = read_chip_property(chip.get_chip_description) model = read_chip_property(chip.get_chip_description)
features = read_chip_property(chip.get_chip_features) features = read_chip_property(chip.get_chip_features)
num_cores = 2 if 'Dual Core' in features else 1 num_cores = 2 if "Dual Core" in features else 1
frequency = next((x for x in ('160MHz', '240MHz') if x in features), '80MHz') frequency = next((x for x in ("160MHz", "240MHz") if x in features), "80MHz")
has_bluetooth = 'BT' in features has_bluetooth = "BT" in features
has_embedded_flash = 'Embedded Flash' in features has_embedded_flash = "Embedded Flash" in features
has_factory_calibrated_adc = 'VRef calibration in efuse' in features has_factory_calibrated_adc = "VRef calibration in efuse" in features
return ESP32ChipInfo(model, mac, num_cores, frequency, has_bluetooth, return ESP32ChipInfo(
has_embedded_flash, has_factory_calibrated_adc) model,
elif isinstance(chip, esptool.ESP8266ROM): 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) model = read_chip_property(chip.get_chip_description)
chip_id = read_chip_property(chip.chip_id) chip_id = read_chip_property(chip.chip_id)
return ESP8266ChipInfo(model, mac, 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): def chip_run_stub(chip):
try: try:
return chip.run_stub() return chip.run_stub()
except esptool.FatalError as err: 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): def detect_flash_size(stub_chip):
flash_id = read_chip_property(stub_chip.flash_id) 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): 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) magic, _, flash_mode_raw, flash_size_freq = struct.unpack("BBBB", header)
if magic != esptool.ESPLoader.ESP_IMAGE_MAGIC: if magic != esptool.ESPLoader.ESP_IMAGE_MAGIC:
raise EsphomeflasherError( raise EsphomeflasherError(
"The firmware binary is invalid (magic byte={:02X}, should be {:02X})" f"The firmware binary is invalid (magic byte={magic:02X}, should be {esptool.ESPLoader.ESP_IMAGE_MAGIC:02X})"
"".format(magic, esptool.ESPLoader.ESP_IMAGE_MAGIC)) )
flash_freq_raw = flash_size_freq & 0x0F flash_freq_raw = flash_size_freq & 0x0F
flash_mode = {0: 'qio', 1: 'qout', 2: 'dio', 3: 'dout'}.get(flash_mode_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) flash_freq = {0: "40m", 1: "26m", 2: "20m", 0xF: "80m"}.get(flash_freq_raw)
return flash_mode, flash_freq return flash_mode, flash_freq
def open_downloadable_binary(path): def open_downloadable_binary(path):
if hasattr(path, 'seek'): if hasattr(path, "seek"):
path.seek(0) path.seek(0)
return path return path
@ -143,10 +164,12 @@ def open_downloadable_binary(path):
response.raise_for_status() response.raise_for_status()
except requests.exceptions.Timeout as err: except requests.exceptions.Timeout as err:
raise EsphomeflasherError( 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: except requests.exceptions.RequestException as err:
raise EsphomeflasherError( raise EsphomeflasherError(
"Error while retrieving firmware file '{}': {}".format(path, err)) f"Error while retrieving firmware file '{path}': {err}"
) from err
binary = io.BytesIO() binary = io.BytesIO()
binary.write(response.content) binary.write(response.content)
@ -154,26 +177,29 @@ def open_downloadable_binary(path):
return binary return binary
try: try:
return open(path, 'rb') return open(path, "rb")
except IOError as err: 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): 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, def configure_write_flash_args(
bootloader_path, partitions_path, otadata_path): info, firmware_path, flash_size, bootloader_path, partitions_path, otadata_path
):
addr_filename = [] addr_filename = []
firmware = open_downloadable_binary(firmware_path) firmware = open_downloadable_binary(firmware_path)
flash_mode, flash_freq = read_firmware_info(firmware) flash_mode, flash_freq = read_firmware_info(firmware)
if isinstance(info, ESP32ChipInfo): if isinstance(info, ESP32ChipInfo):
if flash_freq in ('26m', '20m'): if flash_freq in ("26m", "20m"):
raise EsphomeflasherError( raise EsphomeflasherError(
"No bootloader available for flash frequency {}".format(flash_freq)) f"No bootloader available for flash frequency {flash_freq}"
)
bootloader = open_downloadable_binary( 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) partitions = open_downloadable_binary(partitions_path)
otadata = open_downloadable_binary(otadata_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): 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." msg = "ESP is not in flash boot mode. If your board has a flashing pin, try again while keeping it pressed."
else: else:
msg = "ESP Chip Auto-Detection failed: {}".format(err) msg = f"ESP Chip Auto-Detection failed: {err}"
raise EsphomeflasherError(msg) from err raise EsphomeflasherError(msg) from err
try: try:
chip.connect() chip.connect()
except esptool.FatalError as err: 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 return chip

View File

@ -2,10 +2,16 @@ import re
__version__ = "1.3.1" __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_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/' \ ESP32_DEFAULT_BOOTLOADER_FORMAT = (
'1.0.4/tools/sdk/bin/bootloader_$FLASH_MODE$_$FLASH_FREQ$.bin' "https://raw.githubusercontent.com/espressif/arduino-esp32/"
ESP32_DEFAULT_PARTITIONS = 'https://raw.githubusercontent.com/esphome/esphomeflasher/main/partitions.bin' "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 # 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@:%_+.~#?&/=]*)"
)

View File

@ -1,28 +1,29 @@
# This GUI is a fork of the brilliant https://github.com/marcelstoer/nodemcu-pyflasher # This GUI is a fork of the brilliant https://github.com/marcelstoer/nodemcu-pyflasher
from io import TextIOBase
import re import re
import sys import sys
import threading import threading
from io import TextIOBase
import wx import wx
import wx.adv import wx.adv
from wx.lib.embeddedimage import PyEmbeddedImage
import wx.lib.inspection import wx.lib.inspection
import wx.lib.mixins.inspection import wx.lib.mixins.inspection
from wx.lib.embeddedimage import PyEmbeddedImage
from esphomeflasher.helpers import list_serial_ports 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 = { COLORS = {
'black': wx.BLACK, "black": wx.BLACK,
'red': wx.RED, "red": wx.RED,
'green': wx.GREEN, "green": wx.GREEN,
'yellow': wx.YELLOW, "yellow": wx.YELLOW,
'blue': wx.BLUE, "blue": wx.BLUE,
'magenta': wx.Colour(255, 0, 255), "magenta": wx.Colour(255, 0, 255),
'cyan': wx.CYAN, "cyan": wx.CYAN,
'white': wx.WHITE, "white": wx.WHITE,
} }
FORE_COLORS = {**COLORS, None: wx.WHITE} FORE_COLORS = {**COLORS, None: wx.WHITE}
BACK_COLORS = {**COLORS, None: wx.BLACK} 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 # See discussion at http://stackoverflow.com/q/41101897/131929
class RedirectText(TextIOBase): class RedirectText(TextIOBase):
def __init__(self, text_ctrl): def __init__(self, text_ctrl):
super().__init__()
self._out = text_ctrl self._out = text_ctrl
self._i = 0 self._i = 0
self._line = '' self._line = ""
self._bold = False self._bold = False
self._italic = False self._italic = False
self._underline = False self._underline = False
@ -61,7 +63,7 @@ class RedirectText(TextIOBase):
self._add_content(self._line[pos:j]) self._add_content(self._line[pos:j])
pos = match.end() pos = match.end()
for code in match.group(1).split(';'): for code in match.group(1).split(";"):
code = int(code) code = int(code)
if code == 0: if code == 0:
self._bold = False self._bold = False
@ -87,58 +89,59 @@ class RedirectText(TextIOBase):
elif code == 24: elif code == 24:
self._underline = False self._underline = False
elif code == 30: elif code == 30:
self._foreground = 'black' self._foreground = "black"
elif code == 31: elif code == 31:
self._foreground = 'red' self._foreground = "red"
elif code == 32: elif code == 32:
self._foreground = 'green' self._foreground = "green"
elif code == 33: elif code == 33:
self._foreground = 'yellow' self._foreground = "yellow"
elif code == 34: elif code == 34:
self._foreground = 'blue' self._foreground = "blue"
elif code == 35: elif code == 35:
self._foreground = 'magenta' self._foreground = "magenta"
elif code == 36: elif code == 36:
self._foreground = 'cyan' self._foreground = "cyan"
elif code == 37: elif code == 37:
self._foreground = 'white' self._foreground = "white"
elif code == 39: elif code == 39:
self._foreground = None self._foreground = None
elif code == 40: elif code == 40:
self._background = 'black' self._background = "black"
elif code == 41: elif code == 41:
self._background = 'red' self._background = "red"
elif code == 42: elif code == 42:
self._background = 'green' self._background = "green"
elif code == 43: elif code == 43:
self._background = 'yellow' self._background = "yellow"
elif code == 44: elif code == 44:
self._background = 'blue' self._background = "blue"
elif code == 45: elif code == 45:
self._background = 'magenta' self._background = "magenta"
elif code == 46: elif code == 46:
self._background = 'cyan' self._background = "cyan"
elif code == 47: elif code == 47:
self._background = 'white' self._background = "white"
elif code == 49: elif code == 49:
self._background = None self._background = None
self._add_content(self._line[pos:]) self._add_content(self._line[pos:])
def write(self, string): def write(self, string):
# pylint: disable=invalid-name
for s in string: for s in string:
if s == '\r': if s == "\r":
current_value = self._out.GetValue() current_value = self._out.GetValue()
last_newline = current_value.rfind("\n") last_newline = current_value.rfind("\n")
wx.CallAfter(self._out.Remove, last_newline + 1, len(current_value)) wx.CallAfter(self._out.Remove, last_newline + 1, len(current_value))
# self._line += '\n' # self._line += '\n'
self._write_line() self._write_line()
self._line = '' self._line = ""
continue continue
self._line += s self._line += s
if s == '\n': if s == "\n":
self._write_line() self._write_line()
self._line = '' self._line = ""
continue continue
def writable(self): def writable(self):
@ -161,19 +164,25 @@ class FlashingThread(threading.Thread):
try: try:
from esphomeflasher.__main__ import run_esphomeflasher from esphomeflasher.__main__ import run_esphomeflasher
argv = ['esphomeflasher', '--port', self._port, self._firmware] argv = ["esphomeflasher", "--port", self._port, self._firmware]
if self._show_logs: if self._show_logs:
argv.append('--show-logs') argv.append("--show-logs")
run_esphomeflasher(argv) run_esphomeflasher(argv)
except Exception as e: except Exception as err:
print("Unexpected error: {}".format(e)) print(f"Unexpected error: {err}")
raise raise
class MainFrame(wx.Frame): class MainFrame(wx.Frame):
def __init__(self, parent, title): def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(725, 650), wx.Frame.__init__(
style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE) self,
parent,
-1,
title,
size=(725, 650),
style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE,
)
self._firmware = None self._firmware = None
self._port = None self._port = None
@ -197,7 +206,7 @@ class MainFrame(wx.Frame):
def on_logs_clicked(event): def on_logs_clicked(event):
self.console_ctrl.SetValue("") self.console_ctrl.SetValue("")
worker = FlashingThread(self, 'dummy', self._port, show_logs=True) worker = FlashingThread(self, "dummy", self._port, show_logs=True)
worker.start() worker.start()
def on_select_port(event): 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 = wx.Choice(panel, choices=self._get_serial_ports())
self.choice.Bind(wx.EVT_CHOICE, on_select_port) self.choice.Bind(wx.EVT_CHOICE, on_select_port)
bmp = Reload.GetBitmap() bmp = Reload.GetBitmap()
reload_button = wx.BitmapButton(panel, id=wx.ID_ANY, bitmap=bmp, reload_button = wx.BitmapButton(
size=(bmp.GetWidth() + 7, bmp.GetHeight() + 7)) panel,
id=wx.ID_ANY,
bitmap=bmp,
size=(bmp.GetWidth() + 7, bmp.GetHeight() + 7),
)
reload_button.Bind(wx.EVT_BUTTON, on_reload) reload_button.Bind(wx.EVT_BUTTON, on_reload)
reload_button.SetToolTip("Reload serial device list") 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 = wx.Button(panel, -1, "View Logs")
logs_button.Bind(wx.EVT_BUTTON, on_logs_clicked) 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 = wx.TextCtrl(
self.console_ctrl.SetFont(wx.Font((0, 13), wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL
wx.FONTWEIGHT_NORMAL)) )
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.SetBackgroundColour(wx.BLACK)
self.console_ctrl.SetForegroundColour(wx.WHITE) self.console_ctrl.SetForegroundColour(wx.WHITE)
self.console_ctrl.SetDefaultStyle(wx.TextAttr(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") console_label = wx.StaticText(panel, label="Console")
fgs.AddMany([ fgs.AddMany(
# Port selection row [
port_label, (serial_boxsizer, 1, wx.EXPAND), # Port selection row
# Firmware selection row (growable) port_label,
file_label, (file_picker, 1, wx.EXPAND), (serial_boxsizer, 1, wx.EXPAND),
# Flash ESP button # Firmware selection row (growable)
(wx.StaticText(panel, label="")), (button, 1, wx.EXPAND), file_label,
# View Logs button (file_picker, 1, wx.EXPAND),
(wx.StaticText(panel, label="")), (logs_button, 1, wx.EXPAND), # Flash ESP button
# Console View (growable) (wx.StaticText(panel, label="")),
(console_label, 1, wx.EXPAND), (self.console_ctrl, 1, wx.EXPAND), (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.AddGrowableRow(4, 1)
fgs.AddGrowableCol(1, 1) fgs.AddGrowableCol(1, 1)
hbox.Add(fgs, proportion=2, flag=wx.ALL | wx.EXPAND, border=15) 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): def _get_serial_ports(self):
ports = [] ports = []
for port, desc in list_serial_ports(): for port, _ in list_serial_ports():
ports.append(port) ports.append(port)
if not self._port and ports: if not self._port and ports:
self._port = ports[0] self._port = ports[0]
@ -283,6 +311,7 @@ class MainFrame(wx.Frame):
class App(wx.App, wx.lib.mixins.inspection.InspectionMixin): class App(wx.App, wx.lib.mixins.inspection.InspectionMixin):
# pylint: disable=invalid-name
def OnInit(self): def OnInit(self):
wx.SystemOptions.SetOption("mac.window-plain-transition", 1) wx.SystemOptions.SetOption("mac.window-plain-transition", 1)
self.SetAppName("esphome-flasher (Based on NodeMCU PyFlasher)") self.SetAppName("esphome-flasher (Based on NodeMCU PyFlasher)")
@ -312,7 +341,8 @@ Exit = PyEmbeddedImage(
"3Rl+LVvOwG1syMBrYcbwfetmhmsOdgy/795iuMXEwnDh89c2oJ7jIL0AAQR2wQRgXvgKNAfo" "3Rl+LVvOwG1syMBrYcbwfetmhmsOdgy/795iuMXEwnDh89c2oJ7jIL0AAQR2wQRgXvgKNAfo"
"qRIlJfk2NR42Rj5gEmb5+4/h35+/DJ+/fmd4DUyNN4B+v/DlWwcwcTWzA9PXQqBegACCGwAK" "qRIlJfk2NR42Rj5gEmb5+4/h35+/DJ+/fmd4DUyNN4B+v/DlWwcwcTWzA9PXQqBegACCGwAK"
"ERD+zsBgwszOXirEwe7OzvCP5y/QCx/+/v/26vfv/R///O0GOvkII1AdKxCDDAAIIEZKszNA" "ERD+zsBgwszOXirEwe7OzvCP5y/QCx/+/v/26vfv/R///O0GOvkII1AdKxCDDAAIIEZKszNA"
"gAEA1sFjF+2KokIAAAAASUVORK5CYII=") "gAEA1sFjF+2KokIAAAAASUVORK5CYII="
)
Reload = PyEmbeddedImage( Reload = PyEmbeddedImage(
"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGOfPtRkwAAACBj" "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGOfPtRkwAAACBj"
@ -332,7 +362,8 @@ Reload = PyEmbeddedImage(
"wS9NKRdXQr6kgeuBfwEbWdzTvan9igAAADV0RVh0Y29tbWVudABSZWZyZXNoIGZyb20gSWNv" "wS9NKRdXQr6kgeuBfwEbWdzTvan9igAAADV0RVh0Y29tbWVudABSZWZyZXNoIGZyb20gSWNv"
"biBHYWxsZXJ5IGh0dHA6Ly9pY29uZ2FsLmNvbS/RLzdIAAAAJXRFWHRkYXRlOmNyZWF0ZQAy" "biBHYWxsZXJ5IGh0dHA6Ly9pY29uZ2FsLmNvbS/RLzdIAAAAJXRFWHRkYXRlOmNyZWF0ZQAy"
"MDExLTA4LTIxVDE0OjAxOjU2LTA2OjAwdNJAnQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0w" "MDExLTA4LTIxVDE0OjAxOjU2LTA2OjAwdNJAnQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0w"
"OC0yMVQxNDowMTo1Ni0wNjowMAWP+CEAAAAASUVORK5CYII=") "OC0yMVQxNDowMTo1Ni0wNjowMAWP+CEAAAAASUVORK5CYII="
)
def main(): def main():

View File

@ -5,17 +5,19 @@ import sys
import serial import serial
DEVNULL = open(os.devnull, 'w') # pylint: disable=unspecified-encoding,consider-using-with
DEVNULL = open(os.devnull, "w")
def list_serial_ports(): def list_serial_ports():
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
from serial.tools.list_ports import comports from serial.tools.list_ports import comports
result = [] result = []
for port, desc, info in comports(): for port, desc, info in comports():
if not port or "VID:PID" not in info: if not port or "VID:PID" not in info:
continue continue
split_desc = desc.split(' - ') split_desc = desc.split(" - ")
if len(split_desc) == 2 and split_desc[0] == split_desc[1]: if len(split_desc) == 2 and split_desc[0] == split_desc[1]:
desc = split_desc[0] desc = split_desc[0]
result.append((port, desc)) result.append((port, desc))
@ -31,7 +33,6 @@ def prevent_print(func, *args, **kwargs):
except serial.SerialException as err: except serial.SerialException as err:
from esphomeflasher.common import EsphomeflasherError from esphomeflasher.common import EsphomeflasherError
raise EsphomeflasherError("Serial port closed: {}".format(err)) raise EsphomeflasherError(f"Serial port closed: {err}") from err
finally: finally:
sys.stdout = orig_sys_stdout sys.stdout = orig_sys_stdout
pass

24
pyproject.toml Normal file
View File

@ -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",
]

4
requirements_test.txt Normal file
View File

@ -0,0 +1,4 @@
pylint==2.11.1
black==21.9b0
flake8==4.0.1
isort==5.9.3

14
setup.cfg Normal file
View File

@ -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,