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.
632 lines
24 KiB
Python
632 lines
24 KiB
Python
"""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>"
|