Update TUI controls and TTL editing
This commit is contained in:
83
obd2_tui.py
83
obd2_tui.py
@ -2,6 +2,7 @@ import asyncio
|
||||
import argparse
|
||||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import obd
|
||||
@ -31,6 +32,8 @@ MODE_LABELS = {
|
||||
9: "Mode 09 Vehicle Info",
|
||||
}
|
||||
|
||||
MODE_ORDER = [1, 2, 3, 4, 6, 7, 9]
|
||||
|
||||
|
||||
def format_metric_value(report: Report, metric_id: str) -> str:
|
||||
values = {
|
||||
@ -43,6 +46,22 @@ def format_metric_value(report: Report, metric_id: str) -> str:
|
||||
return values[metric_id]
|
||||
|
||||
|
||||
def parse_duration_ms(value: str) -> int:
|
||||
match = re.fullmatch(r"\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h)?\s*", value, re.IGNORECASE)
|
||||
if match is None:
|
||||
raise ValueError(f"Invalid duration: {value}")
|
||||
|
||||
amount = float(match.group(1))
|
||||
unit = (match.group(2) or "ms").lower()
|
||||
multipliers = {
|
||||
"ms": 1,
|
||||
"s": 1000,
|
||||
"m": 60_000,
|
||||
"h": 3_600_000,
|
||||
}
|
||||
return max(0, int(round(amount * multipliers[unit])))
|
||||
|
||||
|
||||
class RichLogHandler(logging.Handler):
|
||||
def __init__(self, app: "OBD2App") -> None:
|
||||
super().__init__()
|
||||
@ -64,6 +83,12 @@ class OBD2App(App[None]):
|
||||
BINDINGS = [
|
||||
("q", "quit", "Quit"),
|
||||
("b", "toggle_border", "Toggle border"),
|
||||
("e", "focus_ttl", "Edit TTL"),
|
||||
("escape", "focus_table", "Focus Table"),
|
||||
("left", "previous_mode", "Prev Mode"),
|
||||
("right", "next_mode", "Next Mode"),
|
||||
("shift+up", "jump_table_top", "Top"),
|
||||
("shift+down", "jump_table_bottom", "Bottom"),
|
||||
("1", "select_mode(1)", "Mode 1"),
|
||||
("2", "select_mode(2)", "Mode 2"),
|
||||
("3", "select_mode(3)", "Mode 3"),
|
||||
@ -103,7 +128,7 @@ class OBD2App(App[None]):
|
||||
DataTable(id="commands-table"),
|
||||
Container(
|
||||
Label("Selected TTL (ms)", id="ttl-editor-label"),
|
||||
Input(placeholder="TTL in ms", id="ttl-editor"),
|
||||
Input(placeholder="e.g. 10ms, 30s, 1m, 1h", id="ttl-editor"),
|
||||
id="ttl-editor-pane",
|
||||
),
|
||||
id="table-pane",
|
||||
@ -123,13 +148,14 @@ class OBD2App(App[None]):
|
||||
classes="metric-card",
|
||||
)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
async def on_mount(self) -> None:
|
||||
self.configure_logging()
|
||||
self.interface = self.interface_factory(
|
||||
self.logger,
|
||||
query_rate_limit=self.query_rate_limit,
|
||||
ttl_config_path=self.ttl_config_path,
|
||||
)
|
||||
await self.interface.reload_ttl_config(force=True)
|
||||
self.configure_commands_table()
|
||||
self.load_mode_table(self.selected_mode)
|
||||
self.poll_task = asyncio.create_task(self.poll_commands())
|
||||
@ -157,6 +183,41 @@ class OBD2App(App[None]):
|
||||
self.load_mode_table(mode)
|
||||
self.logger.info("Selected %s", MODE_LABELS[mode])
|
||||
|
||||
def action_previous_mode(self) -> None:
|
||||
index = MODE_ORDER.index(self.selected_mode)
|
||||
self.action_select_mode(MODE_ORDER[(index - 1) % len(MODE_ORDER)])
|
||||
|
||||
def action_next_mode(self) -> None:
|
||||
index = MODE_ORDER.index(self.selected_mode)
|
||||
self.action_select_mode(MODE_ORDER[(index + 1) % len(MODE_ORDER)])
|
||||
|
||||
def action_focus_ttl(self) -> None:
|
||||
editor = self.query_one("#ttl-editor", Input)
|
||||
editor.focus()
|
||||
editor.action_end()
|
||||
|
||||
def action_focus_table(self) -> None:
|
||||
self.query_one("#commands-table", DataTable).focus()
|
||||
|
||||
def action_jump_table_top(self) -> None:
|
||||
table = self.query_one("#commands-table", DataTable)
|
||||
table.focus()
|
||||
if table.row_count > 0:
|
||||
table.move_cursor(row=0)
|
||||
table.scroll_to(y=0, animate=False)
|
||||
|
||||
def action_jump_table_bottom(self) -> None:
|
||||
table = self.query_one("#commands-table", DataTable)
|
||||
table.focus()
|
||||
if table.row_count > 0:
|
||||
last_row = table.row_count - 1
|
||||
table.move_cursor(row=last_row)
|
||||
table.scroll_to(y=table.max_scroll_y, animate=False)
|
||||
|
||||
def on_click(self, event) -> None:
|
||||
if getattr(event.widget, "id", None) in {"ttl-editor", "ttl-editor-pane", "ttl-editor-label"}:
|
||||
self.action_focus_ttl()
|
||||
|
||||
def configure_logging(self) -> None:
|
||||
self.log_handler = RichLogHandler(self)
|
||||
self.log_handler.setFormatter(
|
||||
@ -186,11 +247,12 @@ class OBD2App(App[None]):
|
||||
table = self.query_one("#commands-table", DataTable)
|
||||
table.cursor_type = "row"
|
||||
table.zebra_stripes = True
|
||||
table.add_column("Code", key="code", width=10)
|
||||
table.add_column("Name", key="name", width=28)
|
||||
table.add_column("Description", key="desc", width=44)
|
||||
table.add_column("TTL (ms)", key="ttl", width=12)
|
||||
table.add_column("Value", key="value", width=24)
|
||||
table.show_row_labels = False
|
||||
table.add_column("Code", key="code", width=8)
|
||||
table.add_column("Name", key="name", width=24)
|
||||
table.add_column("Description", key="desc", width=34)
|
||||
table.add_column("TTL (ms)", key="ttl", width=10)
|
||||
table.add_column("Value", key="value", width=22)
|
||||
|
||||
def load_mode_table(self, mode: int) -> None:
|
||||
table = self.query_one("#commands-table", DataTable)
|
||||
@ -312,7 +374,9 @@ class OBD2App(App[None]):
|
||||
continue
|
||||
|
||||
def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
|
||||
self.set_ttl_editor(str(event.row_key))
|
||||
row = self.query_one("#commands-table", DataTable).get_row(event.row_key)
|
||||
if len(row) >= 2:
|
||||
self.set_ttl_editor(str(row[1]))
|
||||
|
||||
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
if event.input.id != "ttl-editor" or self.interface is None or self.ttl_edit_command is None:
|
||||
@ -320,7 +384,7 @@ class OBD2App(App[None]):
|
||||
|
||||
raw_value = event.value.strip()
|
||||
try:
|
||||
ttl_ms = int(raw_value)
|
||||
ttl_ms = parse_duration_ms(raw_value)
|
||||
except ValueError:
|
||||
self.logger.error("Invalid TTL value: %s", raw_value)
|
||||
self.set_ttl_editor(self.ttl_edit_command)
|
||||
@ -330,6 +394,7 @@ class OBD2App(App[None]):
|
||||
self.query_one("#commands-table", DataTable).update_cell(self.ttl_edit_command, "ttl", str(ttl_ms))
|
||||
self.logger.info("Updated TTL for %s to %d ms", self.ttl_edit_command, ttl_ms)
|
||||
self.set_ttl_editor(self.ttl_edit_command)
|
||||
self.action_focus_table()
|
||||
|
||||
async def poll_commands(self) -> None:
|
||||
while True:
|
||||
|
||||
Reference in New Issue
Block a user