Clean up UI windows and strengthen settings workflows
This commit is contained in:
+64
-3
@@ -40,12 +40,15 @@ class ControlApi:
|
||||
"app_state": "disconnected",
|
||||
"status": "Ready. Select a USB port to begin.",
|
||||
"battery": None,
|
||||
"link_quality": None,
|
||||
"heavy_quality": None,
|
||||
"log_path": None,
|
||||
"servos": {str(i): {"function": None, "direction": None} for i in range(1, 17)},
|
||||
}
|
||||
self.servo_functions: dict[int, int] = {}
|
||||
self.motor_directions: dict[int, int] = {}
|
||||
self.battery_path = Path(__file__).resolve().parent / "battery_settings.json"
|
||||
self.link_path = Path(__file__).resolve().parent / "link_settings.json"
|
||||
self.model_path = Path(__file__).resolve().parent / "model_settings.json"
|
||||
self.model_cache = Path(__file__).resolve().parent / "runtime" / "vehicle_model.stl"
|
||||
self.model_cache_name = Path(__file__).resolve().parent / "runtime" / "vehicle_model_name.txt"
|
||||
@@ -62,11 +65,22 @@ class ControlApi:
|
||||
self.battery_config.update(json.loads(self.battery_path.read_text(encoding="utf-8")))
|
||||
except (OSError, ValueError, TypeError):
|
||||
pass
|
||||
self.link_config = {
|
||||
"baud": 57600, "control_hz": 20,
|
||||
"ping_hz": 1, "system_hz": 1, "servo_hz": 2,
|
||||
"rc_hz": 2, "esc_hz": 1,
|
||||
}
|
||||
try:
|
||||
self.link_config.update(json.loads(self.link_path.read_text(encoding="utf-8")))
|
||||
except (OSError, ValueError, TypeError):
|
||||
pass
|
||||
self._apply_link_config()
|
||||
|
||||
def initial_state(self):
|
||||
return {
|
||||
"ports": self.refresh_ports(),
|
||||
"battery_config": self.battery_config,
|
||||
"link_config": self.link_config,
|
||||
"state": self._snapshot(),
|
||||
"model_info": self.get_saved_model_info(),
|
||||
}
|
||||
@@ -233,6 +247,9 @@ class ControlApi:
|
||||
self.state.update(
|
||||
connected=True, link="LINK OK", vehicle="DEMO • DISARMED",
|
||||
armed=False, mode="MANUAL", status="Demo mode ready — no hardware connected.",
|
||||
link_quality={"grade": "excellent", "latency_ms": 12.0,
|
||||
"loss_percent": 0.0, "samples": 20,
|
||||
"corrupt_frames_20s": 0, "heartbeat_age_s": 0.1},
|
||||
battery={"voltage": 15.8, "per_cell": 3.95, "percent": 72,
|
||||
"cells": 4, "low": False},
|
||||
)
|
||||
@@ -268,15 +285,26 @@ class ControlApi:
|
||||
raise TimeoutError(f"No MAVLink heartbeat received from {port} within 8 seconds")
|
||||
|
||||
def disconnect(self):
|
||||
if self.worker.heavy_quality_test is not None:
|
||||
self.worker.cancel_heavy_quality_test()
|
||||
if self.demo_mode:
|
||||
self.demo_mode = False
|
||||
with self.lock:
|
||||
self.state.update(connected=False, link="DISCONNECTED", vehicle="No heartbeat",
|
||||
armed=False, mode=None, battery=None, status="Demo mode disconnected")
|
||||
armed=False, mode=None, battery=None, link_quality=None,
|
||||
status="Demo mode disconnected")
|
||||
return True
|
||||
self.worker.disconnect()
|
||||
return True
|
||||
|
||||
def start_heavy_quality_test(self):
|
||||
if self.demo_mode:
|
||||
raise RuntimeError("The heavy link test requires a real flight-controller connection")
|
||||
return self.worker.start_heavy_quality_test()
|
||||
|
||||
def cancel_heavy_quality_test(self):
|
||||
return self.worker.cancel_heavy_quality_test()
|
||||
|
||||
def set_axes(self, forward, turn, vertical, sequence=None):
|
||||
if self.demo_mode:
|
||||
with self.lock:
|
||||
@@ -389,8 +417,33 @@ class ControlApi:
|
||||
raise ValueError("Full voltage per cell must be greater than empty voltage")
|
||||
if not 0 <= clean["warning_percent"] <= 100:
|
||||
raise ValueError("Warning percentage must be between 0 and 100")
|
||||
self.battery_config = clean
|
||||
self.battery_path.write_text(json.dumps(clean, indent=2) + "\n", encoding="utf-8")
|
||||
self.battery_config = clean
|
||||
return clean
|
||||
|
||||
def _apply_link_config(self):
|
||||
self.worker.rate_hz = self.link_config["control_hz"]
|
||||
self.worker.set_link_rates(self.link_config["ping_hz"], {
|
||||
"SYS_STATUS": self.link_config["system_hz"],
|
||||
"SERVO_OUTPUT_RAW": self.link_config["servo_hz"],
|
||||
"RC_CHANNELS": self.link_config["rc_hz"],
|
||||
"ESC_TELEMETRY_1_TO_4": self.link_config["esc_hz"],
|
||||
})
|
||||
|
||||
def save_link_settings(self, config):
|
||||
allowed = {0, 1, 2, 5, 10}
|
||||
clean = {key: int(config.get(key, value)) for key, value in self.link_config.items()}
|
||||
if clean["baud"] not in {9600, 19200, 38400, 57600, 115200}:
|
||||
raise ValueError("Choose a supported baud rate")
|
||||
if clean["control_hz"] not in {10, 20}:
|
||||
raise ValueError("Control rate must be 10 or 20 Hz")
|
||||
if clean["ping_hz"] not in {1, 2, 5, 10}:
|
||||
raise ValueError("Link test rate must be 1, 2, 5 or 10 Hz")
|
||||
if any(clean[key] not in allowed for key in ("system_hz", "servo_hz", "rc_hz", "esc_hz")):
|
||||
raise ValueError("Telemetry rates must be Off, 1, 2, 5 or 10 Hz")
|
||||
self.link_path.write_text(json.dumps(clean, indent=2) + "\n", encoding="utf-8")
|
||||
self.link_config = clean
|
||||
self._apply_link_config()
|
||||
return clean
|
||||
|
||||
def open_logs(self):
|
||||
@@ -399,6 +452,10 @@ class ControlApi:
|
||||
os.startfile(path)
|
||||
return True
|
||||
|
||||
def read_console(self, limit=150):
|
||||
"""Return a bounded, read-only view of the local session console."""
|
||||
return self.worker.read_console(limit)
|
||||
|
||||
def poll(self):
|
||||
# Bound a poll even if telemetry arrives continuously.
|
||||
with self._poll_lock:
|
||||
@@ -468,6 +525,10 @@ class ControlApi:
|
||||
"voltage": voltage, "per_cell": per_cell, "percent": percent,
|
||||
"cells": cells, "low": percent <= self.battery_config["warning_percent"],
|
||||
}
|
||||
elif kind == "link_quality":
|
||||
self.state["link_quality"] = event[1]
|
||||
elif kind == "heavy_quality":
|
||||
self.state["heavy_quality"] = event[1]
|
||||
elif kind == "servo_param":
|
||||
_, param_id, value = event
|
||||
servo = int(param_id[5:param_id.index("_")])
|
||||
@@ -490,7 +551,7 @@ class ControlApi:
|
||||
last_err = self.state.get("last_error")
|
||||
status_msg = last_err if last_err else "Disconnected"
|
||||
self.state.update(connected=False, link="DISCONNECTED", vehicle="No heartbeat",
|
||||
mode=None, status=status_msg)
|
||||
mode=None, link_quality=None, status=status_msg)
|
||||
self.state["last_error"] = None
|
||||
|
||||
def _update_servo(self, servo):
|
||||
|
||||
Reference in New Issue
Block a user