Compare commits
No commits in common. "master" and "v0.1.0" have entirely different histories.
2
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
@ -84,7 +83,6 @@ celerybeat-schedule
|
||||
# virtualenv
|
||||
venv/
|
||||
ENV/
|
||||
.venv
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
40
About.py
@ -1,27 +1,23 @@
|
||||
|
||||
# coding=utf-8
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
import os
|
||||
import wx
|
||||
import sys, os, wx
|
||||
import wx.html
|
||||
import wx.lib.wxpTag
|
||||
import webbrowser
|
||||
from Main import __version__
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AboutDlg(wx.Dialog):
|
||||
class MyAboutBox(wx.Dialog):
|
||||
text = '''
|
||||
<html>
|
||||
<body bgcolor="#DCDCDC" style="font-family: Arial; background-color: #DCDCDC;">
|
||||
<center>
|
||||
<img src="{0}/images/python-64.png" width="64" height="64" alt="Python">
|
||||
<img src="{0}/images/icon-64.png" width="64" height="64" alt="NodeMCU">
|
||||
<img src="{0}/images/espressif-64.png" width="64" height="64" alt="Espressif, producers of ESP8266 et.al.">
|
||||
<img src="{0}/images/wxpython-64.png" width="64" height="43" alt="wxPython, cross-platform GUI framework">
|
||||
<img src="{0}/images/python-256.png" width="64" height="64" alt="Python">
|
||||
<img src="{0}/images/icon-256.png" width="64" height="64" alt="NodeMCU">
|
||||
<img src="{0}/images/espressif-256.png" width="64" height="64" alt="Espressif, producers of ESP8266 et.al.">
|
||||
<img src="{0}/images/wxpython-256.png" width="64" height="43" alt="wxPython, cross-platform GUI framework">
|
||||
|
||||
<h1>NodeMCU PyFlasher</h1>
|
||||
|
||||
@ -30,14 +26,7 @@ class AboutDlg(wx.Dialog):
|
||||
<p>Fork the <a style="color: #004CE5;" href="https://github.com/marcelstoer/nodemcu-pyflasher">project on
|
||||
GitHub</a> and help improve it for all!</p>
|
||||
|
||||
<p>
|
||||
As with everything I offer for free, this is donation-ware.
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HFN4ZMET5XS2Q">
|
||||
<img src="{0}/images/paypal-256.png" width="256" height="88" alt="Donate with PayPal">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>© {2} Marcel Stör. Licensed under MIT.</p>
|
||||
<p>© 2016-2017 Marcel Stör. Licensed under MIT.</p>
|
||||
|
||||
<p>
|
||||
<wxp module="wx" class="Button">
|
||||
@ -51,29 +40,22 @@ class AboutDlg(wx.Dialog):
|
||||
'''
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Dialog.__init__(self, parent, wx.ID_ANY, "About NodeMCU PyFlasher")
|
||||
html = HtmlWindow(self, wx.ID_ANY, size=(420, -1))
|
||||
wx.Dialog.__init__(self, parent, -1, "About NodeMCU PyFlasher")
|
||||
html = wx.html.HtmlWindow(self, -1, size=(420, -1))
|
||||
if "gtk2" in wx.PlatformInfo or "gtk3" in wx.PlatformInfo:
|
||||
html.SetStandardFonts()
|
||||
txt = self.text.format(self._get_bundle_dir(), __version__, datetime.datetime.now().year)
|
||||
txt = self.text.format(self.__get_bundle_dir(), __version__)
|
||||
html.SetPage(txt)
|
||||
ir = html.GetInternalRepresentation()
|
||||
html.SetSize((ir.GetWidth() + 25, ir.GetHeight() + 25))
|
||||
self.SetClientSize(html.GetSize())
|
||||
self.CentreOnParent(wx.BOTH)
|
||||
|
||||
@staticmethod
|
||||
def _get_bundle_dir():
|
||||
def __get_bundle_dir(self):
|
||||
# set by PyInstaller, see http://pyinstaller.readthedocs.io/en/v3.2/runtime-information.html
|
||||
if getattr(sys, 'frozen', False):
|
||||
# noinspection PyUnresolvedReferences,PyProtectedMember
|
||||
return sys._MEIPASS
|
||||
else:
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class HtmlWindow(wx.html.HtmlWindow):
|
||||
def OnLinkClicked(self, link):
|
||||
webbrowser.open(link.GetHref())
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
@ -1,30 +0,0 @@
|
||||
|
||||
# coding=utf-8
|
||||
|
||||
import wx
|
||||
import wx.html
|
||||
import webbrowser
|
||||
|
||||
|
||||
class HtmlPopupTransientWindow(wx.PopupTransientWindow):
|
||||
def __init__(self, parent, style, html_body_content, bgcolor, size):
|
||||
wx.PopupTransientWindow.__init__(self, parent, style)
|
||||
panel = wx.Panel(self)
|
||||
panel.SetBackgroundColour(bgcolor)
|
||||
|
||||
html_window = self.HtmlWindow(panel, wx.ID_ANY, size=size)
|
||||
html_window.SetPage('<body bgcolor="' + bgcolor + '">' + html_body_content + '</body>')
|
||||
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
sizer.Add(html_window, 0, wx.ALL, 5)
|
||||
panel.SetSizer(sizer)
|
||||
|
||||
sizer.Fit(panel)
|
||||
sizer.Fit(self)
|
||||
self.Layout()
|
||||
|
||||
class HtmlWindow(wx.html.HtmlWindow):
|
||||
def OnLinkClicked(self, link):
|
||||
# get a hold of the PopupTransientWindow to close it
|
||||
self.GetParent().GetParent().Dismiss()
|
||||
webbrowser.open(link.GetHref())
|
458
Main.py
@ -1,41 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import wx
|
||||
import wx.adv
|
||||
import wx.lib.inspection
|
||||
import wx.lib.mixins.inspection
|
||||
|
||||
import sys
|
||||
import os
|
||||
import esptool
|
||||
import threading
|
||||
import json
|
||||
import images as images
|
||||
from serial import SerialException
|
||||
from serial.tools import list_ports
|
||||
import locale
|
||||
from esptool import ESPROM
|
||||
from argparse import Namespace
|
||||
|
||||
# see https://discuss.wxpython.org/t/wxpython4-1-1-python3-8-locale-wxassertionerror/35168
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
|
||||
__version__ = "5.1.0"
|
||||
__flash_help__ = '''
|
||||
<p>This setting is highly dependent on your device!<p>
|
||||
<p>
|
||||
Details at <a style="color: #004CE5;"
|
||||
href="https://www.esp32.com/viewtopic.php?p=5523&sid=08ef44e13610ecf2a2a33bb173b0fd5c#p5523">http://bit.ly/2v5Rd32</a>
|
||||
and in the <a style="color: #004CE5;" href="https://github.com/espressif/esptool/#flash-modes">esptool
|
||||
documentation</a>
|
||||
<ul>
|
||||
<li>Most ESP32 and ESP8266 ESP-12 use DIO.</li>
|
||||
<li>Most ESP8266 ESP-01/07 use QIO.</li>
|
||||
<li>ESP8285 requires DOUT.</li>
|
||||
</ul>
|
||||
</p>
|
||||
'''
|
||||
__auto_select__ = "Auto-select"
|
||||
__auto_select_explanation__ = "(first port with Espressif device)"
|
||||
__supported_baud_rates__ = [9600, 57600, 74880, 115200, 230400, 460800, 921600]
|
||||
__version__ = "0.1.0"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -44,28 +20,29 @@ __supported_baud_rates__ = [9600, 57600, 74880, 115200, 230400, 460800, 921600]
|
||||
class RedirectText:
|
||||
def __init__(self, text_ctrl):
|
||||
self.__out = text_ctrl
|
||||
self.__pending_backspaces = 0
|
||||
|
||||
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
|
||||
new_string = ""
|
||||
number_of_backspaces = 0
|
||||
for c in string:
|
||||
if c == "\b":
|
||||
number_of_backspaces += 1
|
||||
else:
|
||||
new_string += c
|
||||
|
||||
if self.__pending_backspaces > 0:
|
||||
# current value minus pending backspaces plus new string
|
||||
new_value = self.__out.GetValue()[:-1 * self.__pending_backspaces] + new_string
|
||||
wx.CallAfter(self.__out.SetValue, new_value)
|
||||
else:
|
||||
wx.CallAfter(self.__out.AppendText, string)
|
||||
wx.CallAfter(self.__out.AppendText, new_string)
|
||||
|
||||
self.__pending_backspaces = number_of_backspaces
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def flush(self):
|
||||
# noinspection PyStatementEffect
|
||||
None
|
||||
|
||||
# esptool >=3 handles output differently of the output stream is not a TTY
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def isatty(self):
|
||||
return True
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@ -74,40 +51,25 @@ class FlashingThread(threading.Thread):
|
||||
def __init__(self, parent, config):
|
||||
threading.Thread.__init__(self)
|
||||
self.daemon = True
|
||||
self._parent = parent
|
||||
self._config = config
|
||||
self.__parent = parent
|
||||
self.__config = config
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
command = []
|
||||
|
||||
if not self._config.port.startswith(__auto_select__):
|
||||
command.append("--port")
|
||||
command.append(self._config.port)
|
||||
|
||||
command.extend(["--baud", str(self._config.baud),
|
||||
"--after", "no_reset",
|
||||
"write_flash",
|
||||
# https://github.com/espressif/esptool/issues/599
|
||||
"--flash_size", "detect",
|
||||
"--flash_mode", self._config.mode,
|
||||
"0x00000", self._config.firmware_path])
|
||||
|
||||
if self._config.erase_before_flash:
|
||||
command.append("--erase-all")
|
||||
|
||||
print("Command: esptool.py %s\n" % " ".join(command))
|
||||
|
||||
esptool.main(command)
|
||||
|
||||
# The last line printed by esptool is "Staying in bootloader." -> some indication that the process is
|
||||
# done is needed
|
||||
print("\nFirmware successfully flashed. Unplug/replug or reset device \nto switch back to normal boot "
|
||||
"mode.")
|
||||
except SerialException as e:
|
||||
self._parent.report_error(e.strerror)
|
||||
raise e
|
||||
|
||||
esp = ESPROM(port=self.__config.port)
|
||||
args = Namespace()
|
||||
args.flash_size = "detect"
|
||||
args.flash_mode = self.__config.mode
|
||||
args.flash_freq = "40m"
|
||||
args.no_progress = False
|
||||
args.verify = True
|
||||
args.baud = self.__config.baud
|
||||
args.addr_filename = [[int("0x00000", 0), open(self.__config.file, 'rb')]]
|
||||
# needs connect() before each operation, see https://github.com/espressif/esptool/issues/157
|
||||
if self.__config.erase_before_flash:
|
||||
esp.connect()
|
||||
esptool.erase_flash(esp, args)
|
||||
esp.connect()
|
||||
esptool.write_flash(esp, args)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -118,34 +80,12 @@ class FlashConfig:
|
||||
def __init__(self):
|
||||
self.baud = 115200
|
||||
self.erase_before_flash = False
|
||||
self.mode = "dio"
|
||||
self.firmware_path = None
|
||||
self.mode = "qio"
|
||||
self.file = None
|
||||
self.port = None
|
||||
|
||||
@classmethod
|
||||
def load(cls, file_path):
|
||||
conf = cls()
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
conf.port = data['port']
|
||||
conf.baud = data['baud']
|
||||
conf.mode = data['mode']
|
||||
conf.erase_before_flash = data['erase']
|
||||
return conf
|
||||
|
||||
def safe(self, file_path):
|
||||
data = {
|
||||
'port': self.port,
|
||||
'baud': self.baud,
|
||||
'mode': self.mode,
|
||||
'erase': self.erase_before_flash,
|
||||
}
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
def is_complete(self):
|
||||
return self.firmware_path is not None and self.port is not None
|
||||
return self.file is not None and self.port is not None
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -154,178 +94,98 @@ class FlashConfig:
|
||||
class NodeMcuFlasher(wx.Frame):
|
||||
|
||||
def __init__(self, parent, title):
|
||||
wx.Frame.__init__(self, parent, -1, title, size=(725, 650),
|
||||
wx.Frame.__init__(self, parent, -1, title, size=(700, 650),
|
||||
style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
|
||||
self._config = FlashConfig.load(self._get_config_file_path())
|
||||
self.__config = FlashConfig()
|
||||
|
||||
self._build_status_bar()
|
||||
self._set_icons()
|
||||
self._build_menu_bar()
|
||||
self._init_ui()
|
||||
self.__build_status_bar()
|
||||
self.__set_icons()
|
||||
self.__build_menu_bar()
|
||||
self.__init_ui()
|
||||
|
||||
sys.stdout = RedirectText(self.console_ctrl)
|
||||
|
||||
self.SetMinSize((640, 480))
|
||||
self.Centre(wx.BOTH)
|
||||
self.Show(True)
|
||||
print("Connect your device")
|
||||
print("\nIf you chose the serial port auto-select feature you might need to ")
|
||||
print("turn off Bluetooth")
|
||||
|
||||
def _init_ui(self):
|
||||
def on_reload(event):
|
||||
self.choice.SetItems(self._get_serial_ports())
|
||||
|
||||
def on_baud_changed(event):
|
||||
radio_button = event.GetEventObject()
|
||||
|
||||
if radio_button.GetValue():
|
||||
self._config.baud = radio_button.rate
|
||||
|
||||
def on_mode_changed(event):
|
||||
radio_button = event.GetEventObject()
|
||||
|
||||
if radio_button.GetValue():
|
||||
self._config.mode = radio_button.mode
|
||||
|
||||
def on_erase_changed(event):
|
||||
radio_button = event.GetEventObject()
|
||||
|
||||
if radio_button.GetValue():
|
||||
self._config.erase_before_flash = radio_button.erase
|
||||
|
||||
def on_clicked(event):
|
||||
self.console_ctrl.SetValue("")
|
||||
worker = FlashingThread(self, self._config)
|
||||
worker.start()
|
||||
|
||||
def on_select_port(event):
|
||||
choice = event.GetEventObject()
|
||||
self._config.port = choice.GetString(choice.GetSelection())
|
||||
|
||||
def on_pick_file(event):
|
||||
self._config.firmware_path = event.GetPath().replace("'", "")
|
||||
|
||||
def __init_ui(self):
|
||||
panel = wx.Panel(self)
|
||||
|
||||
# Fix popup that never goes away.
|
||||
def onHover(event):
|
||||
global hovered
|
||||
if(len(hovered) != 0 ):
|
||||
hovered[0].Dismiss()
|
||||
hovered = []
|
||||
|
||||
panel.Bind(wx.EVT_MOTION,onHover)
|
||||
|
||||
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)
|
||||
self._select_configured_port()
|
||||
|
||||
reload_button = wx.Button(panel, label="Reload")
|
||||
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.Add(reload_button, flag=wx.LEFT, border=10)
|
||||
|
||||
baud_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
def add_baud_radio_button(sizer, index, baud_rate):
|
||||
style = wx.RB_GROUP if index == 0 else 0
|
||||
radio_button = wx.RadioButton(panel, name="baud-%d" % baud_rate, label="%d" % baud_rate, style=style)
|
||||
radio_button.rate = baud_rate
|
||||
# sets default value
|
||||
radio_button.SetValue(baud_rate == self._config.baud)
|
||||
radio_button.Bind(wx.EVT_RADIOBUTTON, on_baud_changed)
|
||||
sizer.Add(radio_button)
|
||||
sizer.AddSpacer(10)
|
||||
|
||||
for idx, rate in enumerate(__supported_baud_rates__):
|
||||
add_baud_radio_button(baud_boxsizer, idx, rate)
|
||||
|
||||
flashmode_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
def add_flash_mode_radio_button(sizer, index, mode, label):
|
||||
style = wx.RB_GROUP if index == 0 else 0
|
||||
radio_button = wx.RadioButton(panel, name="mode-%s" % mode, label="%s" % label, style=style)
|
||||
radio_button.Bind(wx.EVT_RADIOBUTTON, on_mode_changed)
|
||||
radio_button.mode = mode
|
||||
radio_button.SetValue(mode == self._config.mode)
|
||||
sizer.Add(radio_button)
|
||||
sizer.AddSpacer(10)
|
||||
|
||||
add_flash_mode_radio_button(flashmode_boxsizer, 0, "qio", "Quad I/O (QIO)")
|
||||
add_flash_mode_radio_button(flashmode_boxsizer, 1, "dio", "Dual I/O (DIO)")
|
||||
add_flash_mode_radio_button(flashmode_boxsizer, 2, "dout", "Dual Output (DOUT)")
|
||||
|
||||
erase_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
def add_erase_radio_button(sizer, index, erase_before_flash, label, value):
|
||||
style = wx.RB_GROUP if index == 0 else 0
|
||||
radio_button = wx.RadioButton(panel, name="erase-%s" % erase_before_flash, label="%s" % label, style=style)
|
||||
radio_button.Bind(wx.EVT_RADIOBUTTON, on_erase_changed)
|
||||
radio_button.erase = erase_before_flash
|
||||
radio_button.SetValue(value)
|
||||
sizer.Add(radio_button)
|
||||
sizer.AddSpacer(10)
|
||||
|
||||
erase = self._config.erase_before_flash
|
||||
add_erase_radio_button(erase_boxsizer, 0, False, "no", erase is False)
|
||||
add_erase_radio_button(erase_boxsizer, 1, True, "yes, wipes all data", erase is True)
|
||||
|
||||
button = wx.Button(panel, -1, "Flash NodeMCU")
|
||||
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((0, 13), wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL,
|
||||
wx.FONTWEIGHT_NORMAL))
|
||||
self.console_ctrl.SetBackgroundColour(wx.WHITE)
|
||||
self.console_ctrl.SetForegroundColour(wx.BLUE)
|
||||
self.console_ctrl.SetDefaultStyle(wx.TextAttr(wx.BLUE))
|
||||
|
||||
port_label = wx.StaticText(panel, label="Serial port")
|
||||
file_label = wx.StaticText(panel, label="NodeMCU firmware")
|
||||
baud_label = wx.StaticText(panel, label="Baud rate")
|
||||
flashmode_label = wx.StaticText(panel, label="Flash mode")
|
||||
|
||||
def on_info_hover(event):
|
||||
global hovered
|
||||
if(len(hovered) == 0):
|
||||
from HtmlPopupTransientWindow import HtmlPopupTransientWindow
|
||||
win = HtmlPopupTransientWindow(self, wx.SIMPLE_BORDER, __flash_help__, "#FFB6C1", (410, 140))
|
||||
|
||||
image = event.GetEventObject()
|
||||
image_position = image.ClientToScreen((0, 0))
|
||||
image_size = image.GetSize()
|
||||
win.Position(image_position, (0, image_size[1]))
|
||||
|
||||
win.Popup()
|
||||
hovered = [win]
|
||||
|
||||
|
||||
icon = wx.StaticBitmap(panel, wx.ID_ANY, images.Info.GetBitmap())
|
||||
icon.Bind(wx.EVT_MOTION, on_info_hover)
|
||||
|
||||
flashmode_label_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
flashmode_label_boxsizer.Add(flashmode_label, 1, wx.EXPAND)
|
||||
flashmode_label_boxsizer.AddStretchSpacer(0)
|
||||
flashmode_label_boxsizer.Add(icon)
|
||||
|
||||
erase_label = wx.StaticText(panel, label="Erase flash")
|
||||
console_label = wx.StaticText(panel, label="Console")
|
||||
|
||||
self.choice = wx.Choice(panel, choices=self.__get_serial_ports())
|
||||
self.choice.Bind(wx.EVT_CHOICE, self.__on_select_port)
|
||||
bmp = images.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, self.__on_reload)
|
||||
|
||||
file_picker = wx.FilePickerCtrl(panel, style=wx.FLP_USE_TEXTCTRL)
|
||||
file_picker.Bind(wx.EVT_FILEPICKER_CHANGED, self.__on_pick_file)
|
||||
|
||||
button = wx.Button(panel, -1, "Flash NodeMCU")
|
||||
button.Bind(wx.EVT_BUTTON, self.__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))
|
||||
|
||||
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)
|
||||
|
||||
baud_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
baud_boxsizer.Add(wx.RadioButton(panel, name="baud-9600", label="9600", style=wx.RB_GROUP))
|
||||
baud_boxsizer.AddSpacer(10)
|
||||
baud_boxsizer.Add(wx.RadioButton(panel, name="baud-57600", label="57600"))
|
||||
baud_boxsizer.AddSpacer(10)
|
||||
baud_boxsizer.Add(wx.RadioButton(panel, name="baud-74880", label="74880"))
|
||||
baud_boxsizer.AddSpacer(10)
|
||||
radio_button_115200 = wx.RadioButton(panel, name="baud-115200", label="115200")
|
||||
radio_button_115200.SetValue(True) # checks/selects the control
|
||||
baud_boxsizer.Add(radio_button_115200)
|
||||
baud_boxsizer.AddSpacer(10)
|
||||
baud_boxsizer.Add(wx.RadioButton(panel, name="baud-230400", label="230400"))
|
||||
baud_boxsizer.AddSpacer(10)
|
||||
baud_boxsizer.Add(wx.RadioButton(panel, name="baud-460800", label="460800"))
|
||||
baud_boxsizer.AddSpacer(10)
|
||||
baud_boxsizer.Add(wx.RadioButton(panel, name="baud-921600", label="921600"))
|
||||
|
||||
flashmode_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
qio_button = wx.RadioButton(panel, name="mode-qio", label="Quad Flash I/O (qio)", style=wx.RB_GROUP)
|
||||
qio_button.SetValue(True)
|
||||
flashmode_boxsizer.Add(qio_button)
|
||||
flashmode_boxsizer.AddSpacer(10)
|
||||
flashmode_boxsizer.Add(wx.RadioButton(panel, name="mode-dio", label="Dual Flash I/O (dio), usually for >=4MB flash chips"))
|
||||
|
||||
erase_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
qio_button = wx.RadioButton(panel, name="erase-no", label="no", style=wx.RB_GROUP)
|
||||
qio_button.SetValue(True)
|
||||
erase_boxsizer.Add(qio_button)
|
||||
erase_boxsizer.AddSpacer(10)
|
||||
erase_boxsizer.Add(wx.RadioButton(panel, name="erase-yes", label="yes, wipes all data"))
|
||||
|
||||
# handler for all radio button groups as these "light weight" groups can't accept dedicated handlers
|
||||
self.Bind(wx.EVT_RADIOBUTTON, self.__on_radio_group)
|
||||
|
||||
fgs.AddMany([
|
||||
port_label, (serial_boxsizer, 1, wx.EXPAND),
|
||||
file_label, (file_picker, 1, wx.EXPAND),
|
||||
baud_label, baud_boxsizer,
|
||||
flashmode_label_boxsizer, flashmode_boxsizer,
|
||||
flashmode_label, flashmode_boxsizer,
|
||||
erase_label, erase_boxsizer,
|
||||
(wx.StaticText(panel, label="")), (button, 1, wx.EXPAND),
|
||||
(console_label, 1, wx.EXPAND), (self.console_ctrl, 1, wx.EXPAND)])
|
||||
@ -334,84 +194,88 @@ class NodeMcuFlasher(wx.Frame):
|
||||
hbox.Add(fgs, proportion=2, flag=wx.ALL | wx.EXPAND, border=15)
|
||||
panel.SetSizer(hbox)
|
||||
|
||||
def _select_configured_port(self):
|
||||
count = 0
|
||||
for item in self.choice.GetItems():
|
||||
if item == self._config.port:
|
||||
self.choice.Select(count)
|
||||
break
|
||||
count += 1
|
||||
|
||||
@staticmethod
|
||||
def _get_serial_ports():
|
||||
ports = [__auto_select__ + " " + __auto_select_explanation__]
|
||||
def __get_serial_ports(self):
|
||||
ports = [""]
|
||||
for port, desc, hwid in sorted(list_ports.comports()):
|
||||
ports.append(port)
|
||||
return ports
|
||||
|
||||
def _set_icons(self):
|
||||
def __on_reload(self, event):
|
||||
self.choice.SetItems(self.__get_serial_ports())
|
||||
|
||||
def __on_radio_group(self, event):
|
||||
rb = event.GetEventObject()
|
||||
name = rb.GetName()
|
||||
if name.startswith("baud-"):
|
||||
self.__config.baud = int(name[5:])
|
||||
elif name.startswith("mode-"):
|
||||
self.__config.mode = name[5:]
|
||||
elif name.startswith("erase-"):
|
||||
self.__config.erase_before_flash = name == "erase-yes"
|
||||
|
||||
def __on_clicked(self, event):
|
||||
self.console_ctrl.SetValue("")
|
||||
worker = FlashingThread(self, self.__config)
|
||||
worker.start()
|
||||
|
||||
def __on_select_port(self, event):
|
||||
choice = event.GetEventObject()
|
||||
self.__config.port = choice.GetString(choice.GetSelection())
|
||||
|
||||
def __on_pick_file(self, event):
|
||||
self.__config.file = event.GetPath().replace("'", "")
|
||||
|
||||
def __set_icons(self):
|
||||
self.SetIcon(images.Icon.GetIcon())
|
||||
|
||||
def _build_status_bar(self):
|
||||
self.statusBar = self.CreateStatusBar(2, wx.STB_SIZEGRIP)
|
||||
def __build_status_bar(self):
|
||||
self.statusBar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
|
||||
self.statusBar.SetStatusWidths([-2, -1])
|
||||
status_text = "Welcome to NodeMCU PyFlasher %s" % __version__
|
||||
self.statusBar.SetStatusText(status_text, 0)
|
||||
|
||||
def _build_menu_bar(self):
|
||||
self.menuBar = wx.MenuBar()
|
||||
def __build_menu_bar(self):
|
||||
|
||||
# File menu
|
||||
file_menu = wx.Menu()
|
||||
self.menuBar = wx.MenuBar()
|
||||
menu = wx.Menu()
|
||||
wx.App.SetMacExitMenuItemId(wx.ID_EXIT)
|
||||
exit_item = file_menu.Append(wx.ID_EXIT, "E&xit\tCtrl-Q", "Exit NodeMCU PyFlasher")
|
||||
exit_item = menu.Append(wx.ID_EXIT, "E&xit\tCtrl-Q", "Exit NodeMCU PyFlasher")
|
||||
exit_item.SetBitmap(images.Exit.GetBitmap())
|
||||
self.Bind(wx.EVT_MENU, self._on_exit_app, exit_item)
|
||||
self.menuBar.Append(file_menu, "&File")
|
||||
self.Bind(wx.EVT_MENU, self.__on_exit_app, exit_item)
|
||||
self.menuBar.Append(menu, "&File")
|
||||
|
||||
# Help menu
|
||||
help_menu = wx.Menu()
|
||||
help_item = help_menu.Append(wx.ID_ABOUT, '&About NodeMCU PyFlasher', 'About')
|
||||
self.Bind(wx.EVT_MENU, self._on_help_about, help_item)
|
||||
self.menuBar.Append(help_menu, '&Help')
|
||||
menu = wx.Menu()
|
||||
help_item = menu.Append(wx.ID_ABOUT, '&About NodeMCU PyFlasher', 'About')
|
||||
self.Bind(wx.EVT_MENU, self.__on_help_about, help_item)
|
||||
self.menuBar.Append(menu, '&Help')
|
||||
|
||||
self.SetMenuBar(self.menuBar)
|
||||
|
||||
@staticmethod
|
||||
def _get_config_file_path():
|
||||
return wx.StandardPaths.Get().GetUserConfigDir() + "/nodemcu-pyflasher.json"
|
||||
|
||||
# Menu methods
|
||||
def _on_exit_app(self, event):
|
||||
self._config.safe(self._get_config_file_path())
|
||||
def __on_exit_app(self, event):
|
||||
self.Close(True)
|
||||
|
||||
def _on_help_about(self, event):
|
||||
from About import AboutDlg
|
||||
about = AboutDlg(self)
|
||||
def __on_help_about(self, event):
|
||||
from About import MyAboutBox
|
||||
about = MyAboutBox(self)
|
||||
about.ShowModal()
|
||||
about.Destroy()
|
||||
|
||||
def report_error(self, message):
|
||||
self.console_ctrl.SetValue(message)
|
||||
|
||||
def log_message(self, message):
|
||||
self.console_ctrl.AppendText(message)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
class MySplashScreen(wx.adv.SplashScreen):
|
||||
class MySplashScreen(wx.SplashScreen):
|
||||
def __init__(self):
|
||||
global hovered
|
||||
hovered = []
|
||||
wx.adv.SplashScreen.__init__(self, images.Splash.GetBitmap(),
|
||||
wx.adv.SPLASH_CENTRE_ON_SCREEN | wx.adv.SPLASH_TIMEOUT, 2500, None, -1)
|
||||
self.Bind(wx.EVT_CLOSE, self._on_close)
|
||||
self.__fc = wx.CallLater(2000, self._show_main)
|
||||
wx.SplashScreen.__init__(self, images.Splash.GetBitmap(),
|
||||
wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT,
|
||||
2500, None, -1)
|
||||
self.Bind(wx.EVT_CLOSE, self.__on_close)
|
||||
self.__fc = wx.FutureCall(2000, self.__show_main)
|
||||
|
||||
def _on_close(self, evt):
|
||||
def __on_close(self, evt):
|
||||
# Make sure the default handler runs too so this window gets
|
||||
# destroyed
|
||||
evt.Skip()
|
||||
@ -421,9 +285,9 @@ class MySplashScreen(wx.adv.SplashScreen):
|
||||
# main frame now
|
||||
if self.__fc.IsRunning():
|
||||
self.__fc.Stop()
|
||||
self._show_main()
|
||||
self.__show_main()
|
||||
|
||||
def _show_main(self):
|
||||
def __show_main(self):
|
||||
frame = NodeMcuFlasher(None, "NodeMCU PyFlasher")
|
||||
frame.Show()
|
||||
if self.__fc.IsRunning():
|
||||
@ -435,9 +299,7 @@ class MySplashScreen(wx.adv.SplashScreen):
|
||||
# ----------------------------------------------------------------------------
|
||||
class App(wx.App, wx.lib.mixins.inspection.InspectionMixin):
|
||||
def OnInit(self):
|
||||
# see https://discuss.wxpython.org/t/wxpython4-1-1-python3-8-locale-wxassertionerror/35168
|
||||
self.ResetLocale()
|
||||
wx.SystemOptions.SetOption("mac.window-plain-transition", 1)
|
||||
wx.SystemOptions.SetOptionInt("mac.window-plain-transition", 1)
|
||||
self.SetAppName("NodeMCU PyFlasher")
|
||||
|
||||
# Create and show the splash screen. It will then create and
|
||||
@ -460,8 +322,6 @@ def main():
|
||||
app.MainLoop()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
__name__ = 'Main'
|
||||
main()
|
||||
|
||||
|
71
README.md
@ -1,71 +1,6 @@
|
||||
# NodeMCU PyFlasher
|
||||
[](https://github.com/marcelstoer/nodemcu-pyflasher/blob/master/LICENSE)
|
||||
[](https://github.com/marcelstoer/nodemcu-pyflasher/releases)
|
||||
[](https://github.com/marcelstoer/nodemcu-pyflasher/releases)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HFN4ZMET5XS2Q)
|
||||
[](https://github.com/marcelstoer/nodemcu-pyflasher/blob/master/LICENSE)
|
||||
|
||||
Self-contained [NodeMCU](https://github.com/nodemcu/nodemcu-firmware) flasher with GUI based on [esptool.py](https://github.com/espressif/esptool) and [wxPython](https://www.wxpython.org/).
|
||||
Self-contained NodeMCU flasher with GUI based on [esptool.py](https://github.com/espressif/esptool) and [wxPython](https://www.wxpython.org/).
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
NodeMCU PyFlasher doesn't have to be installed, just double-click it and it'll start. Check the [releases section](https://github.com/marcelstoer/nodemcu-pyflasher/releases) for downloads for your platform. For every release there's at least a .exe file for Windows. Starting from 3.0 there's also a .dmg for macOS.
|
||||
|
||||
## Status
|
||||
Scan the [list of open issues](https://github.com/marcelstoer/nodemcu-pyflasher/issues) for bugs and pending features.
|
||||
|
||||
**Note**
|
||||
|
||||
This is my first Python project. If you have constructive feedback as for how to improve the code please do reach out to me.
|
||||
|
||||
## Getting help
|
||||
In the unlikely event that you're stuck with this simple tool the best way to get help is to turn to the ["Tools and IDE" subforum on esp8266.com](http://www.esp8266.com/viewforum.php?f=22).
|
||||
|
||||
## Donationware
|
||||
All open-source development by the author is donationware. Show your love and support for open-source development by donating to the good cause through PayPal.
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HFN4ZMET5XS2Q)
|
||||
|
||||
## Build it yourself
|
||||
If you want to build this application yourself you need to:
|
||||
|
||||
- Install [Python 3.x](https://www.python.org/downloads/) and [Pip](https://pip.pypa.io/en/stable/installing/) (it comes with Python if installed from `python.org`).
|
||||
- Create a virtual environment with `python -m venv venv`
|
||||
- Activate the virtual environment with `. venv/bin/activate` (`. venv/Scripts/activate` if you are on Windows with [Cygwin](https://www.cygwin.com/) or [Mingw](http://mingw.org/))
|
||||
- Run `pip install -r requirements.txt`
|
||||
|
||||
**A note on Linux:** As described on the [downloads section of `wxPython`](https://www.wxpython.org/pages/downloads/), wheels for Linux are complicated and may require you to run something like this to install `wxPython` correctly:
|
||||
|
||||
```bash
|
||||
# Assuming you are running it on Ubuntu 18.04 LTS with GTK3
|
||||
pip install -U \
|
||||
-f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 \
|
||||
wxPython
|
||||
```
|
||||
|
||||
## Why this project exists
|
||||
|
||||
### Motivation
|
||||
This addresses an issue the NodeMCU community touched on several times in the past, most recently at
|
||||
[#1500 (comment)](https://github.com/nodemcu/nodemcu-firmware/pull/1500#issuecomment-247884981).
|
||||
|
||||
I stated that based on my experience doing NodeMCU user support it should be a lot simpler to flash NodeMCU for Windows users.
|
||||
|
||||
- A number of flashing tools are available but only two are actively maintained: esptool-ck and esptool.py. Only one is endorsed by Espressif: [esptool.py](https://github.com/espressif/esptool) (they hired the developer(s)).
|
||||
- 70% of the users of my [nodemcu-build.com](https://nodemcu-build.com) service are on Windows.
|
||||
- BUT Windows doesn't come with Python installed - which is required for esptool.py.
|
||||
- BUT Windows users in general are more reluctant to use the CLI than Linux/Mac users - which is required for esptool.py.
|
||||
|
||||
To conclude: this is not a comfortable situation for NodeMCU's largest user group.
|
||||
|
||||
### The plan
|
||||
For quite a while I planned to write a self-contained GUI tool which would use esptool.py in the background. It should primarily target Windows users but since I'm on Mac it should be cross-platform. Even though I had never used Python before I felt confident to pull this off.
|
||||
|
||||
### Implementation
|
||||
- Uses the cross-platform wxPython GUI framework. I also tried PyForms/PyQt4 but settled for wxPython.
|
||||
- Requires absolutely minimal user input.
|
||||
- The esptool.py "console" output is redirected to text control on the GUI.
|
||||
- Uses [PyInstaller](https://github.com/pyinstaller/pyinstaller) to create self-contained executable for Windows and Mac. The packaged app can run standalone i.e. without installing itself, a Python interpreter or any modules.
|
||||
|
||||
## License
|
||||
[MIT](http://opensource.org/licenses/MIT) © Marcel Stör
|
||||

|
@ -1,60 +1,31 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# -*- mode: python -*-
|
||||
|
||||
import os
|
||||
block_cipher = None
|
||||
|
||||
# We need to add the flasher stub JSON files explicitly: https://github.com/espressif/esptool/issues/1059
|
||||
venv_python_folder_name = next(d for d in os.listdir('./.venv/lib') if d.startswith('python') and os.path.isdir(os.path.join('./.venv/lib', d)))
|
||||
local_stub_flasher_path = "./.venv/lib/{}/site-packages/esptool/targets/stub_flasher".format(venv_python_folder_name)
|
||||
|
||||
a = Analysis(
|
||||
['nodemcu-pyflasher.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[
|
||||
("images", "images"),
|
||||
("{}/1".format(local_stub_flasher_path), "./esptool/targets/stub_flasher/1"),
|
||||
("{}/2".format(local_stub_flasher_path), "./esptool/targets/stub_flasher/2")
|
||||
],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='NodeMCU PyFlasher',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='NodeMCU PyFlasher',
|
||||
icon='images/icon-256.icns'
|
||||
)
|
||||
app = BUNDLE(
|
||||
coll,
|
||||
name='NodeMCU PyFlasher.app',
|
||||
version='5.1.0',
|
||||
icon='images/icon-256.icns',
|
||||
bundle_identifier='com.frightanic.nodemcu-pyflasher')
|
||||
a = Analysis(['nodemcu-pyflasher.py'],
|
||||
binaries=None,
|
||||
datas=[("images", "images")],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='NodeMCU-PyFlasher',
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False , icon='images/icon-256.icns')
|
||||
app = BUNDLE(exe,
|
||||
name='NodeMCU-PyFlasher.app',
|
||||
icon='./images/icon-256.icns',
|
||||
bundle_identifier='com.frightanic.nodemcu-pyflasher')
|
||||
|
@ -1,46 +1,26 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# -*- mode: python -*-
|
||||
|
||||
# We need to add the flasher stub JSON files explicitly: https://github.com/espressif/esptool/issues/1059
|
||||
local_stub_flasher_path = "./.venv/Lib/site-packages/esptool/targets/stub_flasher"
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['nodemcu-pyflasher.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[
|
||||
("images", "images"),
|
||||
("{}/1".format(local_stub_flasher_path), "./esptool/targets/stub_flasher/1"),
|
||||
("{}/2".format(local_stub_flasher_path), "./esptool/targets/stub_flasher/2")
|
||||
],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='NodeMCU-PyFlasher',
|
||||
version='windows-version-info.txt',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
icon='images\\icon-256.ico',
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
a = Analysis(['nodemcu-pyflasher.py'],
|
||||
binaries=[],
|
||||
datas=[("images", "images")],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='NodeMCU-PyFlasher',
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False , icon='images\\icon-256.ico')
|
||||
|
10
build.sh
@ -1,13 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# rm -fr build dist
|
||||
VERSION=5.1.0
|
||||
NAME="NodeMCU PyFlasher"
|
||||
DIST_NAME="NodeMCU-PyFlasher"
|
||||
|
||||
#rm -fr build dist
|
||||
pyinstaller --log-level=DEBUG \
|
||||
--noconfirm \
|
||||
build-on-mac.spec
|
||||
|
||||
# https://github.com/sindresorhus/create-dmg
|
||||
create-dmg "dist/$NAME.app"
|
||||
mv "$NAME $VERSION.dmg" "dist/$DIST_NAME.dmg"
|
||||
|
@ -10,7 +10,6 @@ command_lines = [
|
||||
"-F -n Exit images/exit.png images.py",
|
||||
"-a -F -n Reload images/reload.png images.py",
|
||||
"-a -F -n Splash images/splash.png images.py",
|
||||
"-a -F -n Info images/info.png images.py",
|
||||
"-a -F -i -n Icon images/icon-256.png images.py",
|
||||
]
|
||||
|
||||
|
12
esptool-py-why-here.txt
Normal file
@ -0,0 +1,12 @@
|
||||
If esptool.py is installed using python setup.py` from a checked out version it creates something like the following
|
||||
in the Python environment
|
||||
|
||||
esptool.py file containing
|
||||
|
||||
#!/usr/local/opt/python/bin/python2.7
|
||||
# EASY-INSTALL-SCRIPT: 'esptool==1.3.dev0','esptool.py'
|
||||
__requires__ = 'esptool==1.3.dev0'
|
||||
__import__('pkg_resources').run_script('esptool==1.3.dev0', 'esptool.py')
|
||||
|
||||
PyInstaller (and cx_Freeze) doesn't support pkg_resources and complains about 'ImportError: "No module named
|
||||
pkg_resources"'. This can be avoided if the application maintains a local copy of esptool.py.
|
1311
esptool.py
Executable file
87
images.py
@ -7606,93 +7606,6 @@ Splash = PyEmbeddedImage(
|
||||
"OuPbk3NlUi8yrl/3VYFP7sMlU31aO8jWC1dyUeDftTTrOv6coEp+yK7/QzFrnT7WTSOBiWd8"
|
||||
"DzjhI4F6KouFx5ME0tQbgfvfmHIzmcf7eP0AAAAASUVORK5CYII=")
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
Info = PyEmbeddedImage(
|
||||
"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"
|
||||
"By2uWUJZkrXk9yKborKit0sTljYv01m2eNnjn4J+qiumFwuLu5e7L9++Al/BX9G+0n7l5pVf"
|
||||
"S7glV0ptSstLP6/irLrys+3PFT+PrE5b3b7Gac22tcS1grW31nmtqylTKSsse7x+8vqGDawN"
|
||||
"JRvebpy58XK5Q/n2TZRN4k09FeEVTZuNNq/d/Lkyo/JmlW/VwS3aW1Zueb+Vu7Vrm/e2+u06"
|
||||
"20u3f9rB33F7Z9DOhmqT6vJdxF0Fu57ujt/d+ovLL7V7tPaU7vmyV7C3pya65nytc23tPu19"
|
||||
"a+rQOnFd//5p+zsO+B9oqreq33mQebD0EDgkPvTHrym/3jocdrjliMuR+qPGR7ccYxwraUAa"
|
||||
"5jYMNmY09jQlNXUeDz3e0uzefOw369/2ntA/UXVS/eSaU5RTy06NnC48PXQm98zA2fSzj1tm"
|
||||
"ttw7l3juxvkp59svhF24dDHw4rlWn9bTlzwunbjsdvn4FZcrjVedrja0ObYd+93x92PtTu0N"
|
||||
"15yvNXW4djR3Tuo81eXVdfa6//WLN0JuXL0ZcbPzVtyt293Tuntuc28/v5N959XdgrvD9xbf"
|
||||
"J9wveaD8oPyh9sPqf5j/42CPU8/JXv/etkcxj+495jx+8UT05HPfsqe0p+XP9J7VPrd7fqI/"
|
||||
"sL/jj6l/9L3IfTE8UPynyp9bXpq9PPqX919tg4mDfa+Er0Zer3qj+WbvW4e3LUNRQw/f5bwb"
|
||||
"fl/yQfNDzUeXj62fEj49G579mfS54ov5l+avYV/vj+SMjOSyhWzpUQCDDU1LA+D1XgBoSfDs"
|
||||
"0AEAhS67e0kFkd0XpQj8Jyy7n0nFCYC93gDELQYgHJ5RtsFmDDEV9pKjd6w3QO3tx5pcRGn2"
|
||||
"drJYVHiDIXwYGXmjAwCpGYAvwpGR4a0jI192Q7J3ADiTJ7vzSYQIz/c7JPdJ0NatDH6UfwI5"
|
||||
"m2viYAPWEQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAdVpVFh0WE1MOmNvbS5hZG9iZS54bXAA"
|
||||
"AAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBD"
|
||||
"b3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8x"
|
||||
"OTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6"
|
||||
"YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90"
|
||||
"aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpDb21wcmVzc2lvbj4xPC90aWZmOkNvbXByZXNz"
|
||||
"aW9uPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgog"
|
||||
"ICAgICAgICA8dGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPjI8L3RpZmY6UGhvdG9t"
|
||||
"ZXRyaWNJbnRlcnByZXRhdGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRm"
|
||||
"OlJERj4KPC94OnhtcG1ldGE+CgLYgAUAAAM2SURBVDgRTVNNaBtHFP5mdle2JFvIazsiiWMj"
|
||||
"G2KcprekuMQBhbbk4uJTpEMPgVwKLQ7kGApucushIZe2h97TUgUaCiEtKL5E9Bw3OAmuhUMc"
|
||||
"2LYrGUn1alfen3l9oyTggWFm3sz73nvffA84NOjSJePQUW+HeWYO2+jGDXn4LN4dtLO4dy/5"
|
||||
"DMh9df58OXv69MfDk5PT0jBk4HmO5ziP1123ulqrOQQIEEEIQQOAd85/AGdnL1/+bnJh4axh"
|
||||
"24BSQCoFjIwAvg9nZ6ex3eutlW7f/kkHJiIhqhy5zJG188KVKw/H5ucnQiDRt/LMGQgN0miQ"
|
||||
"HB0VIpUy93Z2sBlFX5bW1r7nTKTUzqucdnF5+duxI0cm/E7nIGo2DWVZplkqmfLcOTMJAiva"
|
||||
"2zPj/f1o/OhRFIm++fXu3QucvjJ1Kp8X5ypjtv2B1+kmMI0U4yJ++hT9+/chuFbqdCByOSTd"
|
||||
"riUzmWg6lxt1HecaZ1kfAAwXJj+ykgRhP1AUhoYsFpGqVCDiGNTvg9JpULcLwWuklGFx0HwY"
|
||||
"Lv5Yr58yPwTSJtFMzCQlpik0q8p1ofb3kb54EcTA/safQLvN1DP9USyUFJRSys4GwSnzfbap"
|
||||
"OJaq14Pi1IVm/PlzHHDqQ0tLEGxTgT/4BTHMsjBCwWJRNDRkSClH5Q98FRI58DxQEJDilQGB"
|
||||
"iQnNMZhpEJ8Tbecg5PuUdDriwLJC5HLNgapC2358oB/4vlRhiGRrC3Gj8UYHnEny6hWo1YJi"
|
||||
"PkS/ryIG6BYKLo4dezYAeLmy8vNePr891O0aanc3EsvLGLl5EzKbBbOOzPXroPl5qHab6PVr"
|
||||
"1Zmagjc7+/DTEye2ZZWrWlldddxS6ev28eMwGw2L2Y/iZlN5tRp5jx5R7LrEtkSur8d+Pm/t"
|
||||
"Li7+FU1P3+HqBkMMtM3b+q1bX7yoVP5rptP0N5fOU/3DqvxXf4xt08urV6lerW7VNjaWtKcW"
|
||||
"65te0JrmxtDG3x88uGBvbl7LtlqLpueNcceY0fh42Juacr25ud/UzMydT06efFGtVo1yuZwM"
|
||||
"AN6iCd1ebFAMbf7y5Ml7ac9b4C8biTOZVlIoPNM1v30r+SU3CfA/ZNahIBhUgOQAAAAASUVO"
|
||||
"RK5CYII=")
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
Icon = PyEmbeddedImage(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAAXNSR0IArs4c6QAAACBjSFJN"
|
||||
|
Before Width: | Height: | Size: 11 KiB |
BIN
images/gui.png
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 236 KiB |
Before Width: | Height: | Size: 4.8 KiB |
BIN
images/info.png
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.3 KiB |
@ -1,6 +0,0 @@
|
||||
esptool==4.8.1
|
||||
pyserial~=3.5
|
||||
wxPython==4.2.2
|
||||
PyInstaller==6.11.1
|
||||
httplib2>=0.18.1
|
||||
pyinstaller-versionfile>=2.0.0
|
@ -1,9 +0,0 @@
|
||||
# https://github.com/DudeNr33/pyinstaller-versionfile
|
||||
# create-version-file windows-metadata.yaml --outfile windows-version-info.txt
|
||||
Version: 5.1.0
|
||||
CompanyName: Marcel Stör
|
||||
FileDescription: NodeMCU PyFlasher
|
||||
InternalName: NodeMCU PyFlasher
|
||||
LegalCopyright: © Marcel Stör. All rights reserved.
|
||||
OriginalFilename: NodeMCU-PyFlasher.exe
|
||||
ProductName: NodeMCU PyFlasher
|
@ -1,46 +0,0 @@
|
||||
# GENERATED FILE. DO NOT EDIT. Created by running create-version-file windows-metadata.yaml --outfile windows-version-info.txt
|
||||
#
|
||||
# UTF-8
|
||||
#
|
||||
# For more details about fixed file info 'ffi' see:
|
||||
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
|
||||
|
||||
VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
|
||||
# Set not needed items to zero 0. Must always contain 4 elements.
|
||||
filevers=(5,1,0,0),
|
||||
prodvers=(5,1,0,0),
|
||||
# Contains a bitmask that specifies the valid bits 'flags'r
|
||||
mask=0x3f,
|
||||
# Contains a bitmask that specifies the Boolean attributes of the file.
|
||||
flags=0x0,
|
||||
# The operating system for which this file was designed.
|
||||
# 0x4 - NT and there is no need to change it.
|
||||
OS=0x40004,
|
||||
# The general type of file.
|
||||
# 0x1 - the file is an application.
|
||||
fileType=0x1,
|
||||
# The function of the file.
|
||||
# 0x0 - the function is not defined for this fileType
|
||||
subtype=0x0,
|
||||
# Creation date and time stamp.
|
||||
date=(0, 0)
|
||||
),
|
||||
kids=[
|
||||
StringFileInfo(
|
||||
[
|
||||
StringTable(
|
||||
u'040904B0',
|
||||
[StringStruct(u'CompanyName', u'Marcel Stör'),
|
||||
StringStruct(u'FileDescription', u'NodeMCU PyFlasher'),
|
||||
StringStruct(u'FileVersion', u'5.1.0.0'),
|
||||
StringStruct(u'InternalName', u'NodeMCU PyFlasher'),
|
||||
StringStruct(u'LegalCopyright', u'© Marcel Stör. All rights reserved.'),
|
||||
StringStruct(u'OriginalFilename', u'NodeMCU-PyFlasher.exe'),
|
||||
StringStruct(u'ProductName', u'NodeMCU PyFlasher'),
|
||||
StringStruct(u'ProductVersion', u'5.1.0.0')])
|
||||
]),
|
||||
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
|
||||
]
|
||||
)
|