Compare commits

..

No commits in common. "master" and "v3.0" have entirely different histories.
master ... v3.0

13 changed files with 2731 additions and 269 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

@ -2,7 +2,6 @@
# coding=utf-8
import sys
import datetime
import os
import wx
import wx.html
@ -37,7 +36,7 @@ class AboutDlg(wx.Dialog):
</a>
</p>
<p>&copy; {2} Marcel St&ouml;r. Licensed under MIT.</p>
<p>&copy; 2018 Marcel St&ouml;r. Licensed under MIT.</p>
<p>
<wxp module="wx" class="Button">
@ -55,7 +54,7 @@ class AboutDlg(wx.Dialog):
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__, 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))
@ -66,7 +65,6 @@ class AboutDlg(wx.Dialog):
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__))

127
Main.py
View File

@ -13,12 +13,11 @@ import json
import images as images
from serial import SerialException
from serial.tools import list_ports
import locale
from esptool import ESPLoader
from esptool import NotImplementedInROMError
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"
__version__ = "3.0"
__flash_help__ = '''
<p>This setting is highly dependent on your device!<p>
<p>
@ -33,8 +32,6 @@ __flash_help__ = '''
</ul>
</p>
'''
__auto_select__ = "Auto-select"
__auto_select_explanation__ = "(first port with Espressif device)"
__supported_baud_rates__ = [9600, 57600, 74880, 115200, 230400, 460800, 921600]
# ---------------------------------------------------------------------------
@ -61,11 +58,6 @@ class RedirectText:
# noinspection PyStatementEffect
None
# esptool >=3 handles output differently of the output stream is not a TTY
# noinspection PyMethodMayBeStatic
def isatty(self):
return True
# ---------------------------------------------------------------------------
@ -79,36 +71,43 @@ class FlashingThread(threading.Thread):
def run(self):
try:
command = []
initial_baud = min(ESPLoader.ESP_ROM_BAUD, self._config.baud)
if not self._config.port.startswith(__auto_select__):
command.append("--port")
command.append(self._config.port)
esp = ESPLoader.detect_chip(self._config.port, initial_baud)
print("Chip is %s" % (esp.get_chip_description()))
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])
esp = esp.run_stub()
if self._config.baud > initial_baud:
try:
esp.change_baud(self._config.baud)
except NotImplementedInROMError:
print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d." %
initial_baud)
args = Namespace()
args.flash_size = "detect"
args.flash_mode = self._config.mode
args.flash_freq = "40m"
args.no_progress = False
args.no_stub = False
args.verify = False # TRUE is deprecated
args.compress = True
args.addr_filename = [[int("0x00000", 0), open(self._config.firmware_path, 'rb')]]
print("Configuring flash size...")
esptool.detect_flash_size(esp, args)
esp.flash_set_parameters(esptool.flash_size_bytes(args.flash_size))
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.")
esptool.erase_flash(esp, args)
esptool.write_flash(esp, args)
# The last line printed by esptool is "Leaving..." -> some indication that the process is done is needed
print("\nDone.")
except SerialException as e:
self._parent.report_error(e.strerror)
raise e
# ---------------------------------------------------------------------------
@ -154,7 +153,7 @@ 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())
@ -165,11 +164,9 @@ class NodeMcuFlasher(wx.Frame):
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):
@ -207,15 +204,6 @@ class NodeMcuFlasher(wx.Frame):
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)
@ -223,8 +211,9 @@ class NodeMcuFlasher(wx.Frame):
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")
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, on_reload)
reload_button.SetToolTip("Reload serial device list")
@ -232,8 +221,9 @@ class NodeMcuFlasher(wx.Frame):
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)
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)
@ -284,11 +274,10 @@ class NodeMcuFlasher(wx.Frame):
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))
self.console_ctrl.SetFont(wx.Font(13, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
self.console_ctrl.SetBackgroundColour(wx.BLACK)
self.console_ctrl.SetForegroundColour(wx.RED)
self.console_ctrl.SetDefaultStyle(wx.TextAttr(wx.RED))
port_label = wx.StaticText(panel, label="Serial port")
file_label = wx.StaticText(panel, label="NodeMCU firmware")
@ -296,19 +285,15 @@ class NodeMcuFlasher(wx.Frame):
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]))
from HtmlPopupTransientWindow import HtmlPopupTransientWindow
win = HtmlPopupTransientWindow(self, wx.SIMPLE_BORDER, __flash_help__, "#FFB6C1", (410, 140))
win.Popup()
hovered = [win]
image = event.GetEventObject()
image_position = image.ClientToScreen((0, 0))
image_size = image.GetSize()
win.Position(image_position, (0, image_size[1]))
win.Popup()
icon = wx.StaticBitmap(panel, wx.ID_ANY, images.Info.GetBitmap())
icon.Bind(wx.EVT_MOTION, on_info_hover)
@ -316,7 +301,7 @@ class NodeMcuFlasher(wx.Frame):
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)
flashmode_label_boxsizer.Add(icon, 0, wx.ALIGN_RIGHT, 20)
erase_label = wx.StaticText(panel, label="Erase flash")
console_label = wx.StaticText(panel, label="Console")
@ -344,7 +329,7 @@ class NodeMcuFlasher(wx.Frame):
@staticmethod
def _get_serial_ports():
ports = [__auto_select__ + " " + __auto_select_explanation__]
ports = [""]
for port, desc, hwid in sorted(list_ports.comports()):
ports.append(port)
return ports
@ -404,8 +389,6 @@ class NodeMcuFlasher(wx.Frame):
# ---------------------------------------------------------------------------
class MySplashScreen(wx.adv.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)
@ -435,8 +418,6 @@ 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)
self.SetAppName("NodeMCU PyFlasher")

View File

@ -1,20 +1,23 @@
# 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)
[![Github Releases](https://img.shields.io/github/downloads/marcelstoer/nodemcu-pyflasher/total.svg?style=flat)](https://github.com/marcelstoer/nodemcu-pyflasher/releases)
[![PayPal Donation](https://marcelstoer.github.io/nodemcu-pyflasher/images/donate-paypal-badge.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HFN4ZMET5XS2Q)
[![Twitter URL](https://marcelstoer.github.io/nodemcu-pyflasher/images/twitter-badge.svg)](https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fmarcelstoer%2Fnodemcu-pyflasher)
[![Facebook URL](https://marcelstoer.github.io/nodemcu-pyflasher/images/facebook-badge.svg)](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fgithub.com%2Fmarcelstoer%2Fnodemcu-pyflasher)
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)
## 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.
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.
## Status
Scan the [list of open issues](https://github.com/marcelstoer/nodemcu-pyflasher/issues) for bugs and pending features.
**Note**
- Due to [pyinstaller/pyinstaller#2355](https://github.com/pyinstaller/pyinstaller/issues/2355) I can't provide an app bundle for macOS yet. The PyInstaller `.spec` file and the build script are ready, though. Of course you can still *run* the application on macOS. Clone this repo and then do `python nodemcu-pyflasher.py` (see "[Build it yourself](#build-it-yourself)" for pre-requisits).
**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.
@ -29,19 +32,9 @@ All open-source development by the author is donationware. Show your love and su
## 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
```
- Install Python
- Install [wxPython 3.x](https://sourceforge.net/projects/wxpython/files/wxPython/) (not 4.0 betas!)
- Install [esptool.py](https://github.com/espressif/esptool#easy-installation) which brings pySerial or install pySerial standalone
## Why this project exists

View File

@ -1,60 +1,30 @@
# -*- 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-3.0.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-3.0',
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"

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.

2599
esptool.py Executable file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 405 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])])
]
)