From b567d9fc909d5f8f5df54af67c87f8f8c6375a50 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:09:58 -0500 Subject: [PATCH] optimize --- common/time_helpers.py | 7 ++- release/pack.py | 3 +- .../the_galaxy/tests/test_dashboard_stats.py | 17 ++++++ starpilot/system/the_galaxy/utilities.py | 56 +++++++++++++++++-- system/camerad/snapshot.py | 7 +++ system/manager/process_config.py | 2 +- system/manager/test/test_process_config.py | 37 +++++++++++- system/sentryd/sentryd.py | 19 ++++++- 8 files changed, 138 insertions(+), 10 deletions(-) diff --git a/common/time_helpers.py b/common/time_helpers.py index c709182d4..b421e7353 100644 --- a/common/time_helpers.py +++ b/common/time_helpers.py @@ -4,13 +4,16 @@ from pathlib import Path MIN_DATE = datetime.datetime(year=2025, month=2, day=21) MAX_DATE = datetime.datetime(year=2035, month=1, day=1) +def _utc_now_naive(): + return datetime.datetime.now(datetime.UTC).replace(tzinfo=None) + def min_date(): # on systemd systems, the default time is the systemd build time systemd_path = Path("/lib/systemd/systemd") if systemd_path.exists(): - d = datetime.datetime.fromtimestamp(systemd_path.stat().st_mtime) + d = datetime.datetime.fromtimestamp(systemd_path.stat().st_mtime, datetime.UTC).replace(tzinfo=None) return max(MIN_DATE, d + datetime.timedelta(days=1)) return MIN_DATE def system_time_valid(): - return min_date() < datetime.datetime.now() < MAX_DATE + return min_date() < _utc_now_naive() < MAX_DATE diff --git a/release/pack.py b/release/pack.py index 92ff68fe7..b20a3ea26 100755 --- a/release/pack.py +++ b/release/pack.py @@ -43,7 +43,8 @@ if __name__ == '__main__': with tempfile.TemporaryDirectory() as tmp: for directory in DIRS: - shutil.copytree(BASEDIR + '/' + directory, tmp + '/' + directory, symlinks=False, dirs_exist_ok=True, copy_function=copy) + shutil.copytree(BASEDIR + '/' + directory, tmp + '/' + directory, symlinks=False, ignore_dangling_symlinks=True, + dirs_exist_ok=True, copy_function=copy) entry = f'{args.module}:{args.entrypoint}' zipapp.create_archive(tmp, target=args.output, interpreter=INTERPRETER, main=entry) diff --git a/starpilot/system/the_galaxy/tests/test_dashboard_stats.py b/starpilot/system/the_galaxy/tests/test_dashboard_stats.py index cecea8a32..b9428fb5d 100644 --- a/starpilot/system/the_galaxy/tests/test_dashboard_stats.py +++ b/starpilot/system/the_galaxy/tests/test_dashboard_stats.py @@ -584,6 +584,23 @@ def test_route_listing_uses_all_segment_times_when_segment_zero_was_touched(tmp_ assert end == "2026-07-18T07:22:00" +def test_route_listing_prefers_logged_time_after_offline_clock_reset(tmp_path, monkeypatch): + route_start = utilities.datetime(2026, 7, 18, 7, 19, 0) + route_name = "000011e3--6e01289631" + segment = tmp_path / f"{route_name}--0" + segment.mkdir() + (segment / "qlog.zst").write_bytes(b"placeholder") + + stale_time = utilities.datetime(2025, 7, 18, 7, 20, 0).timestamp() + os.utime(segment, (stale_time, stale_time)) + monkeypatch.setattr(utilities, "_route_logged_start_time", lambda _path: route_start) + + routes = utilities._list_dashboard_routes([tmp_path]) + + assert routes[0]["startedAt"] == route_start + assert routes[0]["timeSource"] == utilities.DASHBOARD_TIME_SOURCE_LOG + + def test_top_models_are_ranked_from_persisted_usage_not_favorites(): params = FakeParams({ "AvailableModels": "orion,vega,atlas,nova", diff --git a/starpilot/system/the_galaxy/utilities.py b/starpilot/system/the_galaxy/utilities.py index 443c13a00..1617b7a1a 100644 --- a/starpilot/system/the_galaxy/utilities.py +++ b/starpilot/system/the_galaxy/utilities.py @@ -994,9 +994,18 @@ def _select_dashboard_segment_candidate(candidates): return next((candidate for candidate in candidates if _segment_has_dashboard_log(candidate)), candidates[0]) -def _estimate_route_started_at(segments): +def _estimate_route_start_details(segments): estimates = [] + time_source = DASHBOARD_TIME_SOURCE_FILESYSTEM for segment in segments: + log_path = get_route_log_path(segment.get("path")) + if log_path is not None: + logged_time = _route_logged_start_time(log_path) + if logged_time is not None and _dashboard_time_is_valid(logged_time, require_recent=True): + estimates.append(logged_time.timestamp()) + time_source = DASHBOARD_TIME_SOURCE_LOG + continue + segment_num = max(0, _safe_int(segment.get("num", 0), 0)) # Segment directory mtimes normally land at the end of their one-minute segment. estimate = _segment_mtime(segment.get("path")) - (segment_num + 1) * 60 @@ -1004,7 +1013,11 @@ def _estimate_route_started_at(segments): if parsed is not None: estimates.append(parsed.timestamp()) # Dashboard analysis can touch a segment directory later, but cannot make it older. - return datetime.fromtimestamp(min(estimates)) if estimates else None + return (datetime.fromtimestamp(min(estimates)), time_source) if estimates else (None, "") + + +def _estimate_route_started_at(segments): + return _estimate_route_start_details(segments)[0] def _list_dashboard_routes(footage_paths, limit=DASHBOARD_ROUTE_SCAN_LIMIT): @@ -1049,7 +1062,7 @@ def _list_dashboard_routes(footage_paths, limit=DASHBOARD_ROUTE_SCAN_LIMIT): if not segments: continue - started_at = _estimate_route_started_at(segments) + started_at, time_source = _estimate_route_start_details(segments) route_infos.append({ "name": route["name"], @@ -1057,7 +1070,7 @@ def _list_dashboard_routes(footage_paths, limit=DASHBOARD_ROUTE_SCAN_LIMIT): "segmentCount": len(segments), "startedAt": started_at, "modifiedAt": route["modified_at"], - "timeSource": DASHBOARD_TIME_SOURCE_FILESYSTEM if started_at is not None else "", + "timeSource": time_source, }) route_infos.sort(key=lambda route: ( @@ -2901,6 +2914,37 @@ def get_route_log_path(path): return None +def _route_logged_start_time(log_path, reader=None): + """Recover a route's wall-clock start when filesystem time was reset at boot.""" + try: + if reader is None: + from openpilot.tools.lib.logreader import _LogFileReader + reader = _LogFileReader(str(log_path)) + + first_mono_time = None + for message in reader: + mono_time = _safe_float(getattr(message, "logMonoTime", 0), 0.0) / 1e9 + if mono_time <= 0.0: + continue + first_mono_time = mono_time if first_mono_time is None else min(first_mono_time, mono_time) + + message_type = _message_type(message) + payload = _message_payload(message, message_type) + wall_time = _wall_time_seconds_from_payload(payload) + if wall_time is None: + wall_time = _wall_time_seconds_from_payload(message) + if wall_time is None: + continue + + start_seconds = wall_time - (mono_time - first_mono_time) + start_time = datetime.fromtimestamp(start_seconds) + if _dashboard_time_is_valid(start_time): + return start_time + except Exception: + return None + return None + + def get_route_start_time(path): log_path = get_route_log_path(path) if log_path is None: @@ -2917,6 +2961,10 @@ def get_route_start_time(path): if modified_time <= 0: return None + logged_time = _route_logged_start_time(log_path) + if logged_time is not None: + return logged_time + return datetime.fromtimestamp(modified_time) def get_routes_names(footage_path): diff --git a/system/camerad/snapshot.py b/system/camerad/snapshot.py index 35e86d570..370cce8be 100755 --- a/system/camerad/snapshot.py +++ b/system/camerad/snapshot.py @@ -85,6 +85,11 @@ def snapshot(allow_existing=False, include_front=None): set_offroad_alert("Offroad_IsTakingSnapshot", True) time.sleep(2.0) # Give hardwared time to read the param, or if just started give camerad time to start + if not params.get_bool("IsOffroad"): + params.put_bool("IsTakingSnapshot", False) + set_offroad_alert("Offroad_IsTakingSnapshot", False) + return None, None + # Check if camerad is already started camerad_already_running = False try: @@ -106,6 +111,8 @@ def snapshot(allow_existing=False, include_front=None): frame = "wideRoadCameraState" front_frame = "driverCameraState" if front_camera_allowed else None rear, front = get_snapshots(frame, front_frame) + if not params.get_bool("IsOffroad"): + rear, front = None, None finally: if not camerad_already_running: managed_processes['camerad'].stop() diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 6d55e9f2f..2ae8bf309 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -69,7 +69,7 @@ def sensord_run(started: bool, params: Params, CP: car.CarParams, starpilot_togg return started or params.get_bool("SentryModeEnabled") def camera_run(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool: - return driverview(started, params, CP, starpilot_toggles) or params.get_bool("SentryModeCapture") + return driverview(started, params, CP, starpilot_toggles) or (not started and params.get_bool("SentryModeCapture")) def livestream(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool: return params.get_bool("IsLiveStreaming") diff --git a/system/manager/test/test_process_config.py b/system/manager/test/test_process_config.py index 872ebd7a9..617706cb2 100644 --- a/system/manager/test/test_process_config.py +++ b/system/manager/test/test_process_config.py @@ -3,7 +3,7 @@ from types import SimpleNamespace import pytest from cereal import car -from openpilot.system.manager.process_config import allow_uploads, managed_processes +from openpilot.system.manager.process_config import allow_uploads, camera_run, managed_processes, sentry_mode class FakeParams: @@ -36,3 +36,38 @@ def test_allow_uploads(started, no_uploads, no_onroad_uploads, always_allow_uplo def test_uploader_runs_at_background_priority(): assert managed_processes["uploader"].nice == 19 + + +class CameraParams: + def __init__(self, capture: bool): + self.capture = capture + + def get_bool(self, key: str) -> bool: + assert key in {"IsDriverViewEnabled", "SentryModeCapture"} + return self.capture if key == "SentryModeCapture" else False + + +@pytest.mark.parametrize( + "started,capture,expected", + [ + (False, True, True), + (False, False, False), + (True, False, True), + ], +) +def test_camera_run_preserves_onroad_camera_and_offroad_sentry_capture(started, capture, expected): + assert camera_run(started, CameraParams(capture), car.CarParams.new_message(), SimpleNamespace()) is expected + + +class SentryParams: + def __init__(self, enabled: bool): + self.enabled = enabled + + def get_bool(self, key: str) -> bool: + assert key == "SentryModeEnabled" + return self.enabled + + +@pytest.mark.parametrize("started,enabled,expected", [(True, True, False), (False, True, True), (False, False, False)]) +def test_sentry_process_is_offroad_only(started, enabled, expected): + assert sentry_mode(started, SentryParams(enabled), car.CarParams.new_message(), SimpleNamespace()) is expected diff --git a/system/sentryd/sentryd.py b/system/sentryd/sentryd.py index d025b64d0..8967dc349 100644 --- a/system/sentryd/sentryd.py +++ b/system/sentryd/sentryd.py @@ -49,7 +49,7 @@ def _utc_now() -> str: class SentryMode: def __init__(self, params: Params | None = None, sm=None, clock=time.monotonic): self.params = params or Params(return_defaults=True) - self.sm = sm or messaging.SubMaster(["accelerometer"]) + self.sm = sm if sm is not None else messaging.SubMaster(["accelerometer", "deviceState"]) self.clock = clock self.detector = MotionDetector( sensitivity=SENSITIVITY, @@ -63,6 +63,13 @@ class SentryMode: self.armed = False self._last_status = None + def _is_onroad(self) -> bool: + try: + device_state = self.sm["deviceState"] + except (KeyError, TypeError, AttributeError): + return False + return device_state is not None and bool(getattr(device_state, "started", False)) + def _write_status(self, state: str, **extra) -> None: status_values = {"state": state, **extra} if status_values == self._last_status: @@ -132,6 +139,9 @@ class SentryMode: threading.Thread(target=publish, name="sentryd-galaxy-publish", daemon=True).start() def _handle_detection(self, kind: str) -> None: + if self._is_onroad(): + return + event_id = f"{int(time.time())}-{uuid4().hex[:8]}" event = { "eventId": event_id, @@ -147,6 +157,10 @@ class SentryMode: self._publish_event(event) def update(self) -> None: + if self._is_onroad(): + self._write_status("disabled", reason="onroad") + return + now = self.clock() if now - self.started_at < ARM_DELAY_SECONDS: self._write_status("arming", secondsRemaining=max(0, int(ARM_DELAY_SECONDS - (now - self.started_at)))) @@ -174,6 +188,9 @@ class SentryMode: self._write_status("starting") while self.params.get_bool("SentryModeEnabled"): self.sm.update(0) + if self._is_onroad(): + self._write_status("disabled", reason="onroad") + break self.update() time.sleep(LOOP_INTERVAL_SECONDS) self._write_status("disabled")