Initial commit

This commit is contained in:
Otto Winter 2018-11-07 21:24:25 +01:00
commit 6cf760fc3c
No known key found for this signature in database
GPG Key ID: DB66C0BE6013F97E
11 changed files with 865 additions and 0 deletions

106
.gitignore vendored Normal file
View File

@ -0,0 +1,106 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
config/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Marcel Stör, Otto Winter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# esphomeflasher
esphomeflasher is a utility app for the [esphomelib](https://esphomelib.com/esphomeyaml/index.html)
framework and is designed to make flashing ESPs with esphomelib as simple as possible by:
* Having pre-built binaries for most operating systems.
* Hiding all non-essential options for flashing. All necessary options for flashing
(bootloader, flash mode) are automatically extracted from the binary.
This project was originally intended to be a simple command-line tool,
but then I decided that a GUI would be nice. As I don't like writing graphical
front end code, the GUI largely is based on the
[NodeMCU PyFlasher](https://github.com/marcelstoer/nodemcu-pyflasher)
project.
The flashing process is done using the [esptool](https://github.com/espressif/esptool)
library by espressif.
## Installation
Es doesn't have to be installed, just double-click it and it'll start.
Check the [releases section](https://github.com/OttoWinter/esphomeflasher/releases)
for downloads for your platform.
## Installation Using `pip`
If you want to install this application from `pip`:
- Install Python 3.x
- Install [wxPython 4.x](https://wxpython.org/) manually or run `pip3 install wxpython`
- Install this project using `pip3 install esphomeflasher`
- Start the GUI using `esphomeflasher`. Alternatively, you can use the command line interface (
type `esphomeflasher -h` for info)
## Build it yourself
If you want to build this application yourself you need to:
- Install Python 3.x
- Install [wxPython 4.x](https://wxpython.org/) manually or run `pip3 install wxpython`
- Download this project and run `pip3 install -e .` in the project's root.
- Start the GUI using `esphomeflasher`. Alternatively, you can use the command line interface (
type `esphomeflasher -h` for info)
## License
[MIT](http://opensource.org/licenses/MIT) © Marcel Stör, Otto Winter

View File

156
esphomeflasher/__main__.py Normal file
View File

@ -0,0 +1,156 @@
from __future__ import print_function
import argparse
from datetime import datetime
import re
import sys
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.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.")
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('--esp8266', action='store_true')
group.add_argument('--esp32', action='store_true')
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('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))
return args.port
ports = list_serial_ports()
if not ports:
raise EsphomeflasherError("No serial port found!")
if len(ports) != 1:
print("Found more than one serial port:")
for port, desc in ports:
print(u" * {} ({})".format(port, desc))
print("Please choose one with the --port argument.")
raise EsphomeflasherError
print(u"Auto-detected serial port: {}".format(ports[0][0]))
return ports[0][0]
ANSI_REGEX = re.compile(r"\033\[[0-9;]*m")
def run_esphomeflasher(argv):
args = parse_args(argv)
try:
firmware = open(args.binary, 'rb')
except IOError as err:
raise EsphomeflasherError("Error opening binary: {}".format(err))
port = select_port(args)
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))
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'))
else:
print(" - Chip ID: {:08X}".format(info.chip_id))
print(" - MAC Address: {}".format(info.mac))
stub_chip = chip_run_stub(chip)
flash_size = detect_flash_size(stub_chip)
print(" - Flash Size: {}".format(flash_size))
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))
mock_args = configure_write_flash_args(info, firmware, flash_size,
args.bootloader, args.partitions,
args.otadata)
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))
try:
esptool.write_flash(stub_chip, mock_args)
except esptool.FatalError as err:
raise EsphomeflasherError("Error while writing flash: {}".format(err))
print("Hard Resetting...")
stub_chip.hard_reset()
print("Done! Flashing is complete!")
print()
print("Showing logs:")
with stub_chip._port as ser:
while True:
try:
raw = ser.readline()
except serial.SerialException:
print("Serial port closed!")
return
text = raw.decode(errors='ignore')
ANSI_REGEX.sub('', text)
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'))
def main():
try:
if len(sys.argv) <= 1:
from esphomeflasher import gui
return gui.main() or 0
return run_esphomeflasher(sys.argv) or 0
except EsphomeflasherError as err:
msg = str(err)
if msg:
print(msg)
return 1
except KeyboardInterrupt:
return 1
if __name__ == "__main__":
sys.exit(main())

201
esphomeflasher/common.py Normal file
View File

@ -0,0 +1,201 @@
import io
import struct
import esptool
from esphomeflasher.const import HTTP_REGEX
from esphomeflasher.helpers import prevent_print
class EsphomeflasherError(Exception):
pass
class MockEsptoolArgs(object):
def __init__(self, flash_size, addr_filename):
self.compress = True
self.no_compress = False
self.flash_size = flash_size
self.addr_filename = addr_filename
self.flash_mode = 'keep'
self.flash_freq = 'keep'
self.no_stub = False
self.verify = False
class ChipInfo(object):
def __init__(self, family, model, mac):
self.family = family
self.model = model
self.mac = mac
self.is_esp32 = None
def as_dict(self):
return {
'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)
self.num_cores = num_cores
self.cpu_frequency = cpu_frequency
self.has_bluetooth = has_bluetooth
self.has_embedded_flash = has_embedded_flash
self.has_factory_calibrated_adc = has_factory_calibrated_adc
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,
})
return data
class ESP8266ChipInfo(ChipInfo):
def __init__(self, model, mac, chip_id):
super(ESP8266ChipInfo, self).__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,
})
return data
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))
def read_chip_info(chip):
mac = ':'.join('{:02X}'.format(x) 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):
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)))
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))
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')
def read_firmware_info(firmware):
header = firmware.read(4)
firmware.seek(0)
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))
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)
return flash_mode, flash_freq
def open_downloadable_binary(path):
if hasattr(path, 'seek'):
path.seek(0)
return path
if HTTP_REGEX.match(path) is not None:
import requests
try:
response = requests.get(path)
response.raise_for_status()
except requests.exceptions.Timeout as err:
raise EsphomeflasherError(
"Timeout while retrieving firmware file '{}': {}".format(path, err))
except requests.exceptions.RequestException as err:
raise EsphomeflasherError(
"Error while retrieving firmware file '{}': {}".format(path, err))
binary = io.BytesIO()
binary.write(response.content)
binary.seek(0)
return binary
try:
return open(path, 'rb')
except IOError as err:
raise EsphomeflasherError("Error opening binary '{}': {}".format(path, err))
def format_bootloader_path(path, flash_mode, 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):
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'):
raise EsphomeflasherError(
"No bootloader available for flash frequency {}".format(flash_freq))
bootloader = open_downloadable_binary(
format_bootloader_path(bootloader_path, flash_mode, flash_freq))
partitions = open_downloadable_binary(partitions_path)
otadata = open_downloadable_binary(otadata_path)
addr_filename.append((0x1000, bootloader))
addr_filename.append((0x8000, partitions))
addr_filename.append((0xE000, otadata))
addr_filename.append((0x10000, firmware))
else:
addr_filename.append((0x0, firmware))
return MockEsptoolArgs(flash_size, addr_filename)
def detect_chip(port, force_esp8266=False, force_esp32=False):
if force_esp8266 or force_esp32:
klass = esptool.ESP32ROM if force_esp32 else esptool.ESP8266ROM
chip = klass(port)
else:
try:
chip = esptool.ESPLoader.detect_chip(port)
except esptool.FatalError as err:
raise EsphomeflasherError("ESP Chip Auto-Detection failed: {}".format(err))
try:
chip.connect()
except esptool.FatalError as err:
raise EsphomeflasherError("Error connecting to ESP: {}".format(err))
return chip

14
esphomeflasher/const.py Normal file
View File

@ -0,0 +1,14 @@
import re
__version__ = "1.0.0"
ESP32_DEFAULT_OTA_DATA = 'https://github.com/espressif/arduino-esp32/blob/1.0.0/tools/partitions/boot_app0.bin'
# The latest bootloader image seems to work with older firmwares, but the "stable" bootloader
# doesn't work with firmwares generated with the latest esp-idf
ESP32_DEFAULT_BOOTLOADER_FORMAT = 'https://github.com/espressif/arduino-esp32/raw/' \
'96822d783f3ab6a56a69b227ba4d1a1a36c66268/tools/sdk/' \
'bin/bootloader_$FLASH_MODE$_$FLASH_FREQ$.bin'
ESP32_DEFAULT_PARTITIONS = 'https://github.com/OttoWinter/esphomeflasher/blob/master/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@:%_+.~#?&/=]*)')

232
esphomeflasher/gui.py Normal file
View File

@ -0,0 +1,232 @@
# This GUI is a fork of the brilliant https://github.com/marcelstoer/nodemcu-pyflasher
import sys
import threading
import wx
import wx.adv
from wx.lib.embeddedimage import PyEmbeddedImage
import wx.lib.inspection
import wx.lib.mixins.inspection
from esphomeflasher.helpers import list_serial_ports
# See discussion at http://stackoverflow.com/q/41101897/131929
class RedirectText:
def __init__(self, text_ctrl):
self._out = text_ctrl
def write(self, string):
if string.startswith("\r"):
# carriage return -> remove last line i.e. reset position to start of last line
current_value = self._out.GetValue()
last_newline = current_value.rfind("\n")
new_value = current_value[:last_newline + 1] # preserve \n
new_value += string[1:] # chop off leading \r
wx.CallAfter(self._out.SetValue, new_value)
else:
wx.CallAfter(self._out.AppendText, string)
def flush(self):
pass
class FlashingThread(threading.Thread):
def __init__(self, parent, firmware, port):
threading.Thread.__init__(self)
self.daemon = True
self._parent = parent
self._firmware = firmware
self._port = port
def run(self):
try:
from esphomeflasher.__main__ import run_esphomeflasher
argv = ['esphomeflasher', '--port', self._port, self._firmware]
run_esphomeflasher(argv)
except Exception as e:
print("Unexpected error: {}".format(e))
raise
class MainFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(700, 650),
style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
self._firmware = None
self._port = None
self._build_status_bar()
self._build_menu_bar()
self._init_ui()
sys.stdout = RedirectText(self.console_ctrl)
self.SetMinSize((640, 480))
self.Centre(wx.BOTH)
self.Show(True)
def _init_ui(self):
def on_reload(event):
self.choice.SetItems(self._get_serial_ports())
def on_clicked(event):
self.console_ctrl.SetValue("")
worker = FlashingThread(self, self._firmware, self._port)
worker.start()
def on_select_port(event):
choice = event.GetEventObject()
self._port = choice.GetString(choice.GetSelection())
def on_pick_file(event):
self._firmware = event.GetPath().replace("'", "")
panel = wx.Panel(self)
hbox = wx.BoxSizer(wx.HORIZONTAL)
fgs = wx.FlexGridSizer(7, 2, 10, 10)
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.Bind(wx.EVT_BUTTON, on_reload)
reload_button.SetToolTip("Reload serial device list")
file_picker = wx.FilePickerCtrl(panel, style=wx.FLP_USE_TEXTCTRL)
file_picker.Bind(wx.EVT_FILEPICKER_CHANGED, on_pick_file)
serial_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
serial_boxsizer.Add(self.choice, 1, wx.EXPAND)
serial_boxsizer.AddStretchSpacer(0)
serial_boxsizer.Add(reload_button, 0, wx.ALIGN_RIGHT, 20)
button = wx.Button(panel, -1, "Flash ESP")
button.Bind(wx.EVT_BUTTON, on_clicked)
self.console_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL)
self.console_ctrl.SetFont(
wx.Font(13, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
self.console_ctrl.SetBackgroundColour(wx.BLACK)
self.console_ctrl.SetForegroundColour(wx.RED)
self.console_ctrl.SetDefaultStyle(wx.TextAttr(wx.RED))
port_label = wx.StaticText(panel, label="Serial port")
file_label = wx.StaticText(panel, label="Firmware")
console_label = wx.StaticText(panel, label="Console")
fgs.AddMany([
port_label, (serial_boxsizer, 1, wx.EXPAND),
file_label, (file_picker, 1, wx.EXPAND),
(wx.StaticText(panel, label="")), (button, 1, wx.EXPAND),
(console_label, 1, wx.EXPAND), (self.console_ctrl, 1, wx.EXPAND)])
fgs.AddGrowableRow(3, 1)
fgs.AddGrowableCol(1, 1)
hbox.Add(fgs, proportion=2, flag=wx.ALL | wx.EXPAND, border=15)
panel.SetSizer(hbox)
def _get_serial_ports(self):
ports = []
for port, desc in list_serial_ports():
ports.append(port)
if not self._port and ports:
self._port = ports[0]
if not ports:
ports.append("")
return ports
def _build_status_bar(self):
self.statusBar = self.CreateStatusBar(2, wx.STB_SIZEGRIP)
self.statusBar.SetStatusWidths([-2, -1])
status_text = "Welcome to esphomeflasher (based on PyFlasher)"
self.statusBar.SetStatusText(status_text, 0)
def _build_menu_bar(self):
self.menuBar = wx.MenuBar()
# File menu
file_menu = wx.Menu()
wx.App.SetMacExitMenuItemId(wx.ID_EXIT)
exit_item = file_menu.Append(wx.ID_EXIT, "E&xit\tCtrl-Q", "Exit esphomeflasher")
exit_item.SetBitmap(Exit.GetBitmap())
self.Bind(wx.EVT_MENU, self._on_exit_app, exit_item)
self.menuBar.Append(file_menu, "&File")
# Help menu
help_menu = wx.Menu()
help_menu.Append(wx.ID_ABOUT, '&About esphomeflasher', 'About')
self.menuBar.Append(help_menu, '&Help')
self.SetMenuBar(self.menuBar)
# Menu methods
def _on_exit_app(self, event):
self.Close(True)
def log_message(self, message):
self.console_ctrl.AppendText(message)
class App(wx.App, wx.lib.mixins.inspection.InspectionMixin):
def OnInit(self):
wx.SystemOptions.SetOption("mac.window-plain-transition", 1)
self.SetAppName("esphomeflasher")
frame = MainFrame(None, "esphomeflasher")
frame.Show()
return True
Exit = PyEmbeddedImage(
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0"
"RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAN1SURBVHjaYvz//z8DJQAggFhA"
"xEpGRgaQMX+B+A8DgwYLM1M+r4K8P4+8vMi/P38Y3j18+O7Fs+fbvv7+0w9Uc/kHVG070HKA"
"AGJBNg0omC5jZtynnpfHJeHkzPDmxQuGf6/eMIj+/yP+9MD+xFPrN8Reu3W3Gqi0D2IXAwNA"
"AIEN+A/hpWuEBMwwmj6TgUVEjOHTo0cM9y9dZfj76ycDCysrg4K5FYMUvyAL7+pVnYfOXwJp"
"6wIRAAHECAqDJYyMWpLmpmftN2/mYBEVZ3h38SLD9wcPGP6LioIN/7Z+PQM3UB3vv/8MXB/f"
"MSzdvv3vpecvzfr+/z8HEEBMYFMYGXM0iwrAmu+sXcvw4OxZhqenTjEwAv3P9OsXw+unTxne"
"6Osz3Ll3l+HvyzcMVlLSzMBwqgTpBQggsAG8MuKB4r9eM7zfv5PhHxMzg4qLCwPD0ycMDL9/"
"MzD+/cvw/8kTBgUbGwbB1DSGe1cuMbD8+8EgwMPjCtILEEDgMOCSkhT+t20Nw4v7nxkkNuxm"
"eLNmFYO0sCgDCwcHAwMzM4Pkl68MLzs7GGS6uhmOCwgxcD2+x8DLysID0gsQQGAD/gH99vPL"
"dwZGDjaG/0An/z19goHp/z+Gn9dvgoP4/7dPDD9OnGD4+/0bA5uCAsPPW8DA5eACxxxAAIEN"
"+PDuw/ufirJizE9fMzALCjD8efOO4dHObQx/d29k+PObgeHr268MQta2DCw8fAz/X75k+M/I"
"xPDh1+9vIL0AAQQOg9dPX2x7w8TDwPL2FcOvI8cYxFs7GFjFpRl+PP/K8O3NVwZuIREGpe5u"
"hp83rjF8u3iO4RsnO8OzHz8PgvQCBBA4GrsZGfUUtNXPWiuLsny59YxBch3Qdl4uhq/rNzP8"
"BwYin58PAysbG8MFLy+Gnw9uM5xkYPp38fNX22X//x8DCCAmqD8u3bh6s+Lssy8MrCLcDC/8"
"3Rl+LVvOwG1syMBrYcbwfetmhmsOdgy/795iuMXEwnDh89c2oJ7jIL0AAQR2wQRgXvgKNAfo"
"qRIlJfk2NR42Rj5gEmb5+4/h35+/DJ+/fmd4DUyNN4B+v/DlWwcwcTWzA9PXQqBegACCGwAK"
"ERD+zsBgwszOXirEwe7OzvCP5y/QCx/+/v/26vfv/R///O0GOvkII1AdKxCDDAAIIEZKszNA"
"gAEA1sFjF+2KokIAAAAASUVORK5CYII=")
Reload = PyEmbeddedImage(
"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGOfPtRkwAAACBj"
"SFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAAABmJLR0QA/wD/AP+g"
"vaeTAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAYAAAAGAB4TKWmAAACZUlEQVRI"
"x7XVT4iXRRgH8M/8Mv9tUFgRZiBESRIhbFAo8kJ0EYoOwtJBokvTxUtBQnUokIjAoCi6+HiR"
"CNKoU4GHOvQieygMJKRDEUiahC4UtGkb63TY+cnb6/rb3276vQwzzzPf5/9MKqW4kRj8n8s5"
"53U55y03xEDOeRu+xe5ReqtWQDzAC3gTa3D7KP20nBrknDfhMB7vHH+Dj3AWxyPitxUZyDnv"
"xsElPL6MT/BiRJwbaaBN6eamlH9yzmvxPp5bRibPYDIizg96pIM2pak2pSexGiLiEr7H3DIM"
"3IMP/hNBm9It+BDzmGp6oeWcd+BIvdzFRZzGvUOnOtg6qOTrcRxP4ZVmkbxFxDQm8WVPtDMi"
"tmIDPu7JJocpehnb8F1Tyo/XijsizmMX9teCwq1VNlvrdKFzZeOgTelOvFQPfurV5NE2pc09"
"I/MR8TqewAxu68hmMd1RPzXAw1hXD9b3nL4bJ9qUdi0SzbF699ee6K9ObU6swoMd4Y42pYmm"
"lNm6/91C33/RpvQG9jelzHeMnK4F7uK+ur49bNNzHeEdONSmNFH3f9R1gNdwrKZ0UeSc77fQ"
"CCfxFqSveQA/9HTn8DM2d9I3xBk83ZQy3SNPFqb4JjwTEX9S56BN6SimjI857GtKea+ST+Cx"
"6synETHssCuv6V5sd/UQXQur8VCb0tqmlEuYi4jPF1PsTvJGvFMjGfVPzOD5ppTPxvHkqseu"
"Teku7MQm7MEjHfFXeLYp5ey4uRz5XLcpHbAwhH/jVbzblHJ5TG4s/aPN4BT2NKWcXA7xuBFs"
"wS9NKRdXQr6kgeuBfwEbWdzTvan9igAAADV0RVh0Y29tbWVudABSZWZyZXNoIGZyb20gSWNv"
"biBHYWxsZXJ5IGh0dHA6Ly9pY29uZ2FsLmNvbS/RLzdIAAAAJXRFWHRkYXRlOmNyZWF0ZQAy"
"MDExLTA4LTIxVDE0OjAxOjU2LTA2OjAwdNJAnQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0w"
"OC0yMVQxNDowMTo1Ni0wNjowMAWP+CEAAAAASUVORK5CYII=")
def main():
app = App(False)
app.MainLoop()

37
esphomeflasher/helpers.py Normal file
View File

@ -0,0 +1,37 @@
from __future__ import print_function
import os
import sys
import serial
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(' - ')
if len(split_desc) == 2 and split_desc[0] == split_desc[1]:
desc = split_desc[0]
result.append((port, desc))
result.sort()
return result
def prevent_print(func, *args, **kwargs):
orig_sys_stdout = sys.stdout
sys.stdout = DEVNULL
try:
return func(*args, **kwargs)
except serial.SerialException as err:
from esphomeflasher.common import EsphomeflasherError
raise EsphomeflasherError("Serial port closed: {}".format(err))
finally:
sys.stdout = orig_sys_stdout
pass

BIN
partitions.bin Normal file

Binary file not shown.

51
setup.py Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env python
"""esphomeflasher setup script."""
from setuptools import setup, find_packages
from esphomeflasher import const
PROJECT_NAME = 'esphomeflasher'
PROJECT_PACKAGE_NAME = 'esphomeflasher'
PROJECT_LICENSE = 'MIT'
PROJECT_AUTHOR = 'Otto Winter'
PROJECT_COPYRIGHT = '2018, Otto Winter'
PROJECT_URL = 'https://esphomelib.com/esphomeyaml/guides/esphomeflasher.html'
PROJECT_EMAIL = 'contact@otto-winter.com'
PROJECT_GITHUB_USERNAME = 'OttoWinter'
PROJECT_GITHUB_REPOSITORY = 'esphomeflasher'
PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME)
GITHUB_PATH = '{}/{}'.format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH)
DOWNLOAD_URL = '{}/archive/{}.zip'.format(GITHUB_URL, const.__version__)
REQUIRES = [
'esptool>=2.3.1',
'requests',
]
setup(
name=PROJECT_PACKAGE_NAME,
version=const.__version__,
license=PROJECT_LICENSE,
url=GITHUB_URL,
download_url=DOWNLOAD_URL,
author=PROJECT_AUTHOR,
author_email=PROJECT_EMAIL,
description="ESP8266/ESP32 firmware flasher for esphomelib",
include_package_data=True,
zip_safe=False,
platforms='any',
test_suite='tests',
python_requires='>=3.5',
install_requires=REQUIRES,
keywords=['home', 'automation'],
entry_points={
'console_scripts': [
'esphomeflasher = esphomeflasher.__main__:main'
]
},
packages=find_packages()
)