mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-20 15:54:13 +08:00
sentry things
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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"}:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user