This commit is contained in:
firestar5683
2026-06-15 11:30:41 -05:00
parent e02d67c153
commit 838c7fdc9b
4 changed files with 189 additions and 12 deletions
@@ -0,0 +1,82 @@
import json
from types import SimpleNamespace
from openpilot.starpilot import starpilot_process
class FakeParams:
def __init__(self):
self.writes = []
def put(self, key, value):
self.writes.append((key, value))
class FakeThreadManager:
def run_with_lock(self, *args, **kwargs):
return None
class FakeThemeManager:
def update_active_theme(self, *args, **kwargs):
return None
class FakeModelManager:
def randomize_selected_model(self):
return None
def test_transition_offroad_skips_invalid_gps_persist():
params = FakeParams()
planner = SimpleNamespace(gps_position={
"latitude": 0.0,
"longitude": 0.0,
"bearing": 0.0,
"speed": 0.0,
"hasFix": False,
"updatedAtMonotonic": 1.0,
"updatedAtSec": 1.0,
})
toggles = SimpleNamespace(lock_doors_timer=0, random_themes=False)
starpilot_process.transition_offroad(
planner,
FakeModelManager(),
FakeThemeManager(),
FakeThreadManager(),
False,
None,
params,
toggles,
)
assert params.writes == []
def test_transition_offroad_persists_valid_gps():
params = FakeParams()
gps_position = {
"latitude": 41.0,
"longitude": -87.0,
"bearing": 90.0,
"speed": 12.0,
"hasFix": True,
"updatedAtMonotonic": 1.0,
"updatedAtSec": 2.0,
}
planner = SimpleNamespace(gps_position=gps_position)
toggles = SimpleNamespace(lock_doors_timer=0, random_themes=False)
starpilot_process.transition_offroad(
planner,
FakeModelManager(),
FakeThemeManager(),
FakeThreadManager(),
False,
None,
params,
toggles,
)
assert params.writes == [("LastGPSPosition", json.dumps(gps_position))]
+2 -1
View File
@@ -144,7 +144,8 @@ def sync_drive_stats(params, session):
print(f"Failed to sync drive stats: {exception}")
def transition_offroad(starpilot_planner, model_manager, theme_manager, thread_manager, time_validated, sm, params, starpilot_toggles):
params.put("LastGPSPosition", json.dumps(starpilot_planner.gps_position))
if gps_position_valid(starpilot_planner.gps_position):
params.put("LastGPSPosition", json.dumps(starpilot_planner.gps_position))
if starpilot_toggles.lock_doors_timer != 0:
thread_manager.run_with_lock(lock_doors, (starpilot_toggles.lock_doors_timer, sm, params), report=False)
@@ -0,0 +1,76 @@
import json
from openpilot.common.params import ParamKeyType
from openpilot.starpilot.system.the_pond import the_pond
class FakeParamsBackend:
def __init__(self, key_types=None, default_values=None, values=None):
self.key_types = key_types or {}
self.default_values = default_values or {}
self.values = values or {}
self.writes = []
def get_key_type(self, key):
return self.key_types[key]
def get_default_value(self, key):
return self.default_values.get(key)
def put(self, key, value):
self.writes.append((key, value))
self.values[key] = value
def get(self, key, block=False):
return self.values.get(key)
def test_params_compat_accepts_json_strings_for_json_keys():
backend = FakeParamsBackend(
key_types={"FavoriteDestinations": ParamKeyType.JSON},
default_values={"FavoriteDestinations": []},
)
compat = the_pond.ParamsCompat(backend)
compat.put("FavoriteDestinations", json.dumps([{"name": "Home"}]))
assert backend.writes == [("FavoriteDestinations", [{"name": "Home"}])]
def test_navigation_last_position_uses_recent_persisted_fix(monkeypatch):
recent_payload = json.dumps({
"latitude": 41.0,
"longitude": -87.0,
"hasFix": True,
"updatedAtSec": 10_000.0,
})
memory_backend = FakeParamsBackend(values={"LastGPSPosition": ""})
persisted_backend = FakeParamsBackend(values={"LastGPSPosition": recent_payload})
monkeypatch.setattr(the_pond, "params_memory", the_pond.ParamsCompat(memory_backend))
monkeypatch.setattr(the_pond, "params", the_pond.ParamsCompat(persisted_backend))
monkeypatch.setattr(the_pond.time, "time", lambda: 10_300.0)
monkeypatch.setattr(the_pond, "system_time_valid", lambda: True)
position = the_pond._get_navigation_last_position()
assert position["latitude"] == 41.0
assert position["longitude"] == -87.0
def test_navigation_last_position_rejects_stale_persisted_fix(monkeypatch):
stale_payload = json.dumps({
"latitude": 41.0,
"longitude": -87.0,
"hasFix": True,
"updatedAtSec": 10_000.0,
})
memory_backend = FakeParamsBackend(values={"LastGPSPosition": ""})
persisted_backend = FakeParamsBackend(values={"LastGPSPosition": stale_payload})
monkeypatch.setattr(the_pond, "params_memory", the_pond.ParamsCompat(memory_backend))
monkeypatch.setattr(the_pond, "params", the_pond.ParamsCompat(persisted_backend))
monkeypatch.setattr(the_pond.time, "time", lambda: 10_000.0 + the_pond.NAVIGATION_PERSISTED_LOCATION_MAX_AGE_SECONDS + 1.0)
monkeypatch.setattr(the_pond, "system_time_valid", lambda: True)
assert the_pond._get_navigation_last_position() is None
+29 -11
View File
@@ -313,6 +313,20 @@ class ParamsCompat:
typed_value = ""
else:
typed_value = str(value)
elif expected_type == ParamKeyType.JSON:
if isinstance(value, bytes):
value = value.decode("utf-8", errors="replace")
if isinstance(value, str):
stripped = value.strip()
if not stripped:
typed_value = self._params.get_default_value(key)
else:
typed_value = json.loads(stripped)
elif isinstance(value, tuple):
typed_value = list(value)
else:
typed_value = value
self._params.put(key, typed_value)
@@ -466,7 +480,8 @@ KEYS = {
}
NAVIGATION_MEMORY_LOCATION_STALE_SECONDS = 10.0
NAVIGATION_PERSISTED_LOCATION_BOOT_SKEW_SECONDS = 5.0
NAVIGATION_PERSISTED_LOCATION_FUTURE_SKEW_SECONDS = 60.0
NAVIGATION_PERSISTED_LOCATION_MAX_AGE_SECONDS = 24 * 60 * 60
TMUX_LOGS_PATH = Path("/data/tmux_logs")
@@ -524,7 +539,7 @@ def _last_gps_position_is_live(payload):
return (time.monotonic() - updated_at_monotonic) <= NAVIGATION_MEMORY_LOCATION_STALE_SECONDS
def _last_gps_position_is_current_boot(payload):
def _last_gps_position_is_recent(payload):
if not isinstance(payload, dict):
return False
@@ -537,11 +552,14 @@ def _last_gps_position_is_current_boot(payload):
return False
now_sec = time.time()
boot_started_at_sec = now_sec - time.monotonic()
if updated_at_sec > (now_sec + 60.0):
if updated_at_sec > (now_sec + NAVIGATION_PERSISTED_LOCATION_FUTURE_SKEW_SECONDS):
return False
return updated_at_sec >= (boot_started_at_sec - NAVIGATION_PERSISTED_LOCATION_BOOT_SKEW_SECONDS)
if not system_time_valid():
return True
age_sec = now_sec - updated_at_sec
return 0.0 <= age_sec <= NAVIGATION_PERSISTED_LOCATION_MAX_AGE_SECONDS
def _get_navigation_last_position():
@@ -550,7 +568,7 @@ def _get_navigation_last_position():
return memory_position
persisted_position = _parse_last_gps_position(params.get("LastGPSPosition", encoding="utf8") or "")
if _last_gps_position_is_current_boot(persisted_position):
if _last_gps_position_is_recent(persisted_position):
return persisted_position
return None
@@ -3640,7 +3658,7 @@ def setup(app):
destination,
)
params.put("NavDestination", json.dumps(destination))
params.put("ApiCache_NavDestinations", json.dumps(recent_destinations))
params.put("ApiCache_NavDestinations", recent_destinations)
return {"message": "Destination set"}
@app.route("/api/navigation/favorite", methods=["DELETE"])
@@ -3662,7 +3680,7 @@ def setup(app):
)
]
params.put("FavoriteDestinations", json.dumps(favorites))
params.put("FavoriteDestinations", favorites)
return jsonify(message="Destination removed from favorites!")
@app.route("/api/navigation/favorite", methods=["GET"])
@@ -3675,7 +3693,7 @@ def setup(app):
f["id"] = hashlib.sha1(raw.encode()).hexdigest()
changed = True
if changed:
params.put("FavoriteDestinations", json.dumps(favorites))
params.put("FavoriteDestinations", favorites)
return jsonify(favorites=favorites)
@app.route("/api/navigation/favorite", methods=["POST"])
@@ -3690,7 +3708,7 @@ def setup(app):
if not any(f.get("id") == new_fav["id"] for f in existing):
existing.append(new_fav)
params.put("FavoriteDestinations", json.dumps(existing))
params.put("FavoriteDestinations", existing)
return {"message": "Destination added to favorites!"}
@app.route("/api/navigation/favorite/rename", methods=["POST"])
@@ -3740,7 +3758,7 @@ def setup(app):
if not found:
return jsonify({"error": "Favorite not found"}), 404
params.put("FavoriteDestinations", json.dumps(existing_favorites))
params.put("FavoriteDestinations", existing_favorites)
return jsonify(message="Favorite updated successfully!")
@app.route("/api/navigation_key", methods=["DELETE"])