From 364dbdbe5f8f6a7cb780138887a6378ce90abcbb Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 8 Nov 2026 13:37:18 -0800 Subject: [PATCH] Sentry: Tidy battery warning and status checks --- openpilot/starpilot/galaxy/sentry_events.py | 6 ++-- .../starpilot/galaxy/tests/test_settings.mjs | 6 ++-- .../starpilot/galaxy/web/css/settings.css | 9 +++--- openpilot/starpilot/galaxy/web/js/settings.js | 5 ++-- openpilot/starpilot/sentry_mode/README.md | 3 +- .../starpilot/sentry_mode/notifications.py | 6 ++-- openpilot/starpilot/sentry_mode/policy.py | 3 +- openpilot/starpilot/sentry_mode/runtime.py | 12 +++++--- openpilot/starpilot/sentry_mode/status.py | 14 ++++------ openpilot/starpilot/sentry_mode/storage.py | 10 ++++++- .../sentry_mode/tests/test_runtime.py | 28 ++++++++++++++++--- 11 files changed, 65 insertions(+), 37 deletions(-) diff --git a/openpilot/starpilot/galaxy/sentry_events.py b/openpilot/starpilot/galaxy/sentry_events.py index 2957f27818..88e2c936f2 100644 --- a/openpilot/starpilot/galaxy/sentry_events.py +++ b/openpilot/starpilot/galaxy/sentry_events.py @@ -5,7 +5,7 @@ import re from collections.abc import Callable from typing import Protocol, cast -from openpilot.starpilot.sentry_mode.storage import EventStore, StorageUnavailable +from openpilot.starpilot.sentry_mode.storage import BATTERY_FIELDS, EventStore, StorageUnavailable, valid_tenths MAX_RESPONSE_BYTES = 128 * 1024 @@ -40,13 +40,13 @@ class SentryEvents: if (type(value) is not dict or type(value.get("eventId")) is not str or ID.fullmatch(value["eventId"]) is None or type(value.get("kind")) is not str or value["kind"] not in ("warning", "alarm", "selfie", "battery") or - (value["kind"] == "battery" and not all(type(value.get(k)) is int for k in ("voltage", "cutoff"))) or + (value["kind"] == "battery" and not all(valid_tenths(value.get(k)) for k in BATTERY_FIELDS)) or type(value.get("wallTimeNs")) is not int or not 0 < value["wallTimeNs"] < 2**63): raise ValueError("Invalid motion event") events.append({"eventId": value["eventId"], "kind": value["kind"], "systemTimeMs": value["wallTimeNs"] // 1_000_000, **({"simulated": True} if value.get("simulated") else {}), - **({k: value[k] for k in ("voltage", "cutoff")} if value["kind"] == "battery" else {}), + **({k: value[k] for k in BATTERY_FIELDS} if value["kind"] == "battery" else {}), "images": cast(Callable[[str], list], self.store.images)(value["eventId"]) if hasattr(self.store, "images") else []}) result = {"schemaVersion": 1, "source": "local", "scanIncomplete": source["incomplete"], "capacity": source["capacity"], "events": events, diff --git a/openpilot/starpilot/galaxy/tests/test_settings.mjs b/openpilot/starpilot/galaxy/tests/test_settings.mjs index 05c8978fe3..cbb0f47cf6 100644 --- a/openpilot/starpilot/galaxy/tests/test_settings.mjs +++ b/openpilot/starpilot/galaxy/tests/test_settings.mjs @@ -424,11 +424,13 @@ for (const subtitle of ["Off: Motion monitoring is disabled.", "Not armed: Batte assert.equal(sentryMonitorStatus({ subtitle }).label, "Disarmed") assert.equal(sentryMonitorStatus({ subtitle }).detail, subtitle) } -assert.equal(sentryMonitorStatus({ subtitle: "Arming · 82s: Monitoring begins after 90 seconds parked." }).tone, "warning") -assert.equal(sentryMonitorStatus({ subtitle: "Arming · 82s: Monitoring begins after 90 seconds parked." }).label, "Arming") +const armingStatus = sentryMonitorStatus({ subtitle: "Arming · 82s: Monitoring begins after 90 seconds parked." }) +assert.equal(armingStatus.tone, "warning") +assert.equal(armingStatus.label, "Arming") const armedStatus = { subtitle: "Monitoring motion: Watching for movement while parked." } assert.equal(sentryMonitorStatus(armedStatus).label, "Armed") assert.equal(sentryMonitorStatus(armedStatus, "Connection lost").label, "Status unavailable") assert.equal(sentryMonitorStatus({ subtitle: "Waiting for monitor: The monitor has not responded recently." }).label, "Status unavailable") assert.equal(sentryMonitorStatus({ subtitle: "Motion monitor status is unavailable. Reconnecting automatically…" }).label, "Status unavailable") assert.equal(sentryMonitorStatus(null).label, "Status unavailable") +assert.equal(sentryMonitorStatus({ subtitle: 42 }).label, "Status unavailable") diff --git a/openpilot/starpilot/galaxy/web/css/settings.css b/openpilot/starpilot/galaxy/web/css/settings.css index c1573b95d4..293ddc80d1 100644 --- a/openpilot/starpilot/galaxy/web/css/settings.css +++ b/openpilot/starpilot/galaxy/web/css/settings.css @@ -742,8 +742,7 @@ html.gx-maker-open, html.gx-maker-open body { touch-action: manipulation; } } .gx-sentry-status { flex: 1 1 100%; order: 1; display: grid; gap: var(--sp-1); color: var(--on-surface-variant); font-size: var(--fs-sm); } -.gx-sentry-status__label { display: inline-flex; align-items: center; gap: var(--sp-2); width: fit-content; font-weight: 500; } -.gx-sentry-status__label::before { content: ""; width: 6px; height: 6px; flex: 0 0 6px; border-radius: 50%; background: var(--sentry-status-dot, currentColor); } -.gx-sentry-status--armed { --sentry-status-dot: var(--success); } -.gx-sentry-status--warning { --sentry-status-dot: var(--warning); } -.gx-sentry-status__detail { color: var(--on-surface-variant); font-size: var(--fs-sm); } +.gx-sentry-status__label { display: inline-flex; align-items: center; gap: var(--sp-2); width: fit-content; font-weight: var(--fw-medium); } +.gx-sentry-status .gx-status-dot { flex: none; background: currentColor; } +.gx-sentry-status--armed .gx-status-dot { background: var(--success); } +.gx-sentry-status--warning .gx-status-dot { background: var(--warning); } diff --git a/openpilot/starpilot/galaxy/web/js/settings.js b/openpilot/starpilot/galaxy/web/js/settings.js index b289ffdb5e..db038ed2ac 100644 --- a/openpilot/starpilot/galaxy/web/js/settings.js +++ b/openpilot/starpilot/galaxy/web/js/settings.js @@ -44,7 +44,7 @@ const activeSettings = new Set() export const settingsBusy = state => ["updating", "saving"].includes(state.status) || !!state.pending || !!state.reviewing export function sentryMonitorStatus(data, error = "") { - const subtitle = data?.subtitle || "" + const subtitle = typeof data?.subtitle === "string" ? data.subtitle : "" const label = subtitle.split(":", 1)[0] if (error || !subtitle || label.startsWith("Waiting for monitor") || label === "Unavailable" || subtitle.includes("status is unavailable")) return { label: "Status unavailable", tone: "warning", detail: error ? "Reconnecting to the motion monitor…" : subtitle || "Checking motion monitor…" } @@ -465,7 +465,6 @@ export const SettingsPage = {

Assign what the steering-wheel controls do.

Choose what uploads when the vehicle uses mobile data.

Adjust acceleration, braking and following for this driving style.

- Local settings are unavailable in preview.