From 3f9a0126fa095f13c2a5dcf7f2824a2f0b183bdf Mon Sep 17 00:00:00 2001 From: Hector van der Aa Date: Sun, 5 Apr 2026 22:33:38 +0200 Subject: [PATCH] Working serial connection and disconnection mechanic --- src/dataflux/app.py | 2 +- src/dataflux/callbacks/menu.py | 8 +++++ src/dataflux/callbacks/serial.py | 18 ++++++++++ src/dataflux/services/serial/__init__.py | 15 +++++++++ src/dataflux/tags.py | 6 ++++ src/dataflux/ui/colors.py | 12 +++++++ src/dataflux/ui/routines/__init__.py | 14 ++++++++ src/dataflux/ui/routines/status.py | 15 +++++++++ src/dataflux/ui/windows.py | 43 +++++++++++++++++++----- 9 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 src/dataflux/callbacks/serial.py create mode 100644 src/dataflux/ui/colors.py create mode 100644 src/dataflux/ui/routines/status.py diff --git a/src/dataflux/app.py b/src/dataflux/app.py index 3097a6f..a603b83 100644 --- a/src/dataflux/app.py +++ b/src/dataflux/app.py @@ -19,7 +19,7 @@ def run() -> None: app_font = dpg.add_font("./Inter-Regular.ttf", 18) dpg.bind_font(app_font) - dataflux.ui.windows.build_windows() + dataflux.ui.windows.build_windows(state) dpg.setup_dearpygui() dpg.show_viewport() diff --git a/src/dataflux/callbacks/menu.py b/src/dataflux/callbacks/menu.py index 1054e19..ac46dac 100644 --- a/src/dataflux/callbacks/menu.py +++ b/src/dataflux/callbacks/menu.py @@ -2,10 +2,18 @@ # Copyright (C) 2026 Association Exergie # SPDX-License-Identifier: GPL-3.0-or-later import dearpygui.dearpygui as dpg +from dataflux.ui.routines import update_global_connection_status import dataflux.ui.routines.windows +import dataflux.ui.routines.status +import dataflux.services.serial from dataflux.tags import WINDOW_CONNECTION_MENU def open_connection_window(sender, app_data, user_data) -> None: dataflux.ui.routines.windows.update_window_connection_menu_combo() dpg.show_item(WINDOW_CONNECTION_MENU) + +def menu_file_disconnect(sender, app_data, user_data) -> None: + dataflux.services.serial.disconnect_serial(user_data) + update_global_connection_status(user_data) + diff --git a/src/dataflux/callbacks/serial.py b/src/dataflux/callbacks/serial.py new file mode 100644 index 0000000..b122f9d --- /dev/null +++ b/src/dataflux/callbacks/serial.py @@ -0,0 +1,18 @@ +# Copyright (C) 2026 Hector van der Aa +# Copyright (C) 2026 Association Exergie +# SPDX-License-Identifier: GPL-3.0-or-later + +import dearpygui.dearpygui as dpg +import dataflux.services.serial +import dataflux.ui.routines + +from dataflux.state import AppState +from dataflux.tags import WINDOW_CONNECTION_MENU, WINDOW_CONNECTION_MENU_COMBO + + +def connection_window_connect_serial(sender, app_data, user_data: AppState) -> None: + device = dpg.get_value(WINDOW_CONNECTION_MENU_COMBO) + dataflux.services.serial.connect_serial(user_data, device) + dataflux.ui.routines.update_global_connection_status(user_data) + dpg.hide_item(WINDOW_CONNECTION_MENU) + diff --git a/src/dataflux/services/serial/__init__.py b/src/dataflux/services/serial/__init__.py index c455761..7e2f0b7 100644 --- a/src/dataflux/services/serial/__init__.py +++ b/src/dataflux/services/serial/__init__.py @@ -2,8 +2,11 @@ # Copyright (C) 2026 Association Exergie # SPDX-License-Identifier: GPL-3.0-or-later +from serial import Serial import serial.tools.list_ports +from dataflux.state import AppState + def list_serial_ports() -> list[str]: ports = serial.tools.list_ports.comports() @@ -14,5 +17,17 @@ def list_serial_ports() -> list[str]: return valid_ports +def connect_serial(state: AppState, device: str) -> None: + if state.serial_port is not None: + state.serial_port.close() + state.serial_port = None + + state.serial_port = Serial(port=device, baudrate=115200) + +def disconnect_serial(state: AppState) -> None: + if state.serial_port is not None: + state.serial_port.close() + state.serial_port = None + diff --git a/src/dataflux/tags.py b/src/dataflux/tags.py index 69c267b..7097382 100644 --- a/src/dataflux/tags.py +++ b/src/dataflux/tags.py @@ -6,3 +6,9 @@ MENU_FILE_CONNECT: str = "menu_file_connect" MENU_FILE_DISCONNECT: str = "menu_file_disconnect" WINDOW_CONNECTION_MENU: str = "window_connection_menu" WINDOW_CONNECTION_MENU_COMBO: str = "window_connection_menu_combo" + +STATUS_SERIAL_STATUS_BOX: str = "status_serial_status_box" +STATUS_SERIAL_STATUS_TEXT: str = "status_serial_status_text" + +THEME_STATUS_DISCONNECTED: str = "theme_status_disconnected" +THEME_STATUS_CONNECTED: str = "theme_status_connected" diff --git a/src/dataflux/ui/colors.py b/src/dataflux/ui/colors.py new file mode 100644 index 0000000..bebb68b --- /dev/null +++ b/src/dataflux/ui/colors.py @@ -0,0 +1,12 @@ +# Copyright (C) 2026 Hector van der Aa +# Copyright (C) 2026 Association Exergie +# SPDX-License-Identifier: GPL-3.0-or-later + +STATUS_RED_DARK = (140, 35, 35, 255) +STATUS_RED_BRIGHT = (255, 70, 70, 255) + +STATUS_ORANGE_DARK = (160, 90, 20, 255) +STATUS_ORANGE_BRIGHT= (255, 165, 40, 255) + +STATUS_GREEN_DARK = (40, 130, 55, 255) +STATUS_GREEN_BRIGHT = (70, 255, 110, 255) diff --git a/src/dataflux/ui/routines/__init__.py b/src/dataflux/ui/routines/__init__.py index e69de29..af36ce5 100644 --- a/src/dataflux/ui/routines/__init__.py +++ b/src/dataflux/ui/routines/__init__.py @@ -0,0 +1,14 @@ +# Copyright (C) 2026 Hector van der Aa +# Copyright (C) 2026 Association Exergie +# SPDX-License-Identifier: GPL-3.0-or-later + +import dearpygui.dearpygui as dpg +import dataflux.ui.routines.menu +import dataflux.ui.routines.status + +from dataflux.state import AppState + + +def update_global_connection_status(state: AppState): + dataflux.ui.routines.menu.update_menu_file_connection_status(state) + dataflux.ui.routines.status.update_status_connection_status(state) diff --git a/src/dataflux/ui/routines/status.py b/src/dataflux/ui/routines/status.py new file mode 100644 index 0000000..b4b6e66 --- /dev/null +++ b/src/dataflux/ui/routines/status.py @@ -0,0 +1,15 @@ +# Copyright (C) 2026 Hector van der Aa +# Copyright (C) 2026 Association Exergie +# SPDX-License-Identifier: GPL-3.0-or-later + +import dearpygui.dearpygui as dpg +from dataflux.state import AppState +from dataflux.tags import STATUS_SERIAL_STATUS_BOX, STATUS_SERIAL_STATUS_TEXT, THEME_STATUS_CONNECTED, THEME_STATUS_DISCONNECTED + +def update_status_connection_status(state: AppState): + if state.serial_port is None: + dpg.bind_item_theme(STATUS_SERIAL_STATUS_BOX, THEME_STATUS_DISCONNECTED) + dpg.set_value(STATUS_SERIAL_STATUS_TEXT, "Serial: Disconnected") + else: + dpg.bind_item_theme(STATUS_SERIAL_STATUS_BOX, THEME_STATUS_CONNECTED) + dpg.set_value(STATUS_SERIAL_STATUS_TEXT, "Serial: Connected") diff --git a/src/dataflux/ui/windows.py b/src/dataflux/ui/windows.py index a0e2cc4..bad3f7a 100644 --- a/src/dataflux/ui/windows.py +++ b/src/dataflux/ui/windows.py @@ -4,26 +4,26 @@ import dearpygui.dearpygui as dpg import dataflux.callbacks.menu +import dataflux.callbacks.serial -from dataflux.tags import MENU_FILE_CONNECT, MENU_FILE_DISCONNECT, WINDOW_CONNECTION_MENU, WINDOW_CONNECTION_MENU_COMBO +from dataflux.state import AppState +from dataflux.tags import MENU_FILE_CONNECT, MENU_FILE_DISCONNECT, STATUS_SERIAL_STATUS_BOX, STATUS_SERIAL_STATUS_TEXT, THEME_STATUS_CONNECTED, THEME_STATUS_DISCONNECTED, WINDOW_CONNECTION_MENU, WINDOW_CONNECTION_MENU_COMBO +from dataflux.ui.colors import STATUS_GREEN_DARK, STATUS_RED_DARK - -def build_windows() -> None: +def build_windows(state: AppState) -> None: with dpg.window(label='DataFlux',tag="main_window", no_collapse=True): with dpg.menu_bar(): with dpg.menu(label='File'): dpg.add_menu_item(label="Connect", enabled=True, tag=MENU_FILE_CONNECT, callback=dataflux.callbacks.menu.open_connection_window) - dpg.add_menu_item(label="Disonnect", enabled=False, tag=MENU_FILE_DISCONNECT) + dpg.add_menu_item(label="Disonnect", enabled=False, tag=MENU_FILE_DISCONNECT, callback=dataflux.callbacks.menu.menu_file_disconnect, user_data=state) dpg.add_menu_item(label="Quit") with dpg.menu(label='Window'): dpg.add_menu_item(label="Live Data", user_data="page_live_data") dpg.add_menu_item(label="Lap Recap", user_data="page_lap_recap") - with dpg.child_window(tag="content_area", autosize_x=True, autosize_y=True, border=False): + with dpg.child_window(tag="content_area", autosize_x=True, height=-32, border=False): with dpg.group(tag="page_live_data", show=True): - dpg.add_text("Live Data") - dpg.add_separator() with dpg.group(horizontal=True): with dpg.child_window(tag="realtime_stats", width=250, autosize_y=True, border=True): dpg.add_text("Speed: 25kmh") @@ -40,6 +40,33 @@ def build_windows() -> None: dpg.add_text("Lap Recap") dpg.add_separator() + with dpg.theme(tag=THEME_STATUS_CONNECTED): + with dpg.theme_component(dpg.mvChildWindow): + dpg.add_theme_color(dpg.mvThemeCol_ChildBg, STATUS_GREEN_DARK) + + with dpg.theme(tag=THEME_STATUS_DISCONNECTED): + with dpg.theme_component(dpg.mvChildWindow): + dpg.add_theme_color(dpg.mvThemeCol_ChildBg, STATUS_RED_DARK) + + with dpg.child_window(tag="footer_bar", autosize_x=True, height=28, border=False, no_scrollbar=True): + with dpg.group(horizontal=True): + with dpg.child_window(width=200, height=28, border=False, tag=STATUS_SERIAL_STATUS_BOX): + with dpg.table(header_row=False, resizable=False, policy=dpg.mvTable_SizingStretchProp, borders_innerV=False, borders_innerH=False, borders_outerH=False, borders_outerV=False, no_host_extendX=False, no_pad_innerX=True): + dpg.add_table_column(init_width_or_weight=1.0) + dpg.add_table_column(width_fixed=True) + dpg.add_table_column(init_width_or_weight=1.0) + with dpg.table_row(): + with dpg.table_cell(): + pass + + with dpg.table_cell(): + dpg.add_text("Serial: Disconnected", tag=STATUS_SERIAL_STATUS_TEXT) + + with dpg.table_cell(): + pass + + dpg.bind_item_theme(STATUS_SERIAL_STATUS_BOX, THEME_STATUS_DISCONNECTED) + with dpg.window(label="Connection Menu", tag=WINDOW_CONNECTION_MENU, show=False, modal=True, no_collapse=True, width=300): dpg.add_combo([], tag=WINDOW_CONNECTION_MENU_COMBO) - dpg.add_button(label="Connect") + dpg.add_button(label="Connect", callback=dataflux.callbacks.serial.connection_window_connect_serial, user_data=state)