sentry things

This commit is contained in:
firestar5683
2026-08-16 11:29:41 -05:00
parent 604a5ec3d1
commit 730ba6e14d
3 changed files with 116 additions and 2 deletions
@@ -11,7 +11,9 @@ const state = reactive({
params: {},
status: {},
event: {},
liveCapture: {},
testBusy: false,
liveBusy: false,
pushBusy: false,
})
@@ -87,6 +89,36 @@ async function sendTestEvent() {
}
}
async function readJsonResponse(response) {
const body = await response.text()
if (!body) return {}
try {
return JSON.parse(body)
} catch {
throw new Error(`Galaxy returned an unexpected ${response.status} response. Check the device connection or Galaxy tunnel.`)
}
}
async function viewLive() {
if (state.liveBusy) return
state.liveBusy = true
try {
const response = await fetch(galaxyPath("/api/sentry/live"), { cache: "no-store" })
const payload = await readJsonResponse(response)
if (!response.ok) {
showSnackbar(payload.error || "Live camera capture failed.")
return
}
state.liveCapture = payload
showSnackbar("Live camera snapshot captured.")
} catch (error) {
showSnackbar(error.message || "Network error — is the device reachable?")
} finally {
state.liveBusy = false
}
}
async function enablePush() {
if (state.pushBusy) return
state.pushBusy = true
@@ -135,6 +167,24 @@ function renderEvent() {
`
}
function renderLiveCapture() {
const capture = state.liveCapture || {}
const imageUrls = Array.isArray(capture.imageUrls) ? capture.imageUrls : []
if (imageUrls.length === 0) return html`<p class="sentry-empty">No live snapshot captured yet.</p>`
const cacheKey = encodeURIComponent(capture.capturedAt || "")
return html`
<p class="sentry-muted">Captured ${capture.capturedAt || "just now"}.</p>
<div class="sentry-image-grid">
${imageUrls.map((url, index) => html`
<a href="${galaxyPath(`${url}?t=${cacheKey}`)}" target="_blank" rel="noopener">
<img src="${galaxyPath(`${url}?t=${cacheKey}`)}" alt="Live Sentry camera ${index + 1}" />
</a>
`)}
</div>
`
}
export function SentryMode() {
startPolling()
const remote = isGalaxyTunnel()
@@ -198,9 +248,23 @@ export function SentryMode() {
</button>
</div>
<p class="sentry-muted">Enable notifications once, then use the test push to verify Galaxy can reach this browser even when the page is not active.</p>
<p class="sentry-muted">iPhone users: add Galaxy to your Home Screen as a web app before enabling notifications. iOS web push requires the Home Screen web app.</p>
`}
</section>
<section class="sentry-card">
<div class="sentry-card-heading">
<div>
<h3>Live view</h3>
<p class="sentry-muted">Capture one still from both cameras while parked.</p>
</div>
<button class="sentry-button sentry-button-secondary" @click="${viewLive}" disabled="${() => state.liveBusy}">
${() => state.liveBusy ? "Capturing…" : "View live"}
</button>
</div>
${() => renderLiveCapture()}
</section>
<section class="sentry-card">
<div class="sentry-card-heading">
<div>
+50
View File
@@ -623,6 +623,39 @@ def _capture_sentry_test_images(event_id: str) -> list[str]:
return paths
_SENTRY_LIVE_CAPTURE_LOCK = threading.Lock()
_SENTRY_LIVE_EVENT_ID = "live"
def _capture_sentry_live_images() -> list[str]:
from openpilot.system.camerad.snapshot import jpeg_write, snapshot
params.put_bool("SentryModeCapture", True)
try:
rear, front = snapshot(allow_existing=True, include_front=True)
except Exception:
cloudlog.exception("Galaxy: live Sentry snapshot failed")
return []
finally:
params.put_bool("SentryModeCapture", False)
if rear is None and front is None:
return []
directory = _sentry_event_roots()[0] / _SENTRY_LIVE_EVENT_ID
directory.mkdir(parents=True, exist_ok=True)
paths = []
if rear is not None:
path = directory / "wide.jpg"
jpeg_write(str(path), rear)
paths.append(str(path))
if front is not None:
path = directory / "driver.jpg"
jpeg_write(str(path), front)
paths.append(str(path))
return paths
_SENTRY_PUSH_LOCK = threading.Lock()
_SENTRY_PUSH_PRIVATE_KEY_NAME = "sentry_vapid_private.pem"
_SENTRY_PUSH_SUBSCRIPTIONS_NAME = "sentry_push_subscriptions.json"
@@ -7005,6 +7038,23 @@ def setup(app):
return jsonify({"error": "Sentry image not found."}), 404
return send_file(image_path, mimetype="image/jpeg", max_age=0)
@app.route("/api/sentry/live", methods=["GET"])
def sentry_live():
if not params.get_bool("IsOffroad"):
return jsonify({"error": "Live Sentry view is only available while parked."}), 409
with _SENTRY_LIVE_CAPTURE_LOCK:
image_paths = _capture_sentry_live_images()
if not image_paths:
return jsonify({"error": "Unable to capture the Sentry cameras."}), 503
captured_at = datetime.now(timezone.utc).isoformat()
event = _public_sentry_event({
"eventId": _SENTRY_LIVE_EVENT_ID,
"imagePaths": image_paths,
})
return jsonify({"capturedAt": captured_at, "imageUrls": event["imageUrls"]})
@app.route("/api/sentry/test", methods=["POST"])
def sentry_test():
if request.remote_addr not in {None, "127.0.0.1", "::1"}:
+2 -2
View File
@@ -73,14 +73,14 @@ def get_snapshots(frame="roadCameraState", front_frame="driverCameraState"):
return rear, front
def snapshot(allow_existing=False):
def snapshot(allow_existing=False, include_front=None):
params = Params()
if (not params.get_bool("IsOffroad")) or params.get_bool("IsTakingSnapshot"):
print("Already taking snapshot")
return None, None
front_camera_allowed = params.get_bool("RecordFront")
front_camera_allowed = params.get_bool("RecordFront") if include_front is None else bool(include_front)
params.put_bool("IsTakingSnapshot", True)
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