Skip to content

Commit cd66698

Browse files
author
Nathan Seidle
committed
Initial commit
0 parents  commit cd66698

File tree

10 files changed

+275
-0
lines changed

10 files changed

+275
-0
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.vscode/arduino.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"board": "esp8266:esp8266:d1",
3+
"configuration": "xtal=80,vt=flash,exception=disabled,eesz=4M,ip=lm2f,dbg=Disabled,lvl=None____,wipe=none,baud=921600",
4+
"port": "COM3"
5+
}

.vscode/c_cpp_properties.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"configurations": [
3+
{
4+
"name": "Win32",
5+
"includePath": [
6+
"C:\\Users\\nathan.seidle\\AppData\\Local\\Arduino15\\packages\\esp8266\\tools\\**",
7+
"C:\\Users\\nathan.seidle\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\2.5.0\\**"
8+
],
9+
"forcedInclude": [],
10+
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.23.28105/bin/Hostx64/x64/cl.exe",
11+
"cStandard": "c11",
12+
"cppStandard": "c++17"
13+
}
14+
],
15+
"version": 4
16+
}

AFU.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
"""
2+
This is a simple firmware upload GUI designed for the Artemis platform.
3+
Very handy for updating devices in the field without the need for compiling
4+
and uploading through Arduino.
5+
6+
Based on gist by Stefan Lehmann: https://gist.github.com/stlehmann/bea49796ad47b1e7f658ddde9620dff1
7+
8+
MIT license
9+
10+
TODO:
11+
12+
Push user to upgrade bootloader as needed
13+
14+
"""
15+
from typing import Iterator, Tuple
16+
from serial.tools.list_ports import comports
17+
from PyQt5.QtCore import QSettings, QProcess
18+
from PyQt5.QtWidgets import QWidget, QLabel, QComboBox, QGridLayout, \
19+
QPushButton, QApplication, QLineEdit, QFileDialog
20+
from PyQt5.QtGui import QCloseEvent
21+
22+
# import artemis_svl
23+
24+
# Setting constants
25+
SETTING_PORT_NAME = 'port_name'
26+
SETTING_FILE_LOCATION = 'message'
27+
SETTING_BAUD_RATE = '921600'
28+
29+
progressCount = 1
30+
31+
guiVersion = 'v1.0'
32+
33+
34+
def gen_serial_ports() -> Iterator[Tuple[str, str]]:
35+
"""Return all available serial ports."""
36+
ports = comports()
37+
return ((p.description, p.device) for p in ports)
38+
39+
# noinspection PyArgumentList
40+
41+
42+
class RemoteWidget(QWidget):
43+
"""Main Widget."""
44+
45+
def __init__(self, parent: QWidget = None) -> None:
46+
super().__init__(parent)
47+
48+
# File location line edit
49+
self.msg_label = QLabel(self.tr('Firmware File:'))
50+
self.fileLocation_lineedit = QLineEdit()
51+
self.msg_label.setBuddy(self.msg_label)
52+
self.fileLocation_lineedit.setEnabled(False)
53+
self.fileLocation_lineedit.returnPressed.connect(
54+
self.on_browse_btn_pressed)
55+
56+
# Browse for new file button
57+
self.browse_btn = QPushButton(self.tr('Browse'))
58+
self.browse_btn.setEnabled(True)
59+
self.browse_btn.pressed.connect(self.on_browse_btn_pressed)
60+
61+
# Port Combobox
62+
self.port_label = QLabel(self.tr('COM Port:'))
63+
self.port_combobox = QComboBox()
64+
self.port_label.setBuddy(self.port_combobox)
65+
self.update_com_ports()
66+
67+
# Refresh Button
68+
self.refresh_btn = QPushButton(self.tr('Refresh'))
69+
self.refresh_btn.pressed.connect(self.on_refresh_btn_pressed)
70+
71+
# Baudrate Combobox
72+
self.baud_label = QLabel(self.tr('Baud:'))
73+
self.baud_combobox = QComboBox()
74+
self.baud_label.setBuddy(self.baud_combobox)
75+
self.update_baud_rates()
76+
77+
# Upload Button
78+
self.upload_btn = QPushButton(self.tr('Upload'))
79+
self.upload_btn.pressed.connect(self.on_upload_btn_pressed)
80+
81+
# Status bar
82+
self.status_label = QLabel(self.tr('Status:'))
83+
self.status = QLabel(self.tr(' '))
84+
85+
# Arrange Layout
86+
layout = QGridLayout()
87+
layout.addWidget(self.msg_label, 0, 0)
88+
layout.addWidget(self.fileLocation_lineedit, 0, 1)
89+
layout.addWidget(self.browse_btn, 0, 2)
90+
91+
layout.addWidget(self.port_label, 1, 0)
92+
layout.addWidget(self.port_combobox, 1, 1)
93+
layout.addWidget(self.refresh_btn, 1, 2)
94+
95+
layout.addWidget(self.baud_label, 2, 0)
96+
layout.addWidget(self.baud_combobox, 2, 1)
97+
layout.addWidget(self.upload_btn, 3, 2)
98+
99+
layout.addWidget(self.status_label, 3, 0)
100+
layout.addWidget(self.status, 3, 1)
101+
self.setLayout(layout)
102+
103+
self._load_settings()
104+
105+
def _load_settings(self) -> None:
106+
"""Load settings on startup."""
107+
settings = QSettings()
108+
109+
# port name
110+
port_name = settings.value(SETTING_PORT_NAME)
111+
if port_name is not None:
112+
index = self.port_combobox.findData(port_name)
113+
if index > -1:
114+
self.port_combobox.setCurrentIndex(index)
115+
116+
# last message
117+
msg = settings.value(SETTING_FILE_LOCATION)
118+
if msg is not None:
119+
self.fileLocation_lineedit.setText(msg)
120+
121+
baud = settings.value(SETTING_BAUD_RATE)
122+
if baud is not None:
123+
index = self.baud_combobox.findData(baud)
124+
if index > -1:
125+
self.baud_combobox.setCurrentIndex(index)
126+
127+
def _save_settings(self) -> None:
128+
"""Save settings on shutdown."""
129+
settings = QSettings()
130+
settings.setValue(SETTING_PORT_NAME, self.port)
131+
settings.setValue(SETTING_FILE_LOCATION,
132+
self.fileLocation_lineedit.text())
133+
settings.setValue(SETTING_BAUD_RATE, self.baudRate)
134+
135+
def show_error_message(self, msg: str) -> None:
136+
"""Show a Message Box with the error message."""
137+
QMessageBox.critical(self, QApplication.applicationName(), str(msg))
138+
139+
def update_com_ports(self) -> None:
140+
"""Update COM Port list in GUI."""
141+
self.port_combobox.clear()
142+
for name, device in gen_serial_ports():
143+
self.port_combobox.addItem(name, device)
144+
145+
def update_baud_rates(self) -> None:
146+
"""Update COM Port list in GUI."""
147+
self.baud_combobox.addItem("921600", 921600)
148+
self.baud_combobox.addItem("460800", 460800)
149+
self.baud_combobox.addItem("115200", 115200)
150+
# self.baud_combobox.addItem("9600", 9600) #Used to test comm failure
151+
152+
@property
153+
def port(self) -> str:
154+
"""Return the current serial port."""
155+
return self.port_combobox.currentData()
156+
157+
@property
158+
def baudRate(self) -> str:
159+
"""Return the current baud rate."""
160+
return self.baud_combobox.currentData()
161+
162+
def closeEvent(self, event: QCloseEvent) -> None:
163+
"""Handle Close event of the Widget."""
164+
self._save_settings()
165+
166+
event.accept()
167+
168+
def on_refresh_btn_pressed(self) -> None:
169+
self.update_com_ports()
170+
171+
def on_upload_btn_pressed(self) -> None:
172+
"""Check if port is available"""
173+
portAvailable = False
174+
ports = comports()
175+
for p in ports:
176+
if (p.device == self.port):
177+
portAvailable = True
178+
if (portAvailable == False):
179+
self.status.setText("Port No Longer Available")
180+
return
181+
182+
"""Check if file exists"""
183+
fileExists = False
184+
try:
185+
f = open(self.fileLocation_lineedit.text())
186+
fileExists = True
187+
except IOError:
188+
fileExists = False
189+
finally:
190+
if (fileExists == False):
191+
self.status.setText("File Not Found")
192+
return
193+
f.close()
194+
195+
global progressCount
196+
progressCount = 0
197+
198+
self.status.setText("Uploading ")
199+
200+
self.process = QProcess()
201+
self.process.readyReadStandardError.connect(
202+
self.onReadyReadStandardError)
203+
self.process.readyReadStandardOutput.connect(
204+
self.onReadyReadStandardOutput)
205+
206+
self.process.start("artemis_svl.exe " + str(self.port) +
207+
" -f\"" + self.fileLocation_lineedit.text() + "\"" + " -b" + str(self.baudRate))
208+
209+
def onReadyReadStandardError(self):
210+
error = self.process.readAllStandardError().data().decode()
211+
# print(error)
212+
self.status.setText(error)
213+
214+
def onReadyReadStandardOutput(self):
215+
"""Parse the output from the process. Update our status as we go."""
216+
result = self.process.readAllStandardOutput().data().decode()
217+
# print(result)
218+
if ("complete" in result):
219+
self.status.setText("Complete")
220+
elif ("failed" in result):
221+
self.status.setText("Upload Failed")
222+
elif ("open" in result):
223+
self.status.setText("Port In Use / Please Close")
224+
else: # The '#' is displayed 50 times until completion
225+
global progressCount
226+
progressCount = progressCount + result.count("#")
227+
# print(progressCount)
228+
for i in range(int(progressCount / 3)):
229+
current = self.status.text() + "."
230+
self.status.setText(current)
231+
232+
def on_browse_btn_pressed(self) -> None:
233+
"""Open dialog to select bin file."""
234+
options = QFileDialog.Options()
235+
fileName, _ = QFileDialog.getOpenFileName(
236+
None,
237+
"Select Firmware to Upload",
238+
"",
239+
"Firmware Files (*.bin);;All Files (*)",
240+
options=options)
241+
if fileName:
242+
self.fileLocation_lineedit.setText(fileName)
243+
244+
245+
if __name__ == '__main__':
246+
import sys
247+
app = QApplication([])
248+
app.setOrganizationName('SparkFun')
249+
app.setApplicationName('Artemis Firmware Uploader')
250+
w = RemoteWidget()
251+
w.show()
252+
sys.exit(app.exec_())

Blink.bin

8.3 KB
Binary file not shown.

Together/AFU.exe

28.8 MB
Binary file not shown.

Together/AFU.zip

33.9 MB
Binary file not shown.

Together/Blink.bin

8.3 KB
Binary file not shown.

Together/artemis_svl.exe

5.45 MB
Binary file not shown.

artemis_svl.exe

5.45 MB
Binary file not shown.

0 commit comments

Comments
 (0)