Compare commits

..

53 Commits

Author SHA1 Message Date
Marcel Stör
0f1b1099b6
Update shields 2025-02-07 17:33:52 +01:00
Marcel Stör
2d5daab9f1 Migrate to esptool v4
Fixes #105
2025-01-30 22:27:32 +01:00
amelted
31584f693b Fix Windows hovering issue with popup (#104) 2024-07-14 22:27:28 +02:00
Marcel Stör
f22d7857a0 Version bumps 2024-07-14 22:27:28 +02:00
Marcel Stör
2fbc40315d Bump PyInstaller to 4.8
Fixes #77
2022-01-18 22:39:26 +01:00
Marcel Stör
eef9f3a58d Fix the Windows + Python 3.8+ + wxPython 4.1 locale issue
Fixes #78
2022-01-18 22:30:28 +01:00
Marcel Stör
4899c16d4e Properly embed version into executables 2021-04-10 16:47:33 +02:00
Marcel Stör
0122639652 Replace reload button
Use a regular one instead of the bitmap button.
2021-04-08 23:20:07 +02:00
Marcel Stör
d742c2ff13 Bump versions 2021-02-03 08:28:10 +01:00
Utkarsh Sethi
d7c61bd814
Bump Year (#59)
2020
2020-03-01 11:16:53 +01:00
Marcel Stör
1887e38bb1
Upgrade pyinstaller to 3.6 due to CVE-2019-16784 2020-02-22 22:41:26 +01:00
Marcel Stör
4954c93602 Upgrade GUI screen shot 2019-02-17 15:53:47 +01:00
Marcel Stör
4500d940ae Improve console text control by specifying font size in pixels not points 2019-02-17 15:05:42 +01:00
Marcel Stör
29b7193fcf Improve esptool command assembler to support " " in file paths 2019-02-17 15:00:00 +01:00
Marcel Stör
9dc749d768 Bump versions 2019-02-12 22:18:06 +01:00
Marcel Stör
6b4a50835a Bump wxPython to 4.0.4
Fixes #45
2019-02-12 21:59:12 +01:00
Marcel Stör
0345939f08 Bump esptool.py to 2.6
Fixes #43
2019-02-12 21:27:32 +01:00
Marcel Stör
c8bfcc11f2 Bump year 2019-02-12 21:25:09 +01:00
Murilo Polese
fd0122053c Add requirements.txt and update instructions (#40) 2018-11-23 12:09:22 +01:00
Marcel Stör
0e5e250ade Reintroduce the serial port dropdown, add auto-select option 2018-11-22 21:17:51 +01:00
Marcel Stör
861f75415c Upgrade to esptool 2.5.1 with auto-port detection
Remove dropdown for serial port selection.
2018-11-12 23:39:23 +01:00
Marcel Stör
6df7c813e2 Add missing EOF newline 2018-02-12 21:32:54 +01:00
Marcel Stör
cb3606e00d Add .dmg for macOS 2018-02-11 00:54:09 +01:00
Marcel Stör
0fd2b7b7f7 Update to reflect status with 3.0 2018-02-10 17:41:29 +01:00
Marcel Stör
2eeb9893db Major upgrade of all components
- wxPython 4.0.1, finally...
- Python 3
- esptool.py 2.2.1
2018-02-10 00:05:03 +01:00
Marcel Stör
13961407fe Bump esptool to 2.2 2017-12-18 15:24:17 +01:00
Marcel Stör
a6aae0c7fc Small fixes
- address style warnings
- tune console output at the end of the process
2017-10-02 22:16:17 +02:00
Marcel Stör
db84ce52c7 Add flash mode 'dout'
Fixes #20
2017-09-26 08:26:28 +02:00
Marcel Stör
4d73e11dc7 Stop triggering hard-reset after flash
Fixes #22
2017-08-17 21:33:49 +02:00
Marcel Stör
baf99cae2a Add more instructions to README 2017-08-12 22:00:53 +02:00
Marcel Stör
55d3169279 Upgrade to esptool 2.0.1 2017-07-15 22:35:50 +02:00
Marcel Stör
b26b0b20c6 Upgrade esptool.py to final 2.0 2017-06-14 21:47:03 +02:00
Marcel Stör
750ee6cfb3 Upgrade esptool.py to latest 2.0
Brings support for ESP32 and flash chips >4MB, fixes #15
2017-05-20 11:43:47 +02:00
Marcel Stör
9d1b526689 Add try/catch around opening serial port and dump exception to console
Fixes #8
2017-05-07 22:51:46 +02:00
Marcel Stör
a233e6f3f3 Expand README 2017-04-23 22:33:19 +02:00
Marcel Stör
76814d7467 Add PayPal image to About dialog 2017-04-16 21:13:15 +02:00
Marcel Stör
73b06fc48e Fix badge title 2017-04-16 20:53:00 +02:00
Marcel Stör
3df225763f Add PayPal donation badge 2017-04-16 20:51:19 +02:00
Marcel Stör
c6a34aa761 Bump version number for 1.0 2017-03-18 17:04:06 +01:00
Marcel Stör
2c134ef03f Remember settings by writing a file in the user dir, fixes #5 2017-02-28 22:25:58 +01:00
Marcel Stör
1c12a9cffa Reduce size of images used in about dialog to 64x64, fixes #3 2017-02-18 22:37:20 +01:00
Marcel Stör
862eb4bcb8 Fix GH link in about dialog, fixes #4 2017-02-18 22:21:10 +01:00
Marcel Stör
c27e8763a7 Delete facebook-badge.svg 2017-02-13 23:34:32 +01:00
Marcel Stör
411bc5ea74 Replace shields.io badges with custom copy 2017-02-13 23:33:11 +01:00
Marcel Stör
3b4e3ba07a Fix FB badge for testing 2017-02-13 23:00:25 +01:00
Marcel Stör
35f8bc1d75 Add FB badge for testing 2017-02-13 22:36:03 +01:00
Marcel Stör
b8638dbb46 Add Twitter badge 2017-01-18 07:31:49 +01:00
Marcel Stör
fac81dad3c Switch from __ back to _ in most cases 2017-01-14 23:36:28 +01:00
Marcel Stör
83ac8b059a First batch of refactoring after reviews
- reorder widget creation to get proper tab order
- rewrite event handling for radio button groups
- make widget event handler functions function-local
2017-01-14 23:09:36 +01:00
Marcel Stör
cb3f98652b Upgrade esptool.py to v1.3 2017-01-10 22:49:26 +01:00
Marcel Stör
7cbb0f181c Add a few links to README 2017-01-08 15:54:35 +01:00
Marcel Stör
050618fec5 Update README.md 2017-01-08 15:39:07 +01:00
Marcel Stör
5a63c35c05 Update README.md 2017-01-07 22:59:21 +01:00
22 changed files with 687 additions and 1549 deletions

2
.gitignore vendored
View File

@ -1,4 +1,5 @@
.idea/
.DS_Store
# Byte-compiled / optimized / DLL files
__pycache__/
@ -83,6 +84,7 @@ celerybeat-schedule
# virtualenv
venv/
ENV/
.venv
# Spyder project settings
.spyderproject

View File

@ -1,23 +1,27 @@
# coding=utf-8
import sys, os, wx
import sys
import datetime
import os
import wx
import wx.html
import wx.lib.wxpTag
import webbrowser
from Main import __version__
# ---------------------------------------------------------------------------
class MyAboutBox(wx.Dialog):
class AboutDlg(wx.Dialog):
text = '''
<html>
<body bgcolor="#DCDCDC" style="font-family: Arial; background-color: #DCDCDC;">
<center>
<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">
<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">
<h1>NodeMCU PyFlasher</h1>
@ -26,7 +30,14 @@ class MyAboutBox(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>&copy; 2016-2017 Marcel St&ouml;r. Licensed under MIT.</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>&copy; {2} Marcel St&ouml;r. Licensed under MIT.</p>
<p>
<wxp module="wx" class="Button">
@ -40,22 +51,29 @@ class MyAboutBox(wx.Dialog):
'''
def __init__(self, parent):
wx.Dialog.__init__(self, parent, -1, "About NodeMCU PyFlasher")
html = wx.html.HtmlWindow(self, -1, size=(420, -1))
wx.Dialog.__init__(self, parent, wx.ID_ANY, "About NodeMCU PyFlasher")
html = HtmlWindow(self, wx.ID_ANY, size=(420, -1))
if "gtk2" in wx.PlatformInfo or "gtk3" in wx.PlatformInfo:
html.SetStandardFonts()
txt = self.text.format(self.__get_bundle_dir(), __version__)
txt = self.text.format(self._get_bundle_dir(), __version__, datetime.datetime.now().year)
html.SetPage(txt)
ir = html.GetInternalRepresentation()
html.SetSize((ir.GetWidth() + 25, ir.GetHeight() + 25))
self.SetClientSize(html.GetSize())
self.CentreOnParent(wx.BOTH)
def __get_bundle_dir(self):
@staticmethod
def _get_bundle_dir():
# 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())
# ---------------------------------------------------------------------------

View File

@ -0,0 +1,30 @@
# 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
View File

@ -1,17 +1,41 @@
#!/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
from esptool import ESPROM
from argparse import Namespace
import locale
__version__ = "0.1.0"
# 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]
# ---------------------------------------------------------------------------
@ -20,29 +44,28 @@ __version__ = "0.1.0"
class RedirectText:
def __init__(self, text_ctrl):
self.__out = text_ctrl
self.__pending_backspaces = 0
def write(self, string):
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
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, new_string)
self.__pending_backspaces = number_of_backspaces
wx.CallAfter(self.__out.AppendText, string)
# 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
# ---------------------------------------------------------------------------
@ -51,25 +74,40 @@ 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):
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)
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
# ---------------------------------------------------------------------------
@ -80,12 +118,34 @@ class FlashConfig:
def __init__(self):
self.baud = 115200
self.erase_before_flash = False
self.mode = "qio"
self.file = None
self.mode = "dio"
self.firmware_path = 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.file is not None and self.port is not None
return self.firmware_path is not None and self.port is not None
# ---------------------------------------------------------------------------
@ -94,98 +154,178 @@ class FlashConfig:
class NodeMcuFlasher(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(700, 650),
wx.Frame.__init__(self, parent, -1, title, size=(725, 650),
style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
self.__config = FlashConfig()
self._config = FlashConfig.load(self._get_config_file_path())
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, flashmode_boxsizer,
flashmode_label_boxsizer, 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)])
@ -194,88 +334,84 @@ class NodeMcuFlasher(wx.Frame):
hbox.Add(fgs, proportion=2, flag=wx.ALL | wx.EXPAND, border=15)
panel.SetSizer(hbox)
def __get_serial_ports(self):
ports = [""]
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__]
for port, desc, hwid in sorted(list_ports.comports()):
ports.append(port)
return ports
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):
def _set_icons(self):
self.SetIcon(images.Icon.GetIcon())
def __build_status_bar(self):
self.statusBar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
def _build_status_bar(self):
self.statusBar = self.CreateStatusBar(2, wx.STB_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):
def _build_menu_bar(self):
self.menuBar = wx.MenuBar()
# File menu
self.menuBar = wx.MenuBar()
menu = wx.Menu()
file_menu = wx.Menu()
wx.App.SetMacExitMenuItemId(wx.ID_EXIT)
exit_item = menu.Append(wx.ID_EXIT, "E&xit\tCtrl-Q", "Exit NodeMCU PyFlasher")
exit_item = file_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(menu, "&File")
self.Bind(wx.EVT_MENU, self._on_exit_app, exit_item)
self.menuBar.Append(file_menu, "&File")
# Help menu
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')
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')
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):
def _on_exit_app(self, event):
self._config.safe(self._get_config_file_path())
self.Close(True)
def __on_help_about(self, event):
from About import MyAboutBox
about = MyAboutBox(self)
def _on_help_about(self, event):
from About import AboutDlg
about = AboutDlg(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.SplashScreen):
class MySplashScreen(wx.adv.SplashScreen):
def __init__(self):
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)
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)
def __on_close(self, evt):
def _on_close(self, evt):
# Make sure the default handler runs too so this window gets
# destroyed
evt.Skip()
@ -285,9 +421,9 @@ class MySplashScreen(wx.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():
@ -299,7 +435,9 @@ class MySplashScreen(wx.SplashScreen):
# ----------------------------------------------------------------------------
class App(wx.App, wx.lib.mixins.inspection.InspectionMixin):
def OnInit(self):
wx.SystemOptions.SetOptionInt("mac.window-plain-transition", 1)
# see https://discuss.wxpython.org/t/wxpython4-1-1-python3-8-locale-wxassertionerror/35168
self.ResetLocale()
wx.SystemOptions.SetOption("mac.window-plain-transition", 1)
self.SetAppName("NodeMCU PyFlasher")
# Create and show the splash screen. It will then create and
@ -322,6 +460,8 @@ def main():
app.MainLoop()
# ---------------------------------------------------------------------------
if __name__ == '__main__':
__name__ = 'Main'
main()

View File

@ -1,6 +1,71 @@
# NodeMCU PyFlasher
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/marcelstoer/nodemcu-pyflasher/blob/master/LICENSE)
[![License](https://marcelstoer.github.io/nodemcu-pyflasher/images/mit-license-badge.svg)](https://github.com/marcelstoer/nodemcu-pyflasher/blob/master/LICENSE)
[![Github Downloads (all assets, all releases)](https://img.shields.io/github/downloads/marcelstoer/nodemcu-pyflasher/total.svg?style=flat)](https://github.com/marcelstoer/nodemcu-pyflasher/releases)
[![GitHub Downloads (all assets, latest release)](https://img.shields.io/github/downloads/marcelstoer/nodemcu-pyflasher/latest/total?style=flat)](https://github.com/marcelstoer/nodemcu-pyflasher/releases)
[![PayPal Donation](https://img.shields.io/badge/donate_through-PayPal-%23009cde?logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HFN4ZMET5XS2Q)
Self-contained NodeMCU flasher with GUI based on [esptool.py](https://github.com/espressif/esptool) and [wxPython](https://www.wxpython.org/).
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/).
![Image of NodeMCU PyFlasher GUI](images/gui.png)
![Image of NodeMCU PyFlasher GUI](images/gui.png)
## 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.
[![PayPal Donations](./images/paypal-256.png)](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

View File

@ -1,31 +1,60 @@
# -*- mode: python -*-
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
import os
# 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'],
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')
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')

View File

@ -1,26 +1,46 @@
# -*- mode: python -*-
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
# 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"
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')
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,
)

View File

@ -1,5 +1,13 @@
#!/usr/bin/env bash
#rm -fr build dist
# rm -fr build dist
VERSION=5.1.0
NAME="NodeMCU PyFlasher"
DIST_NAME="NodeMCU-PyFlasher"
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"

View File

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

View File

@ -1,12 +0,0 @@
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

File diff suppressed because it is too large Load Diff

View File

@ -7606,6 +7606,93 @@ Splash = PyEmbeddedImage(
"OuPbk3NlUi8yrl/3VYFP7sMlU31aO8jWC1dyUeDftTTrOv6coEp+yK7/QzFrnT7WTSOBiWd8"
"DzjhI4F6KouFx5ME0tQbgfvfmHIzmcf7eP0AAAAASUVORK5CYII=")
#----------------------------------------------------------------------
Info = PyEmbeddedImage(
"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"
"EwgTXCcIJlRP6LaiWvlYFVjVWfVaM63DrYusG61fTjSamDxx3cTWiV9tHG2ybXbb3LNVtQ21"
"LbJttn1tZ2HHsauyu2FPsw+0X2TfZP/KwdKB57DN4bYjw3Gy43LHFscvTs5OQqd6p35nI+cU"
"5y3O3S5qLlEuq1wuuRJcfV0XuZ5w/ejm5JbvdtjtL3cr9yz3fe7PJ5lO4k3aPemxh4EH22On"
"R48nyzPFc4dnj5e+F9ur2uuRt6E313uP9zMfc59Mn/0+L31tfIW+x3zf+7n5LfA744/5B/mX"
"+LcHqAbEBVQGPAw0CEwPrAscDHIMmhd0JpgQHBa8Lrg7RCeEE1IbMhjqHLog9HwYNSwmrDLs"
"UbhFuDC8eTI6OXTy+sn3I4wjBBGNkSAyJHJ95IMo06i8qN+mEKdETama8jTaNnp+dGsMI2Zm"
"zL6Yd7G+sWti78WZxYnjWuKV4qfF18a/T/BPKEvoSZyYuCDxapJWEj+pKZmUHJ+8J3loasDU"
"jVP7pjlOK552a7rp9DnTL8/QmpE94+RMpZnsmUdSCCkJKftSPrMj2dXsodSQ1C2pgxw/zibO"
"C643dwO3n+fBK+M9S/NIK0t7nu6Rvj69P8MrozxjgO/Hr+S/ygzO3J75Pisya2/WSHZC9sEc"
"ck5KznGBqiBLcH6W7qw5szpzLXOLc3vy3PI25g0Kw4R7RIhouqgpXw0ec9rEZuKfxL0FngVV"
"BR9mx88+MkdljmBO21yLuSvnPisMLPxlHj6PM69lvv78JfN7F/gs2LkQWZi6sGWR4aJli/oW"
"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"

BIN
images/espressif-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 83 KiB

BIN
images/icon-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
images/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
images/paypal-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/python-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/wxpython-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
esptool==4.8.1
pyserial~=3.5
wxPython==4.2.2
PyInstaller==6.11.1
httplib2>=0.18.1
pyinstaller-versionfile>=2.0.0

9
windows-metadata.yaml Normal file
View File

@ -0,0 +1,9 @@
# 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

46
windows-version-info.txt Normal file
View File

@ -0,0 +1,46 @@
# 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])])
]
)