From cc99d26f9e25e13857da02eabe6ba570f4c736e4 Mon Sep 17 00:00:00 2001 From: Hector van der Aa Date: Sat, 9 May 2026 12:05:01 +0200 Subject: [PATCH] Added Data menu --- src/dataflux/app.py | 35 +++-- src/dataflux/callbacks/menu.py | 14 +- src/dataflux/telemetry_common | 2 +- src/dataflux/ui/windows.py | 253 +++++++++++++++++++++++++++------ 4 files changed, 244 insertions(+), 60 deletions(-) diff --git a/src/dataflux/app.py b/src/dataflux/app.py index 7d082c2..f3dcf97 100644 --- a/src/dataflux/app.py +++ b/src/dataflux/app.py @@ -22,17 +22,20 @@ def _asset_path(relative_path: str) -> str: if bundle_dir is not None: candidates.append(Path(bundle_dir) / path) - candidates.extend(( - Path.cwd() / path, - Path(__file__).resolve().parents[2] / path, - )) + candidates.extend( + ( + Path.cwd() / path, + Path(__file__).resolve().parents[2] / path, + ) + ) for candidate in candidates: if candidate.exists(): return str(candidate) searched = ", ".join(str(candidate) for candidate in candidates) - raise FileNotFoundError(f"Missing asset {relative_path!r}. Searched: {searched}") + raise FileNotFoundError( + f"Missing asset {relative_path!r}. Searched: {searched}") def run() -> None: @@ -50,13 +53,16 @@ def run() -> None: dataflux.config.MAP_IMAGE_HEIGHT = height with dpg.texture_registry(show=False): - dpg.add_static_texture(width=width, height=height, default_value=data, tag="texture_tab") + dpg.add_static_texture( + width=width, height=height, default_value=data, tag="texture_tab" + ) - dpg.create_viewport(title='DataFlux', width=600, height=600) + dpg.create_viewport(title="DataFlux", width=600, height=600) # Add Inter font to registry and bind as main app font with dpg.font_registry(): - app_font = dpg.add_font(_asset_path("assets/fonts/Inter-Regular.ttf"), 18*2) + app_font = dpg.add_font(_asset_path( + "assets/fonts/Inter-Regular.ttf"), 18 * 2) dpg.bind_font(app_font) dataflux.ui.windows.build_windows(state) @@ -64,22 +70,23 @@ def run() -> None: dpg.setup_dearpygui() dpg.show_viewport() - vp_w = dpg.get_viewport_client_width() vp_h = dpg.get_viewport_client_height() dpg.configure_item("main_window", pos=(0, 0), width=vp_w, height=vp_h) dpg.set_primary_window("main_window", True) - state.ui_worker_thread = Thread(target=dataflux.ui.worker.ui_worker, args=(state,), daemon=True) + state.ui_worker_thread = Thread( + target=dataflux.ui.worker.ui_worker, args=(state,), daemon=True + ) state.ui_worker_thread.start() state.telemetry_thread_running = True - state.telemetry_thread = Thread(target=dataflux.services.telemetry.telemetry_worker, args=(state, ), daemon=True) + state.telemetry_thread = Thread( + target=dataflux.services.telemetry.telemetry_worker, args=( + state,), daemon=True + ) state.telemetry_thread.start() dpg.start_dearpygui() dpg.destroy_context() - - - diff --git a/src/dataflux/callbacks/menu.py b/src/dataflux/callbacks/menu.py index 984278c..7a6f9b5 100644 --- a/src/dataflux/callbacks/menu.py +++ b/src/dataflux/callbacks/menu.py @@ -13,21 +13,29 @@ import dataflux.services.telemetry from dataflux.tags import WINDOW_CONNECTION_MENU, WINDOW_FILE_DIALOG_DUMP_BUFFERS + def open_connection_window(sender, app_data, user_data) -> None: - dataflux.ui.routines.windows.update_window_connection_menu_combo() + 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) + def menu_file_dump_buffers(sender, app_data, user_data: AppState) -> None: dpg.show_item(WINDOW_FILE_DIALOG_DUMP_BUFFERS) + def window_file_dialog_dump_buffers_ok(sender, app_data, user_data: AppState) -> None: - user_data.buffer_dump_thread = Thread(target=dataflux.services.telemetry.buffer_dump, args=(user_data, app_data["file_path_name"]), daemon=True) + user_data.buffer_dump_thread = Thread( + target=dataflux.services.telemetry.buffer_dump, + args=(user_data, app_data["file_path_name"]), + daemon=True, + ) user_data.buffer_dump_thread.start() + def menu_window_select(sender, app_data, user_data: str) -> None: dataflux.ui.routines.windows.toggle_window(user_data) - diff --git a/src/dataflux/telemetry_common b/src/dataflux/telemetry_common index af57f8a..edc567c 120000 --- a/src/dataflux/telemetry_common +++ b/src/dataflux/telemetry_common @@ -1 +1 @@ -/Users/hector/Projects/Exergie/TelemetryCommon/python \ No newline at end of file +/home/hector/projects/Exergie/TelemetryCommon/python/ \ No newline at end of file diff --git a/src/dataflux/ui/windows.py b/src/dataflux/ui/windows.py index 5c29330..f950c29 100644 --- a/src/dataflux/ui/windows.py +++ b/src/dataflux/ui/windows.py @@ -7,9 +7,40 @@ import dataflux.callbacks.menu import dataflux.callbacks.serial from dataflux.state import AppState -from dataflux.tags import GRAPH_SERIES_SPEED, GRAPH_SERIES_TENG, GRAPH_SERIES_VBAT, GRAPH_X_AXIS_SPEED, GRAPH_X_AXIS_TENG, GRAPH_X_AXIS_VBAT, GRAPH_Y_AXIS_SPEED, GRAPH_Y_AXIS_TENG, GRAPH_Y_AXIS_VBAT, LIVE_DATA_TENG_VALUE, LIVE_DATA_UTC_TIME_VALUE, LIVE_DATA_VBAT_VALUE, LIVE_DATA_VEHICLE_TIME_VALUE, LIVE_DATA_SPEED_VALUE, MENU_FILE_CONNECT, MENU_FILE_DISCONNECT, MENU_FILE_DUMP_BUFFERS, PAGE_LAP_RECAP, PAGE_LIVE_DATA, STATUS_SERIAL_STATUS_BOX, STATUS_SERIAL_STATUS_TEXT, SUB_PAGE_DATA_GRAPHS, SUB_PAGE_MAP, THEME_STATUS_CONNECTED, THEME_STATUS_CONNECTED_BRIGHT, THEME_STATUS_DISCONNECTED, WINDOW_CONNECTION_MENU, WINDOW_CONNECTION_MENU_COMBO, WINDOW_FILE_DIALOG_DUMP_BUFFERS +from dataflux.tags import ( + GRAPH_SERIES_SPEED, + GRAPH_SERIES_TENG, + GRAPH_SERIES_VBAT, + GRAPH_X_AXIS_SPEED, + GRAPH_X_AXIS_TENG, + GRAPH_X_AXIS_VBAT, + GRAPH_Y_AXIS_SPEED, + GRAPH_Y_AXIS_TENG, + GRAPH_Y_AXIS_VBAT, + LIVE_DATA_TENG_VALUE, + LIVE_DATA_UTC_TIME_VALUE, + LIVE_DATA_VBAT_VALUE, + LIVE_DATA_VEHICLE_TIME_VALUE, + LIVE_DATA_SPEED_VALUE, + MENU_FILE_CONNECT, + MENU_FILE_DISCONNECT, + MENU_FILE_DUMP_BUFFERS, + PAGE_LAP_RECAP, + PAGE_LIVE_DATA, + STATUS_SERIAL_STATUS_BOX, + STATUS_SERIAL_STATUS_TEXT, + SUB_PAGE_DATA_GRAPHS, + SUB_PAGE_MAP, + THEME_STATUS_CONNECTED, + THEME_STATUS_CONNECTED_BRIGHT, + THEME_STATUS_DISCONNECTED, + WINDOW_CONNECTION_MENU, + WINDOW_CONNECTION_MENU_COMBO, + WINDOW_FILE_DIALOG_DUMP_BUFFERS, +) from dataflux.ui.colors import STATUS_GREEN_BRIGHT, STATUS_GREEN_DARK, STATUS_RED_DARK + def _add_live_data_row(label: str, value: str, tag: str, units: str) -> None: with dpg.table_row(): with dpg.table_cell(): @@ -19,62 +50,168 @@ def _add_live_data_row(label: str, value: str, tag: str, units: str) -> None: with dpg.table_cell(): dpg.add_text(units) + def build_windows(state: AppState) -> None: - - with dpg.window(label='DataFlux',tag="main_window", no_collapse=True): + + with dpg.window(label="DataFlux", tag="main_window", no_collapse=True): dpg.set_global_font_scale(0.5) 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, callback=dataflux.callbacks.menu.menu_file_disconnect, user_data=state) - dpg.add_menu_item(label="Dump Buffers", enabled=True, tag=MENU_FILE_DUMP_BUFFERS, callback=dataflux.callbacks.menu.menu_file_dump_buffers) + 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, + callback=dataflux.callbacks.menu.menu_file_disconnect, + user_data=state, + ) + dpg.add_menu_item( + label="Dump Buffers", + enabled=True, + tag=MENU_FILE_DUMP_BUFFERS, + callback=dataflux.callbacks.menu.menu_file_dump_buffers, + ) dpg.add_menu_item(label="Quit") - with dpg.menu(label='Window'): - dpg.add_menu_item(label="Live Graphs", user_data=SUB_PAGE_DATA_GRAPHS, callback=dataflux.callbacks.menu.menu_window_select) - dpg.add_menu_item(label="Live Map", user_data=SUB_PAGE_MAP, callback=dataflux.callbacks.menu.menu_window_select ) - dpg.add_menu_item(label="Lap Recap", user_data=PAGE_LAP_RECAP, callback=dataflux.callbacks.menu.menu_window_select) + with dpg.menu(label="Window"): + dpg.add_menu_item( + label="Live Graphs", + user_data=SUB_PAGE_DATA_GRAPHS, + callback=dataflux.callbacks.menu.menu_window_select, + ) + dpg.add_menu_item( + label="Live Map", + user_data=SUB_PAGE_MAP, + callback=dataflux.callbacks.menu.menu_window_select, + ) + dpg.add_menu_item( + label="Lap Recap", + user_data=PAGE_LAP_RECAP, + callback=dataflux.callbacks.menu.menu_window_select, + ) + with dpg.menu(label="Data"): + with dpg.menu(label="Timeframe"): + dpg.add_menu_item(label="30s") + dpg.add_menu_item(label="60s") - with dpg.child_window(tag="content_area", autosize_x=True, height=-32, 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): with dpg.group(horizontal=True): - with dpg.child_window(tag="realtime_stats", width=260, autosize_y=True, border=True): - with dpg.table(header_row=False, resizable=False, policy=dpg.mvTable_SizingFixedFit, borders_innerH=False, borders_innerV=False, borders_outerH=False, borders_outerV=False, no_host_extendX=True): + with dpg.child_window( + tag="realtime_stats", width=260, autosize_y=True, border=True + ): + with dpg.table( + header_row=False, + resizable=False, + policy=dpg.mvTable_SizingFixedFit, + borders_innerH=False, + borders_innerV=False, + borders_outerH=False, + borders_outerV=False, + no_host_extendX=True, + ): dpg.add_table_column(width_fixed=True) - dpg.add_table_column(width_stretch=True, init_width_or_weight=1.0) + dpg.add_table_column( + width_stretch=True, init_width_or_weight=1.0 + ) dpg.add_table_column(width_fixed=True) - _add_live_data_row("UTC Time", "no_data", LIVE_DATA_UTC_TIME_VALUE,"") - _add_live_data_row("Vehicle Time", "no_data", LIVE_DATA_VEHICLE_TIME_VALUE,"") - _add_live_data_row("Speed", "no_data", LIVE_DATA_SPEED_VALUE,"km/h") - _add_live_data_row("Battery Voltage", "no_data", LIVE_DATA_VBAT_VALUE,"V") - _add_live_data_row("Engine Temp", "no_data", LIVE_DATA_TENG_VALUE,"°C") + _add_live_data_row( + "UTC Time", "no_data", LIVE_DATA_UTC_TIME_VALUE, "" + ) + _add_live_data_row( + "Vehicle Time", + "no_data", + LIVE_DATA_VEHICLE_TIME_VALUE, + "", + ) + _add_live_data_row( + "Speed", "no_data", LIVE_DATA_SPEED_VALUE, "km/h" + ) + _add_live_data_row( + "Battery Voltage", "no_data", LIVE_DATA_VBAT_VALUE, "V" + ) + _add_live_data_row( + "Engine Temp", "no_data", LIVE_DATA_TENG_VALUE, "°C" + ) - with dpg.child_window(tag=SUB_PAGE_DATA_GRAPHS, autosize_x=True, autosize_y=True, border=True, show=True): - with dpg.plot(label="Speed", height=250, width=-1, no_inputs=True): + with dpg.child_window( + tag=SUB_PAGE_DATA_GRAPHS, + autosize_x=True, + autosize_y=True, + border=True, + show=True, + ): + with dpg.plot( + label="Speed", height=250, width=-1, no_inputs=True + ): dpg.add_plot_legend() - dpg.add_plot_axis(dpg.mvXAxis, label="Time", tag=GRAPH_X_AXIS_SPEED) - y_axis_speed = dpg.add_plot_axis(dpg.mvYAxis, label="Speed", tag=GRAPH_Y_AXIS_SPEED) + dpg.add_plot_axis( + dpg.mvXAxis, label="Time", tag=GRAPH_X_AXIS_SPEED + ) + y_axis_speed = dpg.add_plot_axis( + dpg.mvYAxis, label="Speed", tag=GRAPH_Y_AXIS_SPEED + ) dpg.set_axis_limits(GRAPH_Y_AXIS_SPEED, ymin=0, ymax=50) dpg.set_axis_limits(GRAPH_X_AXIS_SPEED, ymin=-30, ymax=0) - dpg.add_line_series([], [], parent=y_axis_speed, tag=GRAPH_SERIES_SPEED) - with dpg.plot(label="Battery Voltage", height=250, width=-1, no_inputs=True): + dpg.add_line_series( + [], [], parent=y_axis_speed, tag=GRAPH_SERIES_SPEED + ) + with dpg.plot( + label="Battery Voltage", + height=250, + width=-1, + no_inputs=True, + ): dpg.add_plot_legend() - dpg.add_plot_axis(dpg.mvXAxis, label="Time", tag=GRAPH_X_AXIS_VBAT) - y_axis_vbat = dpg.add_plot_axis(dpg.mvYAxis, label="Voltage", tag=GRAPH_Y_AXIS_VBAT) + dpg.add_plot_axis( + dpg.mvXAxis, label="Time", tag=GRAPH_X_AXIS_VBAT + ) + y_axis_vbat = dpg.add_plot_axis( + dpg.mvYAxis, label="Voltage", tag=GRAPH_Y_AXIS_VBAT + ) dpg.set_axis_limits(GRAPH_Y_AXIS_VBAT, ymin=0, ymax=20) dpg.set_axis_limits(GRAPH_X_AXIS_VBAT, ymin=-30, ymax=0) - dpg.add_line_series([], [], parent=y_axis_vbat, tag=GRAPH_SERIES_VBAT) - with dpg.plot(label="Engine Temp", height=250, width=-1, no_inputs=True): + dpg.add_line_series( + [], [], parent=y_axis_vbat, tag=GRAPH_SERIES_VBAT + ) + with dpg.plot( + label="Engine Temp", height=250, width=-1, no_inputs=True + ): dpg.add_plot_legend() - dpg.add_plot_axis(dpg.mvXAxis, label="Time", tag=GRAPH_X_AXIS_TENG) - y_axis_teng = dpg.add_plot_axis(dpg.mvYAxis, label="Temperature", tag=GRAPH_Y_AXIS_TENG) + dpg.add_plot_axis( + dpg.mvXAxis, label="Time", tag=GRAPH_X_AXIS_TENG + ) + y_axis_teng = dpg.add_plot_axis( + dpg.mvYAxis, label="Temperature", tag=GRAPH_Y_AXIS_TENG + ) dpg.set_axis_limits(GRAPH_Y_AXIS_TENG, ymin=0, ymax=120) dpg.set_axis_limits(GRAPH_X_AXIS_TENG, ymin=-30, ymax=0) - dpg.add_line_series([], [], parent=y_axis_teng, tag=GRAPH_SERIES_TENG) + dpg.add_line_series( + [], [], parent=y_axis_teng, tag=GRAPH_SERIES_TENG + ) - with dpg.child_window(tag=SUB_PAGE_MAP, autosize_x=True, autosize_y=True, border=True, show=False, no_scrollbar=True): + with dpg.child_window( + tag=SUB_PAGE_MAP, + autosize_x=True, + autosize_y=True, + border=True, + show=False, + no_scrollbar=True, + ): with dpg.drawlist(width=500, height=500, tag="map_drawlist"): dpg.draw_image("texture_tab", (0, 0), (500, 500)) - dpg.draw_circle((0, 0), 10, color=(255, 0, 0, 255), fill=(255, 0, 0, 255)) + dpg.draw_circle( + (0, 0), + 10, + color=(255, 0, 0, 255), + fill=(255, 0, 0, 255), + ) with dpg.group(tag=PAGE_LAP_RECAP, show=False): dpg.add_text("Lap Recap") @@ -92,10 +229,28 @@ def build_windows(state: AppState) -> None: 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.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): + 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) @@ -104,16 +259,30 @@ def build_windows(state: AppState) -> None: pass with dpg.table_cell(): - dpg.add_text("Serial: Disconnected", tag=STATUS_SERIAL_STATUS_TEXT) + 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): + 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", callback=dataflux.callbacks.serial.connection_window_connect_serial, user_data=state) + dpg.add_button( + label="Connect", + callback=dataflux.callbacks.serial.connection_window_connect_serial, + user_data=state, + ) with dpg.file_dialog( directory_selector=False, @@ -123,6 +292,6 @@ def build_windows(state: AppState) -> None: height=400, modal=True, callback=dataflux.callbacks.menu.window_file_dialog_dump_buffers_ok, - user_data=state + user_data=state, ): dpg.add_file_extension(".csv")