Added Data menu

This commit is contained in:
2026-05-09 12:05:01 +02:00
parent d4b9304d99
commit cc99d26f9e
4 changed files with 244 additions and 60 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -1 +1 @@
/Users/hector/Projects/Exergie/TelemetryCommon/python
/home/hector/projects/Exergie/TelemetryCommon/python/

View File

@@ -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")