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
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)

View File

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

View File

@ -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@:%_+.~#?&/=]*)"
)

View File

@ -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():

View File

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

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,