mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-08 07:02:06 +08:00
Panic Nik
This commit is contained in:
Binary file not shown.
@@ -126,6 +126,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SecOCKey", {PERSISTENT | DONT_LOG, STRING}},
|
||||
{"ShowDebugInfo", {PERSISTENT, BOOL}},
|
||||
{"ShowAllToggles", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"TryRaylibUI", {PERSISTENT, BOOL, "0"}},
|
||||
{"UsePrebuilt", {PERSISTENT, BOOL, "1"}},
|
||||
{"RouteCount", {PERSISTENT, INT, "0"}},
|
||||
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
|
||||
|
||||
Binary file not shown.
@@ -3632,6 +3632,13 @@
|
||||
"name": "Developer",
|
||||
"icon": "bi-exclamation-triangle",
|
||||
"params": [
|
||||
{
|
||||
"key": "TryRaylibUI",
|
||||
"label": "Try raylib UI",
|
||||
"description": "Use the beta raylib UI instead of the default Qt UI on tici/tizi devices. This setting has no effect on C4/mici devices.",
|
||||
"data_type": "bool",
|
||||
"ui_type": "toggle"
|
||||
},
|
||||
{
|
||||
"key": "DisableWideRoad",
|
||||
"label": "Disable Wide Road Camera",
|
||||
|
||||
@@ -43,6 +43,43 @@ class FakeParamsBackend:
|
||||
return self.values.get(key)
|
||||
|
||||
|
||||
class WritableFakeParams:
|
||||
def __init__(self, values=None):
|
||||
self.values = dict(values or {})
|
||||
self.writes = []
|
||||
|
||||
def get(self, key, encoding=None, default=None, block=False):
|
||||
del encoding, block
|
||||
return self.values.get(key, default)
|
||||
|
||||
def get_bool(self, key):
|
||||
value = self.values.get(key, False)
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return str(value).strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
def put(self, key, value):
|
||||
self.writes.append((key, value))
|
||||
self.values[key] = value
|
||||
|
||||
def put_bool(self, key, value):
|
||||
self.writes.append((key, bool(value)))
|
||||
self.values[key] = bool(value)
|
||||
|
||||
|
||||
def _params_client(monkeypatch, values, device_type):
|
||||
fake_params = WritableFakeParams(values)
|
||||
monkeypatch.setattr(the_galaxy, "params", fake_params)
|
||||
monkeypatch.setattr(the_galaxy, "_get_param_type_info", lambda: ({"TryRaylibUI"}, {"TryRaylibUI": bool}))
|
||||
monkeypatch.setattr(the_galaxy.HARDWARE, "get_device_type", lambda: device_type)
|
||||
monkeypatch.setattr(the_galaxy.Paths, "comma_home", lambda: "/tmp/dashboard-test-home", raising=False)
|
||||
|
||||
assert the_galaxy._import_galaxy_web_symbols()
|
||||
app = the_galaxy.Flask(f"params_test_{device_type}")
|
||||
the_galaxy.setup(app)
|
||||
return app.test_client(), fake_params
|
||||
|
||||
|
||||
def test_params_compat_accepts_json_strings_for_json_keys():
|
||||
backend = FakeParamsBackend(
|
||||
key_types={"FavoriteDestinations": ParamKeyType.JSON},
|
||||
@@ -117,3 +154,38 @@ def test_galaxy_session_value_matches_cookie_format():
|
||||
"testGalaxySlug01",
|
||||
"a" * 64,
|
||||
) == f"testGalaxySlug01%3A{'a' * 64}"
|
||||
|
||||
|
||||
def test_try_raylib_ui_is_noop_on_c4_mici(monkeypatch):
|
||||
client, fake_params = _params_client(monkeypatch, {"TryRaylibUI": False, "IsOnroad": False}, "mici")
|
||||
|
||||
response = client.put("/api/params", json={"key": "TryRaylibUI", "value": True})
|
||||
payload = response.get_json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert payload["updated"] == {"TryRaylibUI": False}
|
||||
assert fake_params.values["TryRaylibUI"] is False
|
||||
assert fake_params.writes == []
|
||||
|
||||
|
||||
def test_try_raylib_ui_writes_on_big_device_offroad(monkeypatch):
|
||||
client, fake_params = _params_client(monkeypatch, {"TryRaylibUI": False, "IsOnroad": False}, "tici")
|
||||
|
||||
response = client.put("/api/params", json={"key": "TryRaylibUI", "value": True})
|
||||
payload = response.get_json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert payload["updated"] == {"TryRaylibUI": True}
|
||||
assert fake_params.values["TryRaylibUI"] is True
|
||||
assert fake_params.writes == [("TryRaylibUI", True)]
|
||||
|
||||
|
||||
def test_try_raylib_ui_rejects_big_device_onroad_change(monkeypatch):
|
||||
client, fake_params = _params_client(monkeypatch, {"TryRaylibUI": False, "IsOnroad": True}, "tici")
|
||||
|
||||
response = client.put("/api/params", json={"key": "TryRaylibUI", "value": True})
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.get_json()["error"] == "Cannot change Try raylib UI while driving."
|
||||
assert fake_params.values["TryRaylibUI"] is False
|
||||
assert fake_params.writes == []
|
||||
|
||||
@@ -147,6 +147,13 @@ def _is_comma_device_runtime() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _raylib_ui_toggle_affects_device() -> bool:
|
||||
try:
|
||||
return HARDWARE.get_device_type() in ("tici", "tizi")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _get_param_key_type(params_obj, key):
|
||||
getter = getattr(params_obj, "get_key_type", None)
|
||||
if getter is None:
|
||||
@@ -4163,6 +4170,24 @@ def setup(app):
|
||||
if key not in allowed_keys:
|
||||
return jsonify({"error": f"Parameter '{key}' is not editable."}), 403
|
||||
|
||||
if key == "TryRaylibUI":
|
||||
enabled = str_val.strip() in ("1", "true", "True")
|
||||
if not _raylib_ui_toggle_affects_device():
|
||||
current_enabled = params.get_bool("TryRaylibUI")
|
||||
return jsonify({
|
||||
"message": "Try raylib UI is only available on tici/tizi devices.",
|
||||
"updated": {"TryRaylibUI": current_enabled},
|
||||
}), 200
|
||||
|
||||
if params.get_bool("IsOnroad"):
|
||||
return jsonify({"error": "Cannot change Try raylib UI while driving."}), 403
|
||||
|
||||
params.put_bool("TryRaylibUI", enabled)
|
||||
return jsonify({
|
||||
"message": f"{'Raylib' if enabled else 'Qt'} UI selected. UI will restart shortly.",
|
||||
"updated": {"TryRaylibUI": enabled},
|
||||
}), 200
|
||||
|
||||
# 1. Prevent changing the model or reboot-required toggles while the car is actively driving
|
||||
reboot_keys = {"Model", "DrivingModel", "AlwaysOnLateral", "DisableOpenpilotLongitudinal", "ForceTorqueController", "NNFF", "NNFFLite"}
|
||||
if key in reboot_keys and params.get_bool("IsOnroad"):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import operator
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
@@ -83,6 +84,86 @@ def run_speed_limit_vision(started: bool, params: Params, CP: car.CarParams, sta
|
||||
def run_navigationd(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool:
|
||||
return started and params.get("NavDestination") is not None
|
||||
|
||||
|
||||
class BigDeviceUIProcess:
|
||||
name = "ui"
|
||||
enabled = True
|
||||
sigkill = False
|
||||
daemon = False
|
||||
|
||||
def __init__(self, should_run, watchdog_max_dt=None):
|
||||
self.should_run_fn = should_run
|
||||
self.watchdog_max_dt = watchdog_max_dt
|
||||
self._started = False
|
||||
self._params = None
|
||||
self._active_process = None
|
||||
self._qt_process = NativeProcess("ui", "selfdrive/ui", ["./ui"], should_run, watchdog_max_dt=watchdog_max_dt)
|
||||
self._raylib_process = NativeProcess(
|
||||
"ui",
|
||||
".",
|
||||
["/usr/bin/env", "BIG=1", sys.executable, "-m", "openpilot.selfdrive.ui.ui"],
|
||||
should_run,
|
||||
watchdog_max_dt=watchdog_max_dt,
|
||||
)
|
||||
|
||||
@property
|
||||
def proc(self):
|
||||
return self._active_process.proc if self._active_process is not None else None
|
||||
|
||||
@property
|
||||
def shutting_down(self):
|
||||
return self._active_process.shutting_down if self._active_process is not None else False
|
||||
|
||||
def prepare(self) -> None:
|
||||
self._qt_process.prepare()
|
||||
|
||||
def should_run(self, started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool:
|
||||
self._started = started
|
||||
self._params = params
|
||||
return self.should_run_fn(started, params, CP, starpilot_toggles)
|
||||
|
||||
def _desired_process(self):
|
||||
return self._raylib_process if self._params is not None and self._params.get_bool("TryRaylibUI") else self._qt_process
|
||||
|
||||
def start(self) -> None:
|
||||
desired_process = self._desired_process()
|
||||
|
||||
# Never swap UI implementations mid-drive. Direct param writes while onroad
|
||||
# take effect the next time the device is offroad.
|
||||
if self._started and self._active_process is not None and self._active_process is not desired_process:
|
||||
desired_process = self._active_process
|
||||
|
||||
if self._active_process is not None and self._active_process is not desired_process:
|
||||
self._active_process.stop()
|
||||
|
||||
for process in (self._qt_process, self._raylib_process):
|
||||
if process is not desired_process and process.proc is not None:
|
||||
process.stop()
|
||||
|
||||
self._active_process = desired_process
|
||||
self._active_process.start()
|
||||
|
||||
def stop(self, retry: bool = True, block: bool = True, sig=None):
|
||||
ret = None
|
||||
for process in (self._qt_process, self._raylib_process):
|
||||
process_ret = process.stop(retry=retry, block=block, sig=sig)
|
||||
if process is self._active_process:
|
||||
ret = process_ret
|
||||
return ret
|
||||
|
||||
def restart(self) -> None:
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
def check_watchdog(self, started: bool) -> None:
|
||||
if self._active_process is not None:
|
||||
self._active_process.check_watchdog(started)
|
||||
|
||||
def get_process_state_msg(self):
|
||||
process = self._active_process or self._qt_process
|
||||
return process.get_process_state_msg()
|
||||
|
||||
|
||||
procs = [
|
||||
DaemonProcess("manage_athenad", "system.athena.manage_athenad", "AthenadPid"),
|
||||
|
||||
@@ -145,9 +226,9 @@ procs += [
|
||||
|
||||
device_type = HARDWARE.get_device_type()
|
||||
if device_type in ("tici", "tizi"):
|
||||
procs.append(NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=UI_WATCHDOG_MAX_DT))
|
||||
procs.append(BigDeviceUIProcess(always_run, watchdog_max_dt=UI_WATCHDOG_MAX_DT))
|
||||
else:
|
||||
# C4 (mici) runs the Python raylib UI path.
|
||||
# C4 (mici) already runs the Python raylib UI path; TryRaylibUI must not affect it.
|
||||
procs.append(PythonProcess("ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=UI_WATCHDOG_MAX_DT))
|
||||
|
||||
procs += [
|
||||
|
||||
@@ -4,12 +4,13 @@ import signal
|
||||
import time
|
||||
from pathlib import Path
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
from cereal import car
|
||||
from openpilot.common.params import Params
|
||||
import openpilot.system.manager.manager as manager
|
||||
from openpilot.system.manager.process import ensure_running
|
||||
from openpilot.system.manager.process_config import managed_processes, procs
|
||||
from openpilot.system.manager.process_config import BigDeviceUIProcess, managed_processes, procs
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
|
||||
os.environ['FAKEUPLOAD'] = "1"
|
||||
@@ -71,6 +72,40 @@ class FileBackedFakeParams:
|
||||
self.put(key, float(value))
|
||||
|
||||
|
||||
class FakeManagedProcess:
|
||||
def __init__(self):
|
||||
self.proc = None
|
||||
self.shutting_down = False
|
||||
self.starts = 0
|
||||
self.stops = 0
|
||||
|
||||
def prepare(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
if self.proc is not None:
|
||||
return
|
||||
|
||||
self.starts += 1
|
||||
self.shutting_down = False
|
||||
self.proc = SimpleNamespace(exitcode=None, pid=self.starts, is_alive=lambda: True)
|
||||
|
||||
def stop(self, retry=True, block=True, sig=None):
|
||||
if self.proc is None:
|
||||
return None
|
||||
|
||||
self.stops += 1
|
||||
self.shutting_down = False
|
||||
self.proc = None
|
||||
return 0
|
||||
|
||||
def check_watchdog(self, started):
|
||||
pass
|
||||
|
||||
def get_process_state_msg(self):
|
||||
return SimpleNamespace(name="ui")
|
||||
|
||||
|
||||
class TestManager:
|
||||
def setup_method(self):
|
||||
HARDWARE.set_power_save(False)
|
||||
@@ -96,6 +131,34 @@ class TestManager:
|
||||
assert names.index("the_galaxy") < ui_idx
|
||||
assert names.index("galaxy") < ui_idx
|
||||
|
||||
def test_big_device_ui_process_swaps_offroad_only(self, tmp_path):
|
||||
ui_process = BigDeviceUIProcess(lambda *args: True)
|
||||
qt_process = FakeManagedProcess()
|
||||
raylib_process = FakeManagedProcess()
|
||||
ui_process._qt_process = qt_process
|
||||
ui_process._raylib_process = raylib_process
|
||||
|
||||
params = FileBackedFakeParams(tmp_path / "params", {"TryRaylibUI": False})
|
||||
|
||||
assert ui_process.should_run(False, params, car.CarParams.new_message(), SimpleNamespace())
|
||||
ui_process.start()
|
||||
assert ui_process.proc is qt_process.proc
|
||||
assert qt_process.starts == 1
|
||||
assert raylib_process.starts == 0
|
||||
|
||||
params.put_bool("TryRaylibUI", True)
|
||||
assert ui_process.should_run(True, params, car.CarParams.new_message(), SimpleNamespace())
|
||||
ui_process.start()
|
||||
assert ui_process.proc is qt_process.proc
|
||||
assert qt_process.stops == 0
|
||||
assert raylib_process.starts == 0
|
||||
|
||||
assert ui_process.should_run(False, params, car.CarParams.new_message(), SimpleNamespace())
|
||||
ui_process.start()
|
||||
assert qt_process.stops == 1
|
||||
assert raylib_process.starts == 1
|
||||
assert ui_process.proc is raylib_process.proc
|
||||
|
||||
def test_blacklisted_procs(self):
|
||||
# TODO: ensure there are blacklisted procs until we have a dedicated test
|
||||
assert len(BLACKLIST_PROCS), "No blacklisted procs to test not_run"
|
||||
|
||||
Reference in New Issue
Block a user