From e5fd5cda895b1d3dfed2afc0283fac90a6f5792a Mon Sep 17 00:00:00 2001 From: Hector van der Aa Date: Sat, 16 May 2026 13:31:02 +0200 Subject: [PATCH] Working serial command sender --- src/dataflux/callbacks/serial.py | 10 ++++++++++ src/dataflux/services/serial/__init__.py | 18 ++++++++++++++---- src/dataflux/state.py | 1 + src/dataflux/tags.py | 6 ++++++ src/dataflux/ui/routines/serial.py | 12 +++++++++++- src/dataflux/ui/windows.py | 14 ++++++++++++-- 6 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/dataflux/callbacks/serial.py b/src/dataflux/callbacks/serial.py index 138d0c0..2fd4f2d 100644 --- a/src/dataflux/callbacks/serial.py +++ b/src/dataflux/callbacks/serial.py @@ -8,6 +8,9 @@ import dataflux.ui.routines from dataflux.state import AppState from dataflux.tags import ( + BUTTON_SERIAL_CONSOLE_SEND, + INPUT_SERIAL_CONSOLE, + TEXT_SERIAL_CONSOLE, WINDOW_LORA_CONNECTION_MENU, WINDOW_LORA_CONNECTION_MENU_COMBO, WINDOW_SERIAL_CONNECTION_MENU, @@ -27,3 +30,10 @@ def connection_window_connect_serial(sender, app_data, user_data: AppState) -> N dataflux.services.serial.connect_serial(user_data, device) dataflux.ui.routines.update_global_connection_status(user_data) dpg.hide_item(WINDOW_SERIAL_CONNECTION_MENU) + + +def serial_console_button_send(sender, app_data, user_data: AppState) -> None: + text = dpg.get_value(INPUT_SERIAL_CONSOLE) + dpg.set_value(INPUT_SERIAL_CONSOLE, "") + user_data.serial_send_queue.put(text) + print("Put into send queue: " + text) diff --git a/src/dataflux/services/serial/__init__.py b/src/dataflux/services/serial/__init__.py index 6b1ba3d..80b2263 100644 --- a/src/dataflux/services/serial/__init__.py +++ b/src/dataflux/services/serial/__init__.py @@ -51,10 +51,10 @@ def connect_serial(state: AppState, device: str) -> None: state.serial_port.close() state.serial_port = None - state.serial_port = Serial(port=device, baudrate=115200) - state.serial_thread = Thread( - target=serial_reader_worker, args=(state,), daemon=True + state.serial_port = Serial( + port=device, baudrate=115200, timeout=0.05, write_timeout=0.1 ) + state.serial_thread = Thread(target=serial_worker, args=(state,), daemon=True) state.serial_status_thread = Thread( target=serial_status_worker, args=(state,), daemon=True ) @@ -87,7 +87,7 @@ def lora_status_worker(state: AppState) -> None: dataflux.ui.routines.status.flash_status_connection_status(duration) -def serial_reader_worker(state: AppState) -> None: +def serial_worker(state: AppState) -> None: while state.serial_thread_running: port = state.serial_port if port is None: @@ -103,6 +103,16 @@ def serial_reader_worker(state: AppState) -> None: if line: text = line.decode("utf-8", errors="replace") state.serial_data_queue.put(text) + + if port.writable(): + try: + data: str = state.serial_send_queue.get_nowait() + except Empty: + pass + else: + state.serial_data_queue.put(data + "\n") + port.write(data.encode("utf-8")) + print("Wrote data: " + data) disconnect_serial(state) dataflux.ui.routines.update_global_connection_status(state) diff --git a/src/dataflux/state.py b/src/dataflux/state.py index 3b7fc53..4012b71 100644 --- a/src/dataflux/state.py +++ b/src/dataflux/state.py @@ -29,6 +29,7 @@ class AppState: serial_port: Serial | None = None serial_thread: Thread | None = None serial_data_queue: Queue | None = field(default_factory=Queue) + serial_send_queue: Queue | None = field(default_factory=Queue) serial_thread_running: bool = False telemetry_thread: Thread | None = None diff --git a/src/dataflux/tags.py b/src/dataflux/tags.py index 10c8eba..43397ce 100644 --- a/src/dataflux/tags.py +++ b/src/dataflux/tags.py @@ -13,6 +13,8 @@ WINDOW_LORA_CONNECTION_MENU_COMBO: str = "window_lora_connection_menu_combo" WINDOW_SERIAL_CONNECTION_MENU_COMBO: str = "window_serial_connection_menu_combo" WINDOW_FILE_DIALOG_DUMP_BUFFERS: str = "window_file_dialog_dump_buffers" +CHILD_WINDOW_SERIAL_CONSOLE: str = "child_window_serial_console" + STATUS_LORA_STATUS_BOX: str = "status_lora_status_box" STATUS_LORA_STATUS_TEXT: str = "status_lora_status_text" STATUS_SERIAL_STATUS_BOX: str = "status_serial_status_box" @@ -30,6 +32,10 @@ PAGE_SERIAL_CONSOLE: str = "page_serial_console" TEXT_SERIAL_CONSOLE: str = "text_serial_console" +BUTTON_SERIAL_CONSOLE_SEND: str = "button_serial_console_send" + +INPUT_SERIAL_CONSOLE: str = "input_serial_console" + SUB_PAGE_DATA_GRAPHS: str = "sub_page_data_graphs" SUB_PAGE_MAP: str = "sub_page_map" diff --git a/src/dataflux/ui/routines/serial.py b/src/dataflux/ui/routines/serial.py index de26670..08b0b86 100644 --- a/src/dataflux/ui/routines/serial.py +++ b/src/dataflux/ui/routines/serial.py @@ -4,9 +4,19 @@ import dearpygui.dearpygui as dpg -from dataflux.tags import TEXT_SERIAL_CONSOLE +from dataflux.tags import CHILD_WINDOW_SERIAL_CONSOLE, TEXT_SERIAL_CONSOLE def append_text_to_console(text: str) -> None: old = dpg.get_value(TEXT_SERIAL_CONSOLE) dpg.set_value(TEXT_SERIAL_CONSOLE, old + text) + + def scroll_to_bottom() -> None: + dpg.set_y_scroll( + CHILD_WINDOW_SERIAL_CONSOLE, + dpg.get_y_scroll_max(CHILD_WINDOW_SERIAL_CONSOLE), + ) + + frame = dpg.get_frame_count() + dpg.set_frame_callback(frame + 1, scroll_to_bottom) + dpg.set_frame_callback(frame + 2, scroll_to_bottom) diff --git a/src/dataflux/ui/windows.py b/src/dataflux/ui/windows.py index 786deee..0afffeb 100644 --- a/src/dataflux/ui/windows.py +++ b/src/dataflux/ui/windows.py @@ -8,6 +8,8 @@ import dataflux.callbacks.serial from dataflux.state import AppState from dataflux.tags import ( + BUTTON_SERIAL_CONSOLE_SEND, + CHILD_WINDOW_SERIAL_CONSOLE, GRAPH_SERIES_SPEED, GRAPH_SERIES_TENG, GRAPH_SERIES_VBAT, @@ -17,6 +19,7 @@ from dataflux.tags import ( GRAPH_Y_AXIS_SPEED, GRAPH_Y_AXIS_TENG, GRAPH_Y_AXIS_VBAT, + INPUT_SERIAL_CONSOLE, LIVE_DATA_TENG_VALUE, LIVE_DATA_UTC_TIME_VALUE, LIVE_DATA_VBAT_VALUE, @@ -286,6 +289,7 @@ def build_windows(state: AppState) -> None: with dpg.group(tag=PAGE_SERIAL_CONSOLE, show=False): with dpg.child_window( + tag=CHILD_WINDOW_SERIAL_CONSOLE, width=-1, height=-40, border=True, @@ -293,8 +297,14 @@ def build_windows(state: AppState) -> None: ): dpg.add_text(tag=TEXT_SERIAL_CONSOLE, wrap=0) with dpg.group(horizontal=True): - dpg.add_input_text(width=-100) - dpg.add_button(label="Send", width=100) + dpg.add_input_text(tag=INPUT_SERIAL_CONSOLE, width=-100) + dpg.add_button( + tag=BUTTON_SERIAL_CONSOLE_SEND, + label="Send", + width=100, + callback=dataflux.callbacks.serial.serial_console_button_send, + user_data=state, + ) with dpg.theme(tag=THEME_STATUS_CONNECTED): with dpg.theme_component(dpg.mvChildWindow):