Base map work

This commit is contained in:
2026-04-30 23:31:25 +02:00
parent 2d631f0669
commit f857a0a45c
9 changed files with 77 additions and 32 deletions

BIN
map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 MiB

View File

@@ -6,6 +6,7 @@ from threading import Thread
import dearpygui.dearpygui as dpg import dearpygui.dearpygui as dpg
from dataflux.state import AppState from dataflux.state import AppState
import dataflux.config
import dataflux.ui.windows import dataflux.ui.windows
import dataflux.ui.worker import dataflux.ui.worker
import dataflux.services.telemetry import dataflux.services.telemetry
@@ -15,6 +16,14 @@ def run() -> None:
# Create application context and viewport # Create application context and viewport
dpg.create_context() dpg.create_context()
width, height, channels, data = dpg.load_image("map.png")
dataflux.config.MAP_IMAGE_WIDTH = width
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.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 # Add Inter font to registry and bind as main app font
@@ -47,4 +56,3 @@ def run() -> None:

View File

@@ -27,3 +27,6 @@ def window_file_dialog_dump_buffers_ok(sender, app_data, user_data: AppState) ->
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() 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

@@ -0,0 +1,2 @@
MAP_IMAGE_WIDTH: int = 1
MAP_IMAGE_HEIGHT: int = 1

View File

@@ -3,18 +3,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from queue import Empty from queue import Empty
from threading import local
from dataflux.state import AppState, Buffers from dataflux.state import AppState, Buffers
import time import time
from pathlib import Path from pathlib import Path
import csv import csv
def hhmmsscc_to_day_seconds(value: int) -> int: LIVE_BUFFER_WINDOW_CS = 30 * 100
hours = value // 1000000
minutes = (value // 10000) % 100
seconds = (value // 100) % 100
return (hours * 3600) + (minutes * 60) + seconds
def telemetry_worker(state: AppState): def telemetry_worker(state: AppState):
while state.telemetry_thread_running: while state.telemetry_thread_running:
@@ -32,7 +26,7 @@ def telemetry_worker(state: AppState):
with state.lock: with state.lock:
state.raw_buffers.timestamp.append(hhmmsscc_to_day_seconds(dataframe["time_stamp"])) state.raw_buffers.timestamp.append(dataframe["time_stamp"])
state.raw_buffers.speed.append(dataframe["speed"]) state.raw_buffers.speed.append(dataframe["speed"])
state.raw_buffers.vbat.append(dataframe["vbat"]) state.raw_buffers.vbat.append(dataframe["vbat"])
state.raw_buffers.teng.append(dataframe["teng"]) state.raw_buffers.teng.append(dataframe["teng"])
@@ -51,12 +45,13 @@ def telemetry_worker(state: AppState):
return return
last_timestamp = state.raw_buffers.timestamp[-1] last_timestamp = state.raw_buffers.timestamp[-1]
cutoff = last_timestamp - 30 cutoff = last_timestamp - LIVE_BUFFER_WINDOW_CS
i = len(state.raw_buffers.timestamp) - 1 i = len(state.raw_buffers.timestamp) - 1
while i >= 0 and state.raw_buffers.timestamp[i] >= cutoff: while i >= 0 and state.raw_buffers.timestamp[i] >= cutoff:
state.live_buffers.timestamp.append(state.raw_buffers.timestamp[i] - last_timestamp) elapsed_seconds = (state.raw_buffers.timestamp[i] - last_timestamp) / 100.0
state.live_buffers.timestamp.append(elapsed_seconds)
state.live_buffers.speed.append(state.raw_buffers.speed[i]) state.live_buffers.speed.append(state.raw_buffers.speed[i])
state.live_buffers.vbat.append(state.raw_buffers.vbat[i]) state.live_buffers.vbat.append(state.raw_buffers.vbat[i])
state.live_buffers.teng.append(state.raw_buffers.teng[i]) state.live_buffers.teng.append(state.raw_buffers.teng[i])
@@ -72,7 +67,6 @@ def telemetry_worker(state: AppState):
state.live_buffers.lng.reverse() state.live_buffers.lng.reverse()
def buffer_dump(state: AppState, path: str): def buffer_dump(state: AppState, path: str):
print(path)
save_path = Path(path) save_path = Path(path)
if save_path.is_dir(): if save_path.is_dir():
save_path = save_path / "output.csv" save_path = save_path / "output.csv"
@@ -87,8 +81,6 @@ def buffer_dump(state: AppState, path: str):
lng=list(state.raw_buffers.lng), lng=list(state.raw_buffers.lng),
) )
print(local_raw_buffers.timestamp)
with save_path.open("w", newline="", encoding="utf-8") as f: with save_path.open("w", newline="", encoding="utf-8") as f:
writer = csv.writer(f) writer = csv.writer(f)
@@ -100,4 +92,3 @@ def buffer_dump(state: AppState, path: str):
state.buffer_dump_thread = None state.buffer_dump_thread = None

View File

@@ -18,6 +18,12 @@ LIVE_DATA_SPEED_VALUE: str = "live_data_speed_value"
LIVE_DATA_VBAT_VALUE: str = "live_data_vbat_value" LIVE_DATA_VBAT_VALUE: str = "live_data_vbat_value"
LIVE_DATA_TENG_VALUE: str = "live_data_teng_value" LIVE_DATA_TENG_VALUE: str = "live_data_teng_value"
PAGE_LIVE_DATA: str = "page_live_data"
PAGE_LAP_RECAP: str = "page_lap_recap"
SUB_PAGE_DATA_GRAPHS: str = "sub_page_data_graphs"
SUB_PAGE_MAP: str = "sub_page_map"
THEME_STATUS_DISCONNECTED: str = "theme_status_disconnected" THEME_STATUS_DISCONNECTED: str = "theme_status_disconnected"
THEME_STATUS_CONNECTED: str = "theme_status_connected" THEME_STATUS_CONNECTED: str = "theme_status_connected"
THEME_STATUS_CONNECTED_BRIGHT: str = "theme_status_connected_bigght" THEME_STATUS_CONNECTED_BRIGHT: str = "theme_status_connected_bigght"

View File

@@ -3,9 +3,34 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import dearpygui.dearpygui as dpg import dearpygui.dearpygui as dpg
import dataflux.config
from dataflux.services.serial import list_serial_ports from dataflux.services.serial import list_serial_ports
from dataflux.tags import WINDOW_CONNECTION_MENU_COMBO from dataflux.tags import PAGE_LAP_RECAP, PAGE_LIVE_DATA, SUB_PAGE_DATA_GRAPHS, SUB_PAGE_MAP, WINDOW_CONNECTION_MENU_COMBO
def update_window_connection_menu_combo() -> None: def update_window_connection_menu_combo() -> None:
ports: list[str] = list_serial_ports() ports: list[str] = list_serial_ports()
dpg.configure_item(WINDOW_CONNECTION_MENU_COMBO, items=ports) dpg.configure_item(WINDOW_CONNECTION_MENU_COMBO, items=ports)
def hide_all_but(tag: str) -> None:
arr = [PAGE_LIVE_DATA, PAGE_LAP_RECAP]
for item in arr:
if tag == item:
dpg.show_item(item)
else:
dpg.hide_item(item)
def toggle_window(tag: str) -> None:
if tag == SUB_PAGE_DATA_GRAPHS:
dpg.show_item(SUB_PAGE_DATA_GRAPHS)
dpg.hide_item(SUB_PAGE_MAP)
hide_all_but(PAGE_LIVE_DATA)
elif tag == SUB_PAGE_MAP:
dpg.show_item(SUB_PAGE_MAP)
dpg.hide_item(SUB_PAGE_DATA_GRAPHS)
hide_all_but(PAGE_LIVE_DATA)
else:
hide_all_but(tag)

View File

@@ -7,7 +7,7 @@ import dataflux.callbacks.menu
import dataflux.callbacks.serial import dataflux.callbacks.serial
from dataflux.state import AppState 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, STATUS_SERIAL_STATUS_BOX, STATUS_SERIAL_STATUS_TEXT, 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 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: def _add_live_data_row(label: str, value: str, tag: str, units: str) -> None:
@@ -29,11 +29,12 @@ def build_windows(state: AppState) -> None:
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="Dump Buffers", enabled=True, tag=MENU_FILE_DUMP_BUFFERS, callback=dataflux.callbacks.menu.menu_file_dump_buffers)
dpg.add_menu_item(label="Quit") dpg.add_menu_item(label="Quit")
with dpg.menu(label='Window'): with dpg.menu(label='Window'):
dpg.add_menu_item(label="Live Data", user_data="page_live_data") 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="Lap Recap", user_data="page_lap_recap") 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.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(tag=PAGE_LIVE_DATA, show=True):
with dpg.group(horizontal=True): with dpg.group(horizontal=True):
with dpg.child_window(tag="realtime_stats", width=260, autosize_y=True, border=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.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):
@@ -46,7 +47,7 @@ def build_windows(state: AppState) -> None:
_add_live_data_row("Battery Voltage", "no_data", LIVE_DATA_VBAT_VALUE,"V") _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("Engine Temp", "no_data", LIVE_DATA_TENG_VALUE,"°C")
with dpg.child_window(tag="data_graphs", autosize_x=True, autosize_y=True, border=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): with dpg.plot(label="Speed", height=250, width=-1, no_inputs=True):
dpg.add_plot_legend() dpg.add_plot_legend()
dpg.add_plot_axis(dpg.mvXAxis, label="Time", tag=GRAPH_X_AXIS_SPEED) dpg.add_plot_axis(dpg.mvXAxis, label="Time", tag=GRAPH_X_AXIS_SPEED)
@@ -69,7 +70,12 @@ def build_windows(state: AppState) -> None:
dpg.set_axis_limits(GRAPH_X_AXIS_TENG, ymin=-30, ymax=0) 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.group(tag="page_lap_recap", show=False): 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))
with dpg.group(tag=PAGE_LAP_RECAP, show=False):
dpg.add_text("Lap Recap") dpg.add_text("Lap Recap")
dpg.add_separator() dpg.add_separator()

View File

@@ -26,6 +26,10 @@ def ui_worker(state: AppState):
last_datetime = formatted last_datetime = formatted
if state.serial_thread_running and state.telemetry_valid: if state.serial_thread_running and state.telemetry_valid:
x_common: list[float] | None = None
speed_y: list[float] | None = None
vbat_y: list[float] | None = None
teng_y: list[float] | None = None
with state.lock: with state.lock:
# Vehicle Time # Vehicle Time
no_data_written = False no_data_written = False
@@ -33,14 +37,16 @@ def ui_worker(state: AppState):
veh_speed = state.latest_telemetry["speed"] veh_speed = state.latest_telemetry["speed"]
vbat = state.latest_telemetry["vbat"] vbat = state.latest_telemetry["vbat"]
teng = state.latest_telemetry["teng"] teng = state.latest_telemetry["teng"]
x_common = state.live_buffers.timestamp if state.live_buffers_updated:
speed_y = state.live_buffers.speed x_common = list(state.live_buffers.timestamp)
vbat_y = state.live_buffers.vbat speed_y = list(state.live_buffers.speed)
teng_y = state.live_buffers.teng vbat_y = list(state.live_buffers.vbat)
teng_y = list(state.live_buffers.teng)
state.live_buffers_updated = False
hours = veh_time // 1000000 hours = veh_time // 360000
minutes = (veh_time // 10000) % 100 minutes = (veh_time % 360000) // 6000
seconds = (veh_time // 100) % 100 seconds = (veh_time % 6000) // 100
formatted = f"{hours:02d}:{minutes:02d}:{seconds:02d}" formatted = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
if formatted != last_veh_time: if formatted != last_veh_time:
dpg.set_value(LIVE_DATA_VEHICLE_TIME_VALUE, formatted) dpg.set_value(LIVE_DATA_VEHICLE_TIME_VALUE, formatted)
@@ -65,12 +71,10 @@ def ui_worker(state: AppState):
dpg.set_value(LIVE_DATA_TENG_VALUE, formatted) dpg.set_value(LIVE_DATA_TENG_VALUE, formatted)
last_teng = formatted last_teng = formatted
if state.live_buffers_updated: if x_common is not None:
dpg.set_value(GRAPH_SERIES_SPEED, [x_common, speed_y]) dpg.set_value(GRAPH_SERIES_SPEED, [x_common, speed_y])
dpg.set_value(GRAPH_SERIES_VBAT, [x_common, vbat_y]) dpg.set_value(GRAPH_SERIES_VBAT, [x_common, vbat_y])
dpg.set_value(GRAPH_SERIES_TENG, [x_common, teng_y]) dpg.set_value(GRAPH_SERIES_TENG, [x_common, teng_y])
with state.lock:
state.live_buffers_updated = False
else: else:
if not no_data_written: if not no_data_written: