Skip to content

Commit 165b04e

Browse files
Add port selector, moved around and cleaned some logic
1 parent 177298e commit 165b04e

File tree

3 files changed

+157
-92
lines changed

3 files changed

+157
-92
lines changed

OATFWGUI/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.0.5'
1+
__version__ = '0.0.6'

OATFWGUI/gui_logic.py

Lines changed: 151 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import sys
44
import zipfile
5+
import json
56
from typing import List, Optional
67
from collections import namedtuple
78
from pathlib import Path
@@ -19,13 +20,72 @@
1920
PioEnv = namedtuple('FWVersion', ['nice_name', 'raw_name'])
2021

2122

23+
def get_pio_environments(fw_dir: str) -> List[PioEnv]:
24+
ini_path = Path(fw_dir, 'platformio.ini')
25+
with open(ini_path.resolve(), 'r') as fp:
26+
ini_lines = fp.readlines()
27+
environment_lines = [ini_line for ini_line in ini_lines if ini_line.startswith('[env:')]
28+
raw_pio_envs = []
29+
for environment_line in environment_lines:
30+
match = re.search(r'\[env:(.+)\]', environment_line)
31+
if match:
32+
raw_pio_envs.append(match.group(1))
33+
log.info(f'Found pio environments: {raw_pio_envs}')
34+
35+
# we don't want to build native
36+
if 'native' in raw_pio_envs:
37+
raw_pio_envs.remove('native')
38+
nice_name_lookup = {
39+
'ramps': 'RAMPS',
40+
'esp32': 'ESP32',
41+
'mksgenlv21': 'MKS Gen L v2.1',
42+
'mksgenlv2': 'MKS Gen L v2',
43+
'mksgenlv1': 'MKS Gen L v1',
44+
}
45+
pio_environments = []
46+
for raw_env in raw_pio_envs:
47+
if raw_env in nice_name_lookup:
48+
pio_env = PioEnv(nice_name_lookup[raw_env], raw_env)
49+
else:
50+
pio_env = PioEnv(raw_env, raw_env)
51+
pio_environments.append(pio_env)
52+
return pio_environments
53+
54+
55+
def download_fw(zip_url: str) -> str:
56+
log.info(f'Downloading OAT FW from: {zip_url}')
57+
resp = requests.get(zip_url)
58+
zipfile_name = 'OATFW.zip'
59+
with open(zipfile_name, 'wb') as fd:
60+
fd.write(resp.content)
61+
fd.close()
62+
return zipfile_name
63+
64+
65+
def extract_fw(zipfile_name: str) -> str:
66+
log.info(f'Extracting FW from {zipfile_name}')
67+
with zipfile.ZipFile(zipfile_name, 'r') as zip_ref:
68+
zip_infolist = zip_ref.infolist()
69+
if len(zip_infolist) > 0 and zip_infolist[0].is_dir():
70+
fw_dir = zip_infolist[0].filename
71+
else:
72+
log.fatal(f'Could not find FW top level directory in {zip_infolist}!')
73+
sys.exit(1)
74+
zip_ref.extractall()
75+
log.info(f'Extracted FW to {fw_dir}')
76+
return fw_dir
77+
78+
2279
class LogicState:
2380
release_list: Optional[List[FWVersion]] = None
2481
release_idx: Optional[int] = None
2582
fw_dir: Optional[str] = None
26-
pio_envs: Optional[List[PioEnv]] = None
83+
pio_envs: List[PioEnv] = []
2784
pio_env: Optional[str] = None
2885
config_file_path: Optional[str] = None
86+
build_success: bool = False
87+
serial_ports: List[str] = []
88+
upload_port: Optional[str] = None
2989

3090
def __setattr__(self, key, val):
3191
log.debug(f'LogicState updated: {key} {getattr(self, key)} -> {val}')
@@ -38,18 +98,22 @@ def __init__(self, main_app: 'MainWidget'):
3898
self.pio_process = None
3999

40100
self.main_app = main_app
41-
main_app.wBtn_download_fw.setDisabled(False)
101+
main_app.wBtn_download_fw.setEnabled(True)
42102
main_app.wBtn_download_fw.clicked.connect(self.spawn_worker_thread(self.download_and_extract_fw))
43103
main_app.wBtn_select_local_config.clicked.connect(self.open_local_config_file)
44-
main_app.wCombo_pio_env.currentIndexChanged.connect(self.pio_combo_box_changed)
104+
main_app.wCombo_pio_env.currentIndexChanged.connect(self.pio_env_combo_box_changed)
45105
main_app.wBtn_build_fw.clicked.connect(self.spawn_worker_thread(self.build_fw))
106+
main_app.wBtn_refresh_ports.clicked.connect(self.spawn_worker_thread(self.refresh_ports))
107+
main_app.wCombo_serial_port.currentIndexChanged.connect(self.serial_port_combo_box_changed)
46108
main_app.wBtn_upload_fw.clicked.connect(self.spawn_worker_thread(self.upload_fw))
47109

48110
self.threadpool = QThreadPool()
49111
self.threadpool.setMaxThreadCount(1) # Only one worker
50112

51113
# Manually spawn a worker to grab tags from GitHub
52114
self.spawn_worker_thread(self.get_fw_versions)()
115+
# Manually spawn a worker to refresh serial ports
116+
self.spawn_worker_thread(self.refresh_ports)()
53117

54118
def spawn_worker_thread(self, fn):
55119
@Slot()
@@ -82,14 +146,18 @@ def worker_finished(self, worker_name: Optional[str] = None):
82146
self.main_app.wMsg_config_path.setText(f'Local configuration file:\n{self.logic_state.config_file_path}')
83147

84148
# check requirements to unlock the build button
85-
build_reqs = [
86-
self.logic_state.config_file_path,
87-
self.logic_state.pio_env,
88-
]
89-
if all(r is not None for r in build_reqs):
90-
self.main_app.wBtn_build_fw.setDisabled(False)
91-
else:
92-
self.main_app.wBtn_build_fw.setDisabled(True)
149+
build_reqs_ok = all([
150+
self.logic_state.config_file_path is not None,
151+
self.logic_state.pio_env is not None,
152+
])
153+
self.main_app.wBtn_build_fw.setEnabled(build_reqs_ok)
154+
155+
# check requirements to unlock the upload button
156+
upload_reqs_ok = all([
157+
self.logic_state.build_success == True,
158+
self.logic_state.upload_port is not None,
159+
])
160+
self.main_app.wBtn_upload_fw.setEnabled(upload_reqs_ok)
93161

94162
def get_fw_versions(self) -> str:
95163
fw_api_url = 'https://api.github.com/repos/OpenAstroTech/OpenAstroTracker-Firmware/releases'
@@ -112,16 +180,16 @@ def get_fw_versions_result(main_app: 'MainWidget', fw_versions_list: List[FWVers
112180
for fw_version in fw_versions_list:
113181
main_app.wCombo_fw_version.addItem(fw_version.nice_name)
114182
main_app.wCombo_fw_version.setCurrentIndex(0)
115-
main_app.wBtn_download_fw.setDisabled(False)
183+
main_app.wBtn_download_fw.setEnabled(True)
116184

117185
def download_and_extract_fw(self) -> str:
118186
self.main_app.wSpn_download.setState(BusyIndicatorState.BUSY)
119187
fw_idx = self.main_app.wCombo_fw_version.currentIndex()
120188
zip_url = self.logic_state.release_list[fw_idx].url
121-
zipfile_name = self.download_fw(zip_url)
189+
zipfile_name = download_fw(zip_url)
122190

123-
self.logic_state.fw_dir = self.extract_fw(zipfile_name)
124-
self.logic_state.pio_envs = self.get_pio_environments(self.logic_state.fw_dir)
191+
self.logic_state.fw_dir = extract_fw(zipfile_name)
192+
self.logic_state.pio_envs = get_pio_environments(self.logic_state.fw_dir)
125193
return self.download_and_extract_fw.__name__
126194

127195
@staticmethod
@@ -133,68 +201,14 @@ def download_and_extract_fw_result(main_app: 'MainWidget', pio_environments: Lis
133201
main_app.wCombo_pio_env.addItem(pio_env_name.nice_name)
134202
main_app.wCombo_pio_env.setPlaceholderText('Select Board')
135203

136-
@staticmethod
137-
def download_fw(zip_url: str) -> str:
138-
log.info(f'Downloading OAT FW from: {zip_url}')
139-
resp = requests.get(zip_url)
140-
zipfile_name = 'OATFW.zip'
141-
with open(zipfile_name, 'wb') as fd:
142-
fd.write(resp.content)
143-
fd.close()
144-
return zipfile_name
145-
146-
@staticmethod
147-
def extract_fw(zipfile_name: str) -> str:
148-
log.info(f'Extracting FW from {zipfile_name}')
149-
with zipfile.ZipFile(zipfile_name, 'r') as zip_ref:
150-
zip_infolist = zip_ref.infolist()
151-
if len(zip_infolist) > 0 and zip_infolist[0].is_dir():
152-
fw_dir = zip_infolist[0].filename
153-
else:
154-
log.fatal(f'Could not find FW top level directory in {zip_infolist}!')
155-
sys.exit(1)
156-
zip_ref.extractall()
157-
log.info(f'Extracted FW to {fw_dir}')
158-
return fw_dir
159-
160-
@staticmethod
161-
def get_pio_environments(fw_dir: str) -> List[PioEnv]:
162-
ini_path = Path(fw_dir, 'platformio.ini')
163-
with open(ini_path.resolve(), 'r') as fp:
164-
ini_lines = fp.readlines()
165-
environment_lines = [ini_line for ini_line in ini_lines if ini_line.startswith('[env:')]
166-
raw_pio_envs = []
167-
for environment_line in environment_lines:
168-
match = re.search(r'\[env:(.+)\]', environment_line)
169-
if match:
170-
raw_pio_envs.append(match.group(1))
171-
log.info(f'Found pio environments: {raw_pio_envs}')
172-
173-
# we don't want to build native
174-
if 'native' in raw_pio_envs:
175-
raw_pio_envs.remove('native')
176-
nice_name_lookup = {
177-
'ramps': 'RAMPS',
178-
'esp32': 'ESP32',
179-
'mksgenlv21': 'MKS Gen L v2.1',
180-
'mksgenlv2': 'MKS Gen L v2',
181-
'mksgenlv1': 'MKS Gen L v1',
182-
}
183-
pio_environments = []
184-
for raw_env in raw_pio_envs:
185-
if raw_env in nice_name_lookup:
186-
pio_env = PioEnv(nice_name_lookup[raw_env], raw_env)
187-
else:
188-
pio_env = PioEnv(raw_env, raw_env)
189-
pio_environments.append(pio_env)
190-
return pio_environments
191-
192204
@Slot()
193-
def pio_combo_box_changed(self, idx: int):
194-
if self.logic_state.pio_envs is not None and idx != -1:
205+
def pio_env_combo_box_changed(self, idx: int):
206+
if self.logic_state.pio_envs and idx != -1:
195207
self.logic_state.pio_env = self.logic_state.pio_envs[idx].raw_name
196208
# manually update GUI
197209
self.worker_finished()
210+
else:
211+
self.logic_state.pio_env = None
198212

199213
@Slot()
200214
def open_local_config_file(self):
@@ -241,36 +255,79 @@ def build_fw(self):
241255
)
242256
self.pio_process.start()
243257

244-
def upload_fw(self):
245-
self.main_app.wSpn_upload.setState(BusyIndicatorState.BUSY)
258+
@Slot()
259+
def pio_build_finished(self):
260+
log.info(f'platformio build finished')
261+
exit_state = self.pio_process.qproc.exitCode()
262+
if exit_state == QProcess.NormalExit:
263+
log.info('Normal exit')
264+
self.main_app.wSpn_build.setState(BusyIndicatorState.GOOD)
265+
self.logic_state.build_success = True
266+
else:
267+
log.error('Did not exit normally')
268+
self.main_app.wSpn_build.setState(BusyIndicatorState.BAD)
269+
self.pio_process = None
270+
271+
def refresh_ports(self):
246272
if self.pio_process is not None:
247273
log.error(f'platformio already running! {self.pio_process}')
248274
return
249275

250276
self.pio_process = ExternalProcess(
251277
'platformio',
252-
['run',
253-
'--environment', self.logic_state.pio_env,
254-
'--project-dir', self.logic_state.fw_dir,
255-
'--verbose',
256-
'--target', 'upload'
257-
],
258-
self.pio_upload_finished,
278+
['device', 'list', '--serial', '--json-output'],
279+
self.pio_refresh_ports_finished,
259280
)
260281
self.pio_process.start()
261282

262283
@Slot()
263-
def pio_build_finished(self):
264-
log.info(f'platformio build finished')
284+
def pio_refresh_ports_finished(self):
285+
log.info(f'platformio refresh ports finished')
265286
exit_state = self.pio_process.qproc.exitCode()
266287
if exit_state == QProcess.NormalExit:
267288
log.info('Normal exit')
268-
self.main_app.wSpn_build.setState(BusyIndicatorState.GOOD)
269-
self.main_app.wBtn_upload_fw.setDisabled(False)
270289
else:
271290
log.error('Did not exit normally')
272-
self.main_app.wSpn_build.setState(BusyIndicatorState.BAD)
291+
all_port_data = json.loads(self.pio_process.stdout_text)
273292
self.pio_process = None
293+
self.logic_state.serial_ports = [port_data['port'] for port_data in all_port_data]
294+
295+
self.main_app.wCombo_serial_port.clear()
296+
for serial_port in self.logic_state.serial_ports:
297+
self.main_app.wCombo_serial_port.addItem(serial_port)
298+
if len(self.logic_state.serial_ports) > 0:
299+
self.main_app.wCombo_serial_port.setCurrentIndex(0)
300+
else:
301+
self.logic_state.upload_port = None
302+
self.main_app.wCombo_serial_port.setCurrentIndex(-1)
303+
304+
@Slot()
305+
def serial_port_combo_box_changed(self, idx: int):
306+
if self.logic_state.serial_ports and idx != -1:
307+
self.logic_state.upload_port = self.logic_state.serial_ports[idx]
308+
# manually update GUI
309+
self.worker_finished()
310+
else:
311+
self.logic_state.upload_port = None
312+
313+
def upload_fw(self):
314+
self.main_app.wSpn_upload.setState(BusyIndicatorState.BUSY)
315+
if self.pio_process is not None:
316+
log.error(f'platformio already running! {self.pio_process}')
317+
return
318+
319+
self.pio_process = ExternalProcess(
320+
'platformio',
321+
['run',
322+
'--environment', self.logic_state.pio_env,
323+
'--project-dir', self.logic_state.fw_dir,
324+
'--verbose',
325+
'--target', 'upload',
326+
'--upload-port', self.logic_state.upload_port,
327+
],
328+
self.pio_upload_finished,
329+
)
330+
self.pio_process.start()
274331

275332
@Slot()
276333
def pio_upload_finished(self):
@@ -294,20 +351,23 @@ def __init__(self, log_object: LogObject):
294351
self.wCombo_fw_version = QComboBox()
295352
self.wCombo_fw_version.setPlaceholderText('Grabbing FW Versions...')
296353
self.wBtn_download_fw = QPushButton('Download')
297-
self.wBtn_download_fw.setDisabled(True)
354+
self.wBtn_download_fw.setEnabled(False)
298355
self.wSpn_download = QBusyIndicatorGoodBad(fixed_size=(50, 50))
299356

300357
self.wMsg_pio_env = QLabel('Select board:')
301358
self.wCombo_pio_env = QComboBox()
302359
self.wCombo_pio_env.setPlaceholderText('No FW downloaded yet...')
303360
self.wBtn_select_local_config = QPushButton('Select local config file')
304361
self.wBtn_build_fw = QPushButton('Build FW')
305-
self.wBtn_build_fw.setDisabled(True)
362+
self.wBtn_build_fw.setEnabled(False)
306363
self.wMsg_config_path = QLabel('No config file selected')
307364
self.wSpn_build = QBusyIndicatorGoodBad(fixed_size=(50, 50))
308365

366+
self.wBtn_refresh_ports = QPushButton('Refresh ports')
367+
self.wCombo_serial_port = QComboBox()
368+
self.wCombo_serial_port.setPlaceholderText('No port selected')
309369
self.wBtn_upload_fw = QPushButton('Upload FW')
310-
self.wBtn_upload_fw.setDisabled(True)
370+
self.wBtn_upload_fw.setEnabled(False)
311371
self.wSpn_upload = QBusyIndicatorGoodBad(fixed_size=(50, 50))
312372

313373
self.logText = QPlainTextEdit()
@@ -321,7 +381,7 @@ def __init__(self, log_object: LogObject):
321381
[self.wMsg_fw_version, self.wCombo_fw_version, self.wBtn_download_fw, self.wSpn_download],
322382
[self.wMsg_pio_env, self.wCombo_pio_env, self.wBtn_select_local_config, self.wBtn_build_fw],
323383
[self.wMsg_config_path, None, None, self.wSpn_build],
324-
[None, None, self.wBtn_upload_fw, self.wSpn_upload]
384+
[self.wBtn_refresh_ports, self.wCombo_serial_port, self.wBtn_upload_fw, self.wSpn_upload]
325385
]
326386
for y, row_arr in enumerate(layout_arr):
327387
for x, widget in enumerate(row_arr):

OATFWGUI/qt_extensions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def __init__(self, proc_name, proc_args, finish_signal):
5656
self.qproc.setProgram(self.proc_name)
5757
self.qproc.setArguments(self.proc_args)
5858

59+
self.stdout_text = ''
60+
self.stderr_text = ''
61+
5962
# signals
6063
self.qproc.readyReadStandardOutput.connect(self.handle_stdout)
6164
self.qproc.readyReadStandardError.connect(self.handle_stderr)
@@ -77,12 +80,14 @@ def start(self):
7780
def handle_stderr(self):
7881
data = self.qproc.readAllStandardError()
7982
stderr = bytes(data).decode("utf8")
83+
self.stderr_text += stderr
8084
log.error(stderr)
8185

8286
@Slot()
8387
def handle_stdout(self):
8488
data = self.qproc.readAllStandardOutput()
8589
stdout = bytes(data).decode("utf8")
90+
self.stdout_text += stdout
8691
log.info(stdout)
8792

8893
@Slot()

0 commit comments

Comments
 (0)