Replace sidebar + 5-screen layout with horizontal tab bar (F1-F4) and persistent StatusStrip. Consolidate Position + Scan screens into Signal (F3) with Monitor/Sweep/Sky Map sub-modes via ModeBar. Screens: - F1 Dashboard: system health, tracking panel, quick actions, presets - F2 Control: motor tuning, compass rose, preset management - F3 Signal: RSSI monitor, 1D sweep (SweepPlot), 2D sky map (heatmap) - F4 System: NVS editor with regex filter, EEPROM, firmware info - F5 Console: push/pop overlay (no longer a tab) New widgets: StatusStrip, ModeBar, SweepPlot, QuickActions, PresetList, ReceiverInfo, MotorTuning, NvsFilter, SystemHealth, TrackingPanel. Removed: PositionScreen, ScanScreen, DeviceStatusBar (functionality absorbed into new screens and StatusStrip). App-level position poll feeds StatusStrip and active screen at ~2 Hz. Fix shared threading.Event across instances (class-level mutable default).
112 lines
3.3 KiB
Python
112 lines
3.3 KiB
Python
"""Status strip -- persistent 1-row connection/position/signal bar."""
|
|
|
|
from rich.text import Text
|
|
from textual.reactive import reactive
|
|
from textual.widgets import Static
|
|
|
|
# Firmware prompt → display color mapping.
|
|
_MENU_COLORS: dict[str, str] = {
|
|
"TRK>": "#00d4aa",
|
|
"MOT>": "#00e060",
|
|
"DVB>": "#2080d0",
|
|
"NVS>": "#e8a020",
|
|
"A3981>": "#00b8c8",
|
|
"STEP>": "#40c0a0",
|
|
"EE>": "#e8a020",
|
|
"OS>": "#8090a0",
|
|
"ADC>": "#00b8c8",
|
|
"GPIO>": "#40c0a0",
|
|
"PEAK>": "#e8c020",
|
|
}
|
|
|
|
|
|
class StatusStrip(Static):
|
|
"""Persistent status bar showing connection, position, signal, and motor state.
|
|
|
|
Docked below the header on every screen. Updated by the app-level
|
|
position poll and signal monitors.
|
|
"""
|
|
|
|
connected: reactive[bool] = reactive(False)
|
|
demo: reactive[bool] = reactive(False)
|
|
port: reactive[str] = reactive("/dev/ttyUSB0")
|
|
azimuth: reactive[float] = reactive(0.0)
|
|
elevation: reactive[float] = reactive(0.0)
|
|
rssi: reactive[int] = reactive(-1) # -1 means no data
|
|
motor_state: reactive[str] = reactive("IDLE")
|
|
fw_menu: reactive[str] = reactive("TRK>")
|
|
|
|
def render(self) -> Text:
|
|
result = Text()
|
|
sep = Text(" \u2502 ", style="#1a2a38")
|
|
|
|
# Connection status
|
|
if self.demo:
|
|
result.append(" DEMO", style="#e8a020 bold italic")
|
|
elif self.connected:
|
|
result.append(" CONNECTED", style="#00e060 bold")
|
|
result.append(f" {self.port}", style="#506878")
|
|
else:
|
|
result.append(" OFFLINE", style="#e04040 bold")
|
|
|
|
result.append_text(sep)
|
|
|
|
# Position
|
|
result.append("AZ ", style="#506878")
|
|
result.append(f"{self.azimuth:7.2f}", style="#00d4aa bold")
|
|
result.append(" EL ", style="#506878")
|
|
result.append(f"{self.elevation:6.2f}", style="#00d4aa bold")
|
|
|
|
result.append_text(sep)
|
|
|
|
# Signal (RSSI)
|
|
if self.rssi >= 0:
|
|
result.append("RSSI ", style="#506878")
|
|
result.append(f"{self.rssi}", style="#00b8c8 bold")
|
|
else:
|
|
result.append("RSSI ", style="#384858")
|
|
result.append("---", style="#384858")
|
|
|
|
result.append_text(sep)
|
|
|
|
# Motor state
|
|
state = self.motor_state
|
|
if state == "MOVING":
|
|
result.append(state, style="#e8c020 bold")
|
|
elif state == "ENGAGED":
|
|
result.append(state, style="#00e060")
|
|
else:
|
|
result.append(state, style="#506878")
|
|
|
|
result.append_text(sep)
|
|
|
|
# Firmware context
|
|
menu_color = _MENU_COLORS.get(self.fw_menu, "#506878")
|
|
result.append(self.fw_menu, style=f"{menu_color}")
|
|
|
|
return result
|
|
|
|
def watch_connected(self, _value: bool) -> None:
|
|
self.refresh()
|
|
|
|
def watch_demo(self, _value: bool) -> None:
|
|
self.refresh()
|
|
|
|
def watch_port(self, _value: str) -> None:
|
|
self.refresh()
|
|
|
|
def watch_azimuth(self, _value: float) -> None:
|
|
self.refresh()
|
|
|
|
def watch_elevation(self, _value: float) -> None:
|
|
self.refresh()
|
|
|
|
def watch_rssi(self, _value: int) -> None:
|
|
self.refresh()
|
|
|
|
def watch_motor_state(self, _value: str) -> None:
|
|
self.refresh()
|
|
|
|
def watch_fw_menu(self, _value: str) -> None:
|
|
self.refresh()
|