Add Birdcage TUI: 5-screen Textual interface for Carryout G2
F1 Position (compass rose, motor control, sparklines), F2 Signal (RSSI gauge with sub-char precision, DVB/ADC sparklines, LNA toggle), F3 Scan (AZ/EL grid sweep with heatmap and CSV export), F4 System (NVS table, A3981 diagnostics, motor dynamics), F5 Console (raw serial terminal with prompt detection and safety gates). Includes SerialBridge (thread-safe protocol wrapper), DemoDevice (synthetic simulation for --demo mode), dark RF theme with rounded borders and teal accents, and send_raw() on CarryoutG2Protocol.
This commit is contained in:
parent
a70b9b0a29
commit
7271b53c63
23 changed files with 4160 additions and 0 deletions
631
tui/src/birdcage_tui/demo.py
Normal file
631
tui/src/birdcage_tui/demo.py
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
"""Synthetic demo device for the Birdcage TUI.
|
||||
|
||||
Drop-in replacement for SerialBridge that simulates a Winegard Carryout G2
|
||||
dish with motor movement, RSSI signal modeling, and canned firmware responses.
|
||||
No serial hardware required.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import math
|
||||
import random
|
||||
import time
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class _DemoMenu(Enum):
|
||||
"""Simulated firmware submenu states."""
|
||||
|
||||
ROOT = auto()
|
||||
MOT = auto()
|
||||
DVB = auto()
|
||||
NVS = auto()
|
||||
A3981 = auto()
|
||||
ADC = auto()
|
||||
OS = auto()
|
||||
STEP = auto()
|
||||
PEAK = auto()
|
||||
EEPROM = auto()
|
||||
GPIO = auto()
|
||||
LATLON = auto()
|
||||
DIPSWITCH = auto()
|
||||
|
||||
|
||||
# Complete NVS dump text from firmware 02.02.48 (captured 2026-02-12).
|
||||
_NVS_DUMP_TEXT = """\
|
||||
Num Name Current Saved Default
|
||||
---- -------------------------- ---------- ---------- ----------
|
||||
0) Log ID's 0x00000007 0x00000007 0x00000007
|
||||
1) Log Device 0x00000001 0x00000001 0x00000001
|
||||
2) Debug 2nd Console Port 0 0 0
|
||||
3) Debug 2nd Packet Port 0 0 0
|
||||
4) Debug Port Connection 0 0 0
|
||||
16) Pitch Deadband 0.00 0.00 0.00
|
||||
17) Roll Deadband 0.00 0.00 0.00
|
||||
18) Yaw Deadband 0.00 0.00 0.00
|
||||
20) Disable Tracker Proc? TRUE TRUE FALSE
|
||||
21) Tracker Proc Run Mode 0 0 0
|
||||
22) Conical Alpha Az 200 200 200
|
||||
23) Conical Alpha El 200 200 200
|
||||
24) Conical Radius 1.00 1.00 1.00
|
||||
25) Conical Count Max 20 20 20
|
||||
26) Conical Test Drift +0 +0 +0
|
||||
27) Circle RPM 120 120 120
|
||||
28) Circle Pts/Rev 6 6 6
|
||||
32) Conical Az Clamp 8.00 8.00 8.00
|
||||
33) Conical El Clamp 8.00 8.00 8.00
|
||||
35) Motor Pts/Rev 72 72 72
|
||||
36) Circle Az Radius 1.00 1.00 1.00
|
||||
37) Circle El Radius 1.00 1.00 1.00
|
||||
38) Sleep Mode Timer Secs 420 420 420
|
||||
40) Motor Type 0 0 0
|
||||
41) Satellite Scan Velocity 55.00 55.00 55.00
|
||||
48) Motor Spiral Velocity 55.00 55.00 55.00
|
||||
49) Motor Gear Ratio 0x00000000 0x00000000 0x00000000
|
||||
63) GPS Heading Threshold 1.00 1.00 1.00
|
||||
64) GPS Moving Threshold 5.00 MPH 5.00 MPH 5.00 MPH
|
||||
66) Spiral Signal In A Row Min +3 +3 +3
|
||||
67) Spiral Signal In A Row Max +20 +20 +20
|
||||
68) Signal Odd to Even Offset +0 +0 +0
|
||||
69) Signal Offset 80 80 80
|
||||
70) Signal Baseline Angle 65.00 65.00 65.00
|
||||
71) Signal Re-Peak Degrade Percent 25 25 25
|
||||
72) Gyro Sensitivity +1110 +1110 +1110
|
||||
73) Gyro Filter Size +1 +1 +1
|
||||
74) Gyro Calib Readings 100 100 100
|
||||
75) Gyro Mount Type 1 1 1
|
||||
76) Gyro Velocity Offset 4 4 4
|
||||
77) Gyro Max Accel 600 600 600
|
||||
80) AZ Max Vel 65.00 65.00 65.00
|
||||
81) AZ Max Accel 400.00 400.00 400.00
|
||||
82) AZ Home Velocity 55.00 55.00 55.00
|
||||
83) AZ Steps/Rev 40000 40000 40000
|
||||
84) AZ Direction +1 +1 +1
|
||||
85) EL Max Vel 45.00 45.00 45.00
|
||||
86) EL Max Accel 400.00 400.00 400.00
|
||||
87) EL Home Velocity 45.00 45.00 45.00
|
||||
88) EL Steps/Rev 24960 24960 24960
|
||||
89) EL Direction +1 +1 +1
|
||||
95) AZ Low current limit 0x0000ff0c 0x0000ff0c 0x0000ff0c
|
||||
96) AZ High current limit 0x0000ff30 0x0000ff30 0x0000ff30
|
||||
97) EL Low current limit 0x0000ff0c 0x0000ff0c 0x0000ff0c
|
||||
98) EL High current limit 0x0000ff40 0x0000ff40 0x0000ff40
|
||||
101) Minimum Elevation Angle 18.00 18.00 18.00
|
||||
102) Maximum Elevation Angle 65.00 65.00 65.00
|
||||
103) Elevation Home Angle 65.00 65.00 65.00
|
||||
106) Az Stall Detect 78 78 78
|
||||
107) El Stall Detect 75 75 75
|
||||
108) Az Stall Samples 100 100 100
|
||||
109) El Stall Samples 100 100 100
|
||||
110) EL Home Current Limit 0x0000ff28 0x0000ff28 0x0000ff28
|
||||
111) AZ Home Current Limit 0x0000ff40 0x0000ff40 0x0000ff40
|
||||
112) Disable Dipswitch? FALSE FALSE FALSE
|
||||
113) Dipswitch Value 101 101 101
|
||||
114) Dipswitch Front/Rear Mount 0 0 0
|
||||
115) Mount Offset Angle +0 +0 +0
|
||||
118) Signal Use LNB Clamp FALSE FALSE FALSE
|
||||
128) AZ PID Kp +600 +600 +600
|
||||
129) AZ PID Kv +60 +60 +60
|
||||
130) AZ PID Ki +1 +1 +1
|
||||
131) EL PID Kp +250 +250 +250
|
||||
132) EL PID Kv +50 +50 +50
|
||||
133) EL PID Ki +1 +1 +1
|
||||
136) AZ PWM Stall Cnt 6 6 6
|
||||
137) EL PWM Stall Cnt 5 5 5
|
||||
143) Tracking Number 0 0 0"""
|
||||
|
||||
# Parse NVS lines into a dict keyed by index for nvs_read().
|
||||
_NVS_LINES: dict[int, str] = {}
|
||||
for _line in _NVS_DUMP_TEXT.splitlines():
|
||||
_line_stripped = _line.strip()
|
||||
if _line_stripped and _line_stripped[0].isdigit():
|
||||
_idx_str = _line_stripped.split(")")[0].strip()
|
||||
with contextlib.suppress(ValueError):
|
||||
_NVS_LINES[int(_idx_str)] = _line_stripped
|
||||
|
||||
# Firmware identification text matching ``os > id`` output.
|
||||
_FIRMWARE_ID = """\
|
||||
NVS Version: 1.02.13
|
||||
System ID: TWELINCH
|
||||
K60-144pin
|
||||
Silicon Rev 2.4
|
||||
Mask Set 4N22D
|
||||
512 kBytes of P-flash
|
||||
P-flash only
|
||||
128 kBytes of RAM
|
||||
Board Rev ID: A
|
||||
Board ID: STATIONARY
|
||||
Ant ID: 12-IN G2
|
||||
Software version: 02.02.48
|
||||
CCLK: 96000000
|
||||
BCLK: 48000000
|
||||
Flash Base Address: 65536
|
||||
Flash Size: 458752"""
|
||||
|
||||
_DVB_CONFIG = """\
|
||||
BCM Hardware= ID: 0x4515 VER: 0xB0
|
||||
BCM Firmware= MAJOR VER: 0x71 (113) MINOR VER: 0x25 (37)
|
||||
BCM Strap Config: 0x25018"""
|
||||
|
||||
_CHANNEL_PARAMS = """\
|
||||
Power Mode: ON
|
||||
Search Transponders: ON
|
||||
Auto Search Mode: 1
|
||||
Shuffle Mode: ON
|
||||
Frequency List: Non-Stacked
|
||||
|
||||
Num Parameter Current Default
|
||||
1 Frequency 1090640 (kHz) 974000 (kHz)
|
||||
2 Symbol Rate 0 (PeakScanEnabled) 20000 (ksps)
|
||||
3 Trans_Mod_CRate blind_scan blind_scan
|
||||
4 Blind Scan Mode ___trb_dvb_dss_____ ___trb_dvb_dss_____
|
||||
5 LNB Polarity ODU:13V ---
|
||||
6 LNB Tone (ODU) off off
|
||||
7 Roll-off 0.35 0.35
|
||||
8 LPF Cutoff 0 (auto) 0 (MHz)
|
||||
9 Carrier Offset 0 (kHz) 0 (kHz)
|
||||
10 FreqSearchRange 5000 (kHz) 5000 (kHz)
|
||||
11 DCII Mode dcii_qpsk_comb dcii_qpsk_comb
|
||||
12 Spectral Inv scan scan
|
||||
13 PScnSymRtRngMin 18000 (ksps) 18000 (ksps)
|
||||
14 PScnSymRtRngMax 24000 (ksps) 24000 (ksps)
|
||||
15 SignalDetectMode off off"""
|
||||
|
||||
_MOTOR_LIFE = """\
|
||||
AZ total moves: 847
|
||||
AZ total degrees: 52340.50
|
||||
EL total moves: 423
|
||||
EL total degrees: 18920.75
|
||||
Uptime hours: 312.4"""
|
||||
|
||||
# Simulated satellite at AZ=200, EL=38 for RSSI modeling.
|
||||
_SAT_AZ = 200.0
|
||||
_SAT_EL = 38.0
|
||||
_RSSI_NOISE_FLOOR = 500
|
||||
_RSSI_PEAK = 2000
|
||||
_RSSI_BEAM_WIDTH = 50.0 # Gaussian denominator (degrees squared)
|
||||
|
||||
# Motor simulation speed (degrees per second).
|
||||
_MOTOR_SPEED = 10.0
|
||||
|
||||
|
||||
class DemoDevice:
|
||||
"""Synthetic demo device implementing the same interface as SerialBridge.
|
||||
|
||||
Simulates a Carryout G2 dish with motor movement, RSSI signal modeling,
|
||||
and canned firmware responses. No serial hardware required.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._connected = False
|
||||
self._engaged = True
|
||||
|
||||
# Current position and movement targets.
|
||||
self._az = 180.0
|
||||
self._el = 45.0
|
||||
self._target_az = 180.0
|
||||
self._target_el = 45.0
|
||||
self._last_move_time = time.monotonic()
|
||||
|
||||
# Submenu tracking for console simulation.
|
||||
self._menu = _DemoMenu.ROOT
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _update_position(self) -> None:
|
||||
"""Interpolate position toward target at ~10 deg/s."""
|
||||
now = time.monotonic()
|
||||
dt = now - self._last_move_time
|
||||
self._last_move_time = now
|
||||
|
||||
max_step = _MOTOR_SPEED * dt
|
||||
|
||||
for axis in ("az", "el"):
|
||||
current = getattr(self, f"_{axis}")
|
||||
target = getattr(self, f"_target_{axis}")
|
||||
delta = target - current
|
||||
|
||||
if abs(delta) < 0.001:
|
||||
continue
|
||||
|
||||
if abs(delta) <= max_step:
|
||||
# Arrived — add a tiny settling noise.
|
||||
noise = random.gauss(0.0, 0.02)
|
||||
setattr(self, f"_{axis}", target + noise)
|
||||
else:
|
||||
direction = 1.0 if delta > 0 else -1.0
|
||||
noise = random.gauss(0.0, 0.02)
|
||||
setattr(self, f"_{axis}", current + direction * max_step + noise)
|
||||
|
||||
def _compute_rssi(self) -> float:
|
||||
"""Gaussian signal model centered on the simulated satellite."""
|
||||
self._update_position()
|
||||
dist_sq = (self._az - _SAT_AZ) ** 2 + (self._el - _SAT_EL) ** 2
|
||||
signal = _RSSI_PEAK * math.exp(-dist_sq / _RSSI_BEAM_WIDTH)
|
||||
drift = math.sin(time.monotonic() / 60.0) * 50.0
|
||||
return _RSSI_NOISE_FLOOR + signal + drift
|
||||
|
||||
@property
|
||||
def _is_moving(self) -> bool:
|
||||
return (
|
||||
abs(self._az - self._target_az) > 0.05
|
||||
or abs(self._el - self._target_el) > 0.05
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Connection
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def connect(self, port: str = "/dev/demo", baudrate: int = 115200) -> None:
|
||||
self._connected = True
|
||||
self._menu = _DemoMenu.ROOT
|
||||
|
||||
def disconnect(self) -> None:
|
||||
self._connected = False
|
||||
self._menu = _DemoMenu.ROOT
|
||||
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
def initialize(self, skip_init: bool = False) -> None:
|
||||
self._connected = True
|
||||
self._menu = _DemoMenu.MOT
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Motor (MOT>)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_position(self) -> dict[str, float]:
|
||||
self._update_position()
|
||||
return {
|
||||
"azimuth": round(self._az, 2),
|
||||
"elevation": round(self._el, 2),
|
||||
}
|
||||
|
||||
def move_to(self, az: float, el: float) -> None:
|
||||
self._target_az = az
|
||||
self._target_el = el
|
||||
self._last_move_time = time.monotonic()
|
||||
|
||||
def move_motor(self, motor_id: int, degrees: float) -> None:
|
||||
if motor_id == 0:
|
||||
self._target_az = degrees
|
||||
elif motor_id == 1:
|
||||
self._target_el = degrees
|
||||
self._last_move_time = time.monotonic()
|
||||
|
||||
def home_motor(self, motor_id: int) -> None:
|
||||
if motor_id == 0:
|
||||
self._target_az = 0.0
|
||||
elif motor_id == 1:
|
||||
self._target_el = 65.0
|
||||
self._last_move_time = time.monotonic()
|
||||
|
||||
def engage(self) -> None:
|
||||
self._engaged = True
|
||||
|
||||
def release(self) -> None:
|
||||
self._engaged = False
|
||||
|
||||
def get_motor_list(self) -> str:
|
||||
return "Motors:\n 0 - AZIMUTH: local\n 1 - ELEVATION: local"
|
||||
|
||||
def get_motor_dynamics(self) -> dict[str, float]:
|
||||
return {
|
||||
"az_max_vel": 65.0,
|
||||
"el_max_vel": 45.0,
|
||||
"az_accel": 400.0,
|
||||
"el_accel": 400.0,
|
||||
}
|
||||
|
||||
def get_motor_life(self) -> str:
|
||||
return _MOTOR_LIFE
|
||||
|
||||
def get_el_limits(self) -> dict[str, float]:
|
||||
return {"min": 18.0, "max": 65.0, "home": 65.0}
|
||||
|
||||
def get_step_positions(self) -> dict[str, int]:
|
||||
self._update_position()
|
||||
return {
|
||||
"az_steps": int(self._az * 40000 / 360),
|
||||
"el_steps": int(self._el * 24960 / 360),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Signal (DVB>)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_rssi(self, iterations: int = 10) -> dict[str, int]:
|
||||
rssi = self._compute_rssi()
|
||||
noise = random.gauss(0.0, 30.0)
|
||||
return {
|
||||
"reads": iterations,
|
||||
"average": int(rssi),
|
||||
"current": int(rssi + noise),
|
||||
}
|
||||
|
||||
def enable_lna(self) -> None:
|
||||
pass # No-op in demo mode.
|
||||
|
||||
def get_lock_status(self) -> str:
|
||||
rssi = int(self._compute_rssi())
|
||||
locked = 1 if rssi > 1500 else 0
|
||||
return f"Lock:{locked} rssi:{rssi} cnt:0"
|
||||
|
||||
def get_dvb_config(self) -> str:
|
||||
return _DVB_CONFIG
|
||||
|
||||
def get_channel_params(self) -> str:
|
||||
return _CHANNEL_PARAMS
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# A3981
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_a3981_diag(self) -> str:
|
||||
return "AZ DIAG: OK\nEL DIAG: OK"
|
||||
|
||||
def get_a3981_modes(self) -> dict[str, str]:
|
||||
return {
|
||||
"step_mode": "AZ Step Size Mode = AUTO\nEL Step Size Mode = AUTO",
|
||||
"current_mode": "AZ: Mode = AUTO\nEL: Mode = AUTO",
|
||||
"step_size": (
|
||||
"KEY: FULL-16, HALF-8, QTR-4, EIGHTH-2, SIXTEENTH-1\n"
|
||||
"AZ Step Size:1\n"
|
||||
"EL Step Size:1"
|
||||
),
|
||||
}
|
||||
|
||||
def get_a3981_torque(self) -> str:
|
||||
if self._is_moving:
|
||||
return "AZ Torq:HIGH\nEL Torq:HIGH"
|
||||
return "AZ Torq:LOW\nEL Torq:LOW"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# NVS
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def nvs_dump(self) -> str:
|
||||
return _NVS_DUMP_TEXT
|
||||
|
||||
def nvs_read(self, index: int) -> str:
|
||||
line = _NVS_LINES.get(index)
|
||||
if line:
|
||||
return line
|
||||
return f"NVS index {index} not found"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# ADC
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_adc_rssi(self) -> str:
|
||||
rssi = self._compute_rssi()
|
||||
return str(int(rssi))
|
||||
|
||||
def get_board_id(self) -> str:
|
||||
return "STATIONARY"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# OS
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get_firmware_id(self) -> str:
|
||||
return _FIRMWARE_ID
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Raw / Console
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def send_raw(self, cmd: str) -> str:
|
||||
"""Simulate firmware console with basic submenu tracking."""
|
||||
cmd_stripped = cmd.strip().lower()
|
||||
|
||||
# Submenu navigation.
|
||||
if cmd_stripped == "q":
|
||||
self._menu = _DemoMenu.ROOT
|
||||
return "TRK>"
|
||||
|
||||
_enter_map: dict[str, _DemoMenu] = {
|
||||
"mot": _DemoMenu.MOT,
|
||||
"dvb": _DemoMenu.DVB,
|
||||
"nvs": _DemoMenu.NVS,
|
||||
"a3981": _DemoMenu.A3981,
|
||||
"adc": _DemoMenu.ADC,
|
||||
"os": _DemoMenu.OS,
|
||||
"step": _DemoMenu.STEP,
|
||||
"peak": _DemoMenu.PEAK,
|
||||
"eeprom": _DemoMenu.EEPROM,
|
||||
"gpio": _DemoMenu.GPIO,
|
||||
"latlon": _DemoMenu.LATLON,
|
||||
"dipswitch": _DemoMenu.DIPSWITCH,
|
||||
}
|
||||
|
||||
if cmd_stripped in _enter_map and self._menu == _DemoMenu.ROOT:
|
||||
self._menu = _enter_map[cmd_stripped]
|
||||
prompt = cmd_stripped.upper() + ">"
|
||||
return prompt
|
||||
|
||||
# Context-dependent responses.
|
||||
if self._menu == _DemoMenu.MOT:
|
||||
return self._handle_mot(cmd_stripped)
|
||||
if self._menu == _DemoMenu.DVB:
|
||||
return self._handle_dvb(cmd_stripped)
|
||||
if self._menu == _DemoMenu.NVS:
|
||||
return self._handle_nvs(cmd_stripped)
|
||||
if self._menu == _DemoMenu.A3981:
|
||||
return self._handle_a3981(cmd_stripped)
|
||||
if self._menu == _DemoMenu.ADC:
|
||||
return self._handle_adc(cmd_stripped)
|
||||
if self._menu == _DemoMenu.OS:
|
||||
return self._handle_os(cmd_stripped)
|
||||
if self._menu == _DemoMenu.ROOT:
|
||||
return self._handle_root(cmd_stripped)
|
||||
|
||||
return f"Unknown command: {cmd}\nTRK>"
|
||||
|
||||
def _handle_root(self, cmd: str) -> str:
|
||||
if cmd in ("?", "help"):
|
||||
return (
|
||||
"Available commands:\n"
|
||||
" a3981 adc dipswitch dvb eeprom gpio\n"
|
||||
" latlon mot nvs os peak step\n"
|
||||
" q reboot stow\n"
|
||||
"TRK>"
|
||||
)
|
||||
if cmd == "reboot":
|
||||
return "Rebooting...\nApplication Starting Kinetis PCB...\nTRK>"
|
||||
return f"Unknown command: {cmd}\nTRK>"
|
||||
|
||||
def _handle_mot(self, cmd: str) -> str:
|
||||
if cmd in ("?", "help"):
|
||||
return (
|
||||
"Available commands:\n"
|
||||
" a azscan azscanwxp e ela2s elminmaxhome\n"
|
||||
" els2a g h l life ma motorboth motorlife\n"
|
||||
" mv p pid r sd sp sw v vms w\n"
|
||||
"MOT>"
|
||||
)
|
||||
if cmd == "a":
|
||||
self._update_position()
|
||||
return f" Angle[0] = {self._az:.2f}\n Angle[1] = {self._el:.2f}\nMOT>"
|
||||
if cmd == "l":
|
||||
return "Motors:\n 0 - AZIMUTH: local\n 1 - ELEVATION: local\nMOT>"
|
||||
if cmd == "e":
|
||||
self._engaged = True
|
||||
return "Motors engaged\nMOT>"
|
||||
if cmd == "r":
|
||||
self._engaged = False
|
||||
return "Motors released\nMOT>"
|
||||
if cmd == "elminmaxhome":
|
||||
return "Min: 1800 Max: 6500 Home: 6500\nMOT>"
|
||||
if cmd == "life":
|
||||
return _MOTOR_LIFE + "\nMOT>"
|
||||
if cmd.startswith("a "):
|
||||
parts = cmd.split()
|
||||
if len(parts) >= 3:
|
||||
motor_id = int(parts[1])
|
||||
degrees = float(parts[2])
|
||||
if motor_id == 0:
|
||||
self._target_az = degrees
|
||||
elif motor_id == 1:
|
||||
self._target_el = degrees
|
||||
self._last_move_time = time.monotonic()
|
||||
return f" Angle = {degrees:.2f}\nMOT>"
|
||||
return "Invalid parameters\nMOT>"
|
||||
if cmd.startswith("h "):
|
||||
parts = cmd.split()
|
||||
if len(parts) >= 2:
|
||||
motor_id = int(parts[1])
|
||||
self.home_motor(motor_id)
|
||||
return f"Homing motor {motor_id}\nMOT>"
|
||||
return "Invalid parameters\nMOT>"
|
||||
if cmd == "mv":
|
||||
return "Max Vel [0] = 65.0 Max Vel [1] = 45.0\nMOT>"
|
||||
if cmd == "ma":
|
||||
return "Accel[0] = 400.0 Accel[1] = 400.0\nMOT>"
|
||||
if cmd == "p":
|
||||
self._update_position()
|
||||
az_steps = int(self._az * 40000 / 360)
|
||||
el_steps = int(self._el * 24960 / 360)
|
||||
return f"Position[0] = {az_steps} Position[1] = {el_steps}\nMOT>"
|
||||
return f"Unknown command: {cmd}\nMOT>"
|
||||
|
||||
def _handle_dvb(self, cmd: str) -> str:
|
||||
if cmd in ("?", "help"):
|
||||
return (
|
||||
"Available commands:\n"
|
||||
" agc config def diag dis e freqs\n"
|
||||
" lnbdc lnbv ls man msw nid pwr\n"
|
||||
" qls range rssi shuf snr srch srch_mode\n"
|
||||
" stats t table tablex tabto to\n"
|
||||
"DVB>"
|
||||
)
|
||||
if cmd.startswith("rssi"):
|
||||
rssi_val = int(self._compute_rssi())
|
||||
parts = cmd.split()
|
||||
iters = int(parts[1]) if len(parts) > 1 else 10
|
||||
noise = random.gauss(0.0, 30.0)
|
||||
cur = int(rssi_val + noise)
|
||||
return (
|
||||
f"iterations:{iters} interval(msec):20\n"
|
||||
f" Reads:{iters} RSSI[avg: {rssi_val} cur: {cur}]\n"
|
||||
"DVB>"
|
||||
)
|
||||
if cmd == "config":
|
||||
return _DVB_CONFIG + "\nDVB>"
|
||||
if cmd == "dis":
|
||||
return _CHANNEL_PARAMS + "\nDVB>"
|
||||
if cmd == "lnbdc odu":
|
||||
return "Enabled LNB ODU 13V\nDVB>"
|
||||
if cmd == "qls":
|
||||
rssi_val = int(self._compute_rssi())
|
||||
locked = 1 if rssi_val > 1500 else 0
|
||||
return f"Lock:{locked} rssi:{rssi_val} cnt:0\nDVB>"
|
||||
return f"Unknown command: {cmd}\nDVB>"
|
||||
|
||||
def _handle_nvs(self, cmd: str) -> str:
|
||||
if cmd in ("?", "help"):
|
||||
return "Available commands:\n d e s\nNVS>"
|
||||
if cmd == "d":
|
||||
return _NVS_DUMP_TEXT + "\nNVS>"
|
||||
if cmd == "s":
|
||||
return "NVS saved\nNVS>"
|
||||
if cmd.startswith("e "):
|
||||
parts = cmd.split()
|
||||
if len(parts) >= 2:
|
||||
try:
|
||||
idx = int(parts[1])
|
||||
line = _NVS_LINES.get(idx)
|
||||
if line:
|
||||
return line + "\nNVS>"
|
||||
return f"NVS index {idx} not found\nNVS>"
|
||||
except ValueError:
|
||||
pass
|
||||
return "Invalid parameters\nNVS>"
|
||||
return f"Unknown command: {cmd}\nNVS>"
|
||||
|
||||
def _handle_a3981(self, cmd: str) -> str:
|
||||
if cmd in ("?", "help"):
|
||||
return "Available commands:\n cm diag reset sm ss st\nA3981>"
|
||||
if cmd == "diag":
|
||||
return "AZ DIAG: OK\nEL DIAG: OK\nA3981>"
|
||||
if cmd == "sm":
|
||||
return "AZ Step Size Mode = AUTO\nEL Step Size Mode = AUTO\nA3981>"
|
||||
if cmd == "cm":
|
||||
return "AZ: Mode = AUTO\nEL: Mode = AUTO\nA3981>"
|
||||
if cmd == "ss":
|
||||
return (
|
||||
"KEY: FULL-16, HALF-8, QTR-4, EIGHTH-2, SIXTEENTH-1\n"
|
||||
"AZ Step Size:1\n"
|
||||
"EL Step Size:1\n"
|
||||
"A3981>"
|
||||
)
|
||||
if cmd == "st":
|
||||
if self._is_moving:
|
||||
return "AZ Torq:HIGH\nEL Torq:HIGH\nA3981>"
|
||||
return "AZ Torq:LOW\nEL Torq:LOW\nA3981>"
|
||||
if cmd == "reset":
|
||||
return "Az/El A3981 Faults Reset.\nA3981>"
|
||||
return f"Unknown command: {cmd}\nA3981>"
|
||||
|
||||
def _handle_adc(self, cmd: str) -> str:
|
||||
if cmd in ("?", "help"):
|
||||
return "Available commands:\n bdid bdrevid m rssi scan\nADC>"
|
||||
if cmd == "rssi":
|
||||
return str(int(self._compute_rssi())) + "\nADC>"
|
||||
if cmd == "bdid":
|
||||
return "STATIONARY\nADC>"
|
||||
if cmd == "bdrevid":
|
||||
return "A\nADC>"
|
||||
return f"Unknown command: {cmd}\nADC>"
|
||||
|
||||
def _handle_os(self, cmd: str) -> str:
|
||||
if cmd in ("?", "help"):
|
||||
return "Available commands:\n id reboot\nOS>"
|
||||
if cmd == "id":
|
||||
return _FIRMWARE_ID + "\nOS>"
|
||||
if cmd == "reboot":
|
||||
return "Rebooting...\nApplication Starting Kinetis PCB...\nTRK>"
|
||||
return f"Unknown command: {cmd}\nOS>"
|
||||
Loading…
Add table
Add a link
Reference in a new issue