Skip to content

Commit 4d8189a

Browse files
committed
implement plugin system and new read serial feature
1 parent e48cd8f commit 4d8189a

11 files changed

+126
-60
lines changed

config/application_configuration.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# application settings
22
TITLE: str = 'MicroPython Firmware Studio'
33
LOG_LEVEL: str = 'INFO'
4-
SERIAL_RATE: int = 115200
54

65
# application colors
76
APPEARANCE_MODE: str = 'dark'
@@ -18,3 +17,7 @@
1817

1918
# images
2019
RELOAD_ICON: str = 'img/reload.png'
20+
21+
# plugin
22+
SERIAL_RATE: int = 115200
23+
SERIAL_SECONDS: int = 5

serial_plugin/serial_command_runner.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class SerialCommandRunner:
1515
serial ports, such as version and file structure data.
1616
"""
1717

18-
1918
@staticmethod
2019
def _run_in_thread(worker: Callable[[], str], callback: Callable[[str], None]) -> None:
2120
"""
@@ -42,6 +41,14 @@ def task() -> None:
4241

4342
@staticmethod
4443
def _run_monitor(port: str) -> str:
44+
"""
45+
Executes a monitor utility for a given port and retrieves debug information.
46+
47+
:param port: The serial port to connect to.
48+
:type port: str
49+
:return: The debug information as a string.
50+
:rtype: str
51+
"""
4552
with Debug(port=port) as monitor:
4653
return monitor.get_debug()
4754

@@ -72,6 +79,16 @@ def _get_structure(port: str) -> str:
7279
return structure_fetcher.get_tree()
7380

7481
def get_debug(self, port: str, callback: Callable[[str], None]) -> None:
82+
"""
83+
Invokes a debug process in a separate thread. The function runs a monitoring
84+
process for the provided port and applies the specified callback upon completion.
85+
86+
:param port: The serial port to connect to.
87+
:type port: str
88+
:param callback: The function to be executed with the result.
89+
:type callback: Callable[[str], None]
90+
:return: None
91+
"""
7592
self._run_in_thread(lambda: self._run_monitor(port), callback)
7693

7794
def get_version(self, port: str, callback: Callable[[str], None]) -> None:

serial_plugin/serial_monitor.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
from logging import getLogger, debug
1+
from logging import getLogger, debug, error
2+
from time import time
23
from .serial_base import SerialBase
4+
from config.application_configuration import SERIAL_SECONDS
35

46

57
logger = getLogger(__name__)
@@ -8,13 +10,32 @@
810
class Debug(SerialBase):
911
"""
1012
Represents a utility for interacting with a device to fetch the
11-
current console over a serial connection.
13+
current console output over a serial connection for specific time.
1214
"""
13-
pass
1415

15-
def get_debug(self) -> str:
16-
while True:
17-
line = self._ser.readline().decode('utf-8', errors='ignore').strip()
16+
def get_debug(self, seconds: int = SERIAL_SECONDS) -> str:
17+
"""
18+
Retrieves debug information from a serial connection over a fixed period.
1819
19-
if line:
20-
debug(line)
20+
:param seconds: The number of seconds to wait for debug information.
21+
:type seconds: int
22+
:return: The debug information as a string.
23+
:rtype: str
24+
"""
25+
debug(f"read debug for {seconds} sec")
26+
output = []
27+
start_time = time()
28+
end_time = start_time + seconds
29+
30+
while time() < end_time:
31+
try:
32+
if self._ser.in_waiting:
33+
line = self._ser.readline().decode('utf-8', errors='ignore').strip()
34+
if line:
35+
debug(line)
36+
output.append(line)
37+
except Exception as e:
38+
error(e)
39+
break
40+
41+
return "\n".join(output)

ui/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
__all__ = ["BaseUI",
1212
"CommandRunner",
13-
"FrameConsole",
13+
"FrameConsole",
1414
"FrameSearchDevice",
1515
"FrameDeviceInformation",
1616
"FrameEraseDevice",

ui/firmware_studio.py

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
from serial.tools import list_ports
33
from logging import getLogger, debug, info, error
44
from os.path import expanduser
5+
from customtkinter import CTkButton, CTkFrame
56
from tkinter import filedialog, Event
67
from webbrowser import open_new
7-
from typing import Optional, Callable
88
from queue import Queue, Empty
9+
from typing import Optional, Callable, Tuple
910
from ui.base_ui import BaseUI
1011
from ui.frame_device_information import FrameDeviceInformation
1112
from ui.frame_erase_device import FrameEraseDevice
@@ -16,8 +17,6 @@
1617
from esptool_plugin.esptool_command_runner import CommandRunner
1718
from serial_plugin.serial_command_runner import SerialCommandRunner
1819
from config.device_configuration import BAUDRATE_OPTIONS, DEFAULT_URL, CONFIGURED_DEVICES
19-
from config.application_configuration import (FONT_PATH, FONT_CATEGORY, FONT_DESCRIPTION, CONSOLE_INFO,
20-
CONSOLE_COMMAND, CONSOLE_ERROR)
2120

2221

2322
logger = getLogger(__name__)
@@ -55,74 +54,62 @@ def __init__(self):
5554
debug('Adding frames to UI and configuring elements')
5655
# Search Device
5756
self.search_device = FrameSearchDevice(self)
58-
self.search_device.label.configure(font=FONT_PATH)
5957
self.search_device.reload_btn.configure(command=self._search_devices)
6058
self.search_device.device_option.configure(command=self._set_device)
6159

6260
# Device Information
6361
self.information = FrameDeviceInformation(self)
64-
self.information.label.configure(font=FONT_CATEGORY)
62+
6563
self.information.chip_info_btn.configure(command=lambda: self._esptool_command("chip_id"))
6664
self.information.memory_info_btn.configure(command=lambda: self._esptool_command("flash_id"))
6765
self.information.mac_info_btn.configure(command=lambda: self._esptool_command("read_mac"))
68-
self.information.mac_info_btn.pack_forget()
6966
self.information.flash_status_btn.configure(command=lambda: self._esptool_command("read_flash_status"))
67+
68+
self.information.mac_info_btn.pack_forget()
7069
self.information.flash_status_btn.pack_forget()
7170

7271
# PlugIns
7372
self.plugins = FramePlugIns(self)
74-
self.plugins.label.configure(font=FONT_CATEGORY)
75-
# self.plugins.mp_debug_btn.configure(command=self._debug_device)
73+
74+
self.plugins.mp_debug_btn.configure(command=self._handler_toplevel_serial_debug)
7675
self.plugins.mp_version_btn.configure(command=self._get_version)
77-
self.plugins.mp_version_btn.pack_forget()
7876
self.plugins.mp_structure_btn.configure(command=self._get_structure)
77+
78+
self.plugins.mp_version_btn.pack_forget()
7979
self.plugins.mp_structure_btn.pack_forget()
8080

8181
# Erase Device
8282
self.erase_device = FrameEraseDevice(self)
83-
self.erase_device.label.configure(font=FONT_CATEGORY)
8483
self.erase_device.erase_btn.configure(command=lambda: self._esptool_command("erase_flash"))
8584

8685
# Flash Firmware
8786
self.flash_firmware = FrameFirmwareFlash(self)
88-
self.flash_firmware.label.configure(font=FONT_CATEGORY)
87+
8988
self.flash_firmware.expert_mode.configure(command=self.toggle_expert_mode)
90-
self.flash_firmware.chip_option.set("Select Chip")
9189
self.flash_firmware.chip_option.configure(command=self._set_chip)
92-
self.flash_firmware.chip_info.configure(font=FONT_DESCRIPTION)
9390
self.flash_firmware.firmware_btn.configure(command=self._handle_firmware_selection)
94-
self.flash_firmware.link_label.configure(font=(*FONT_DESCRIPTION, "underline"))
9591
self.flash_firmware.link_label.bind("<Button-1>", self.open_url)
96-
self.flash_firmware.firmware_info.configure(font=FONT_DESCRIPTION)
9792
self.flash_firmware.baudrate_option.set(str(self.__selected_baudrate))
9893
self.flash_firmware.baudrate_option.configure(command=self._set_baudrate)
9994
self.flash_firmware.baudrate_checkbox.select()
100-
self.flash_firmware.baudrate_info.configure(font=FONT_DESCRIPTION)
10195
self.flash_firmware.sector_input.bind("<KeyRelease>", self._handle_sector_input)
102-
self.flash_firmware.sector_info.configure(font=FONT_DESCRIPTION)
96+
self.flash_firmware.flash_btn.configure(command=self._flash_firmware_command)
97+
10398
self.flash_firmware.flash_mode_label.grid_remove()
104-
self.flash_firmware.flash_mode_option.set("keep")
10599
self.flash_firmware.flash_mode_option.grid_remove()
106100
self.flash_firmware.flash_mode_info.grid_remove()
107101
self.flash_firmware.flash_frequency_label.grid_remove()
108-
self.flash_firmware.flash_frequency_option.set("keep")
109102
self.flash_firmware.flash_frequency_option.grid_remove()
110103
self.flash_firmware.flash_frequency_info.grid_remove()
111104
self.flash_firmware.flash_size_label.grid_remove()
112-
self.flash_firmware.flash_size_option.set("detect")
113105
self.flash_firmware.flash_size_option.grid_remove()
114106
self.flash_firmware.flash_size_info.grid_remove()
115107
self.flash_firmware.erase_before_label.grid_remove()
116108
self.flash_firmware.erase_before_switch.grid_remove()
117109
self.flash_firmware.erase_before_info.grid_remove()
118-
self.flash_firmware.flash_btn.configure(command=self._flash_firmware_command)
119110

120111
# Console
121112
self.console = FrameConsole(self)
122-
self.console.label.configure(font=FONT_CATEGORY)
123-
self.console.console_text.tag_config("info", foreground=CONSOLE_INFO)
124-
self.console.console_text.tag_config("normal", foreground=CONSOLE_COMMAND)
125-
self.console.console_text.tag_config("error", foreground=CONSOLE_ERROR)
126113
self.console.console_text.bind("<Key>", BaseUI._block_text_input)
127114

128115
debug('Searching for USB devices')
@@ -226,31 +213,35 @@ def _disable_buttons(self) -> None:
226213
227214
:return: None
228215
"""
229-
self.information.chip_info_btn.configure(state='disabled')
230-
self.information.memory_info_btn.configure(state='disabled')
231-
self.information.mac_info_btn.configure(state='disabled')
232-
self.information.flash_status_btn.configure(state='disabled')
233-
# self.plugins.mp_debug_btn.configure(state='disabled')
234-
self.plugins.mp_version_btn.configure(state='disabled')
235-
self.plugins.mp_structure_btn.configure(state='disabled')
236-
self.erase_device.erase_btn.configure(state='disabled')
237-
self.flash_firmware.flash_btn.configure(state='disabled')
216+
frames: Tuple[CTkFrame, ...] = (self.information, self.plugins, self.erase_device, self.flash_firmware)
217+
218+
buttons = [
219+
widget
220+
for frame in frames if isinstance(frame, CTkFrame)
221+
for widget in frame.winfo_children()
222+
if isinstance(widget, CTkButton)
223+
]
224+
225+
for button in buttons:
226+
button.configure(state='disabled')
238227

239228
def _enable_buttons(self) -> None:
240229
"""
241230
Enables specific UI buttons by changing their state to 'normal'.
242231
243232
:return: None
244233
"""
245-
self.information.chip_info_btn.configure(state='normal')
246-
self.information.memory_info_btn.configure(state='normal')
247-
self.information.mac_info_btn.configure(state='normal')
248-
self.information.flash_status_btn.configure(state='normal')
249-
# self.plugins.mp_debug_btn.configure(state='normal')
250-
self.plugins.mp_version_btn.configure(state='normal')
251-
self.plugins.mp_structure_btn.configure(state='normal')
252-
self.erase_device.erase_btn.configure(state='normal')
253-
self.flash_firmware.flash_btn.configure(state='normal')
234+
frames: Tuple[CTkFrame, ...] = (self.information, self.plugins, self.erase_device, self.flash_firmware)
235+
236+
buttons = [
237+
widget
238+
for frame in frames if isinstance(frame, CTkFrame)
239+
for widget in frame.winfo_children()
240+
if isinstance(widget, CTkButton)
241+
]
242+
243+
for button in buttons:
244+
button.configure(state='normal')
254245

255246
def _search_devices(self) -> None:
256247
"""
@@ -416,8 +407,19 @@ def _run_serial_task(self, info_text: str, command: Callable[[SerialCommandRunne
416407
runner = SerialCommandRunner()
417408
command(runner)
418409

419-
def _debug_device(self) -> None:
420-
pass
410+
def _handler_toplevel_serial_debug(self) -> None:
411+
"""
412+
Handles the creation or focus of a top-level debug window.
413+
414+
:return: None
415+
"""
416+
self._run_serial_task(
417+
info_text="Start Serial debugging",
418+
command=lambda runner: runner.get_debug(
419+
port=self.__device_path,
420+
callback=lambda output: self._handle_serial_output(output)
421+
)
422+
)
421423

422424
def _get_version(self) -> None:
423425
"""

ui/frame_console.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from logging import getLogger, debug
22
from customtkinter import CTkFrame, CTkLabel, CTkTextbox
3+
from config.application_configuration import FONT_CATEGORY
4+
from config.application_configuration import CONSOLE_INFO, CONSOLE_COMMAND, CONSOLE_ERROR
35

46

57
logger = getLogger(__name__)
@@ -24,6 +26,10 @@ def __init__(self, master, *args, **kwargs):
2426

2527
self.label = CTkLabel(self, text='Console Output')
2628
self.label.pack(padx=10, pady=10)
29+
self.label.configure(font=FONT_CATEGORY)
2730

2831
self.console_text = CTkTextbox(self, width=800, height=300)
2932
self.console_text.pack(padx=10, pady=10, fill="both", expand=True)
33+
self.console_text.tag_config("info", foreground=CONSOLE_INFO)
34+
self.console_text.tag_config("normal", foreground=CONSOLE_COMMAND)
35+
self.console_text.tag_config("error", foreground=CONSOLE_ERROR)

ui/frame_device_information.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from logging import getLogger, debug
22
from customtkinter import CTkFrame, CTkLabel, CTkButton
3+
from config.application_configuration import FONT_CATEGORY
34

45

56
logger = getLogger(__name__)
@@ -24,6 +25,7 @@ def __init__(self, master, *args, **kwargs):
2425

2526
self.label = CTkLabel(self, text='Information')
2627
self.label.pack(padx=10, pady=10)
28+
self.label.configure(font=FONT_CATEGORY)
2729

2830
self.chip_info_btn = CTkButton(self, text='Chip ID')
2931
self.chip_info_btn.pack(padx=10, pady=5)

ui/frame_erase_device.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from logging import getLogger, debug
22
from customtkinter import CTkFrame, CTkLabel, CTkButton
3+
from config.application_configuration import FONT_CATEGORY
34

45

56
logger = getLogger(__name__)
@@ -24,6 +25,7 @@ def __init__(self, master, *args, **kwargs):
2425

2526
self.label = CTkLabel(self, text='Erase')
2627
self.label.pack(padx=10, pady=10)
28+
self.label.configure(font=FONT_CATEGORY)
2729

2830
self.erase_btn = CTkButton(self, text='Erase Flash')
2931
self.erase_btn.pack(padx=10, pady=5)

0 commit comments

Comments
 (0)