Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

22 changed files with 1549 additions and 687 deletions

2
.gitignore vendored
View File

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

View File

@ -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>&copy; {2} Marcel St&ouml;r. Licensed under MIT.</p>
<p>&copy; 2016-2017 Marcel St&ouml;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())
# ---------------------------------------------------------------------------

View File

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

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

View File

@ -1,71 +1,6 @@
# NodeMCU PyFlasher
[![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)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](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/).
![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
![Image of NodeMCU PyFlasher GUI](images/gui.png)

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -7606,93 +7606,6 @@ 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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

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

View File

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

View File

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