From 675227a8eb8dc6a320054d25fb4b132473ac3f7a Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:36:05 -0500 Subject: [PATCH] pond --- .../assets/components/tools/speed_limits.css | 30 +++++-- .../assets/components/tools/speed_limits.js | 82 ++++++++++++++++++- starpilot/system/the_pond/the_pond.py | 72 ++++++++++++++++ 3 files changed, 173 insertions(+), 11 deletions(-) diff --git a/starpilot/system/the_pond/assets/components/tools/speed_limits.css b/starpilot/system/the_pond/assets/components/tools/speed_limits.css index 160c55e0..9497c100 100644 --- a/starpilot/system/the_pond/assets/components/tools/speed_limits.css +++ b/starpilot/system/the_pond/assets/components/tools/speed_limits.css @@ -13,7 +13,7 @@ } .download-speed-limits-button + .download-speed-limits-button { - margin-top: var(--padding-sm); + margin-top: 0; } .download-speed-limits-button:hover { @@ -22,25 +22,39 @@ transform: var(--hover-scale-sm); } .download-speed-limits-button-wrapper { - height: calc(100% + 20px); - position: relative; + align-items: stretch; + display: flex; + flex-direction: column; + gap: var(--padding-sm); width: 100%; } .download-speed-limits-link { color: var(--text-color); font-size: var(--font-size-sm); - left: 50%; - margin-top: var(--border-radius-sm); - position: absolute; + text-align: center; text-decoration: underline; - top: 100%; - transform: translateX(-50%); white-space: nowrap; } +.download-speed-limits-note { + color: var(--secondary-fg); + font-size: var(--font-size-sm); + margin: 0; + text-align: center; +} + +.download-speed-limits-status { + color: var(--text-color); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-demi-bold); + margin: 0; + text-align: center; +} + .download-speed-limits-text { color: var(--text-color); + margin-bottom: 0; text-align: center; } diff --git a/starpilot/system/the_pond/assets/components/tools/speed_limits.js b/starpilot/system/the_pond/assets/components/tools/speed_limits.js index 31272d1e..490a41f4 100644 --- a/starpilot/system/the_pond/assets/components/tools/speed_limits.js +++ b/starpilot/system/the_pond/assets/components/tools/speed_limits.js @@ -1,4 +1,58 @@ -import { html } from "/assets/vendor/arrow-core.js" +import { html, reactive } from "/assets/vendor/arrow-core.js" + +const state = reactive({ + canProcessNow: false, + fetched: false, + loading: true, + processing: false, + reason: "", + status: "Checking...", + submitting: false, +}) + +let pollTimer = null + +async function fetchStatus() { + try { + const response = await fetch("/api/speed_limits/status") + const result = await response.json() + + state.canProcessNow = Boolean(result.canProcessNow) + state.processing = Boolean(result.processing) + state.reason = result.reason || "" + state.status = result.status || "Idle" + } catch (error) { + state.canProcessNow = false + state.processing = false + state.reason = "Failed to load processor status." + state.status = "Unavailable" + } + + state.loading = false +} + +async function handleProcessNow() { + if (state.submitting || state.processing || !state.canProcessNow) { + return + } + + state.submitting = true + try { + const response = await fetch("/api/speed_limits/process", { method: "POST" }) + const result = await response.json() + + if (response.ok) { + showSnackbar(result.message || "Speed limit processing started.") + } else { + showSnackbar(result.error || "Failed to start speed limit processing.", "error") + } + } catch (error) { + showSnackbar("Failed to start speed limit processing.", "error") + } + + state.submitting = false + fetchStatus() +} export function SpeedLimits() { function handleDownload() { @@ -10,16 +64,38 @@ export function SpeedLimits() { showSnackbar("Download started...") } + if (!state.fetched) { + state.fetched = true + fetchStatus() + + if (pollTimer === null) { + pollTimer = setInterval(fetchStatus, 3000) + } + } + return html`
Download Speed Limits

- Download speed limit data collected using "Speed Limit Filler". + Enable "Speed Limit Filler" on the device, drive to collect data, then process and download it here when parked.

+

+ ${() => state.loading ? "Checking processor status..." : `Processor Status: ${state.status}`} +

+ ${() => !state.loading && state.reason && state.reason !== state.status ? html` +

${state.reason}

+ ` : ""} diff --git a/starpilot/system/the_pond/the_pond.py b/starpilot/system/the_pond/the_pond.py index 860f0f1e..b6a23867 100644 --- a/starpilot/system/the_pond/the_pond.py +++ b/starpilot/system/the_pond/the_pond.py @@ -3839,6 +3839,78 @@ def setup(app): buffer.seek(0) return send_file(buffer, as_attachment=True, download_name="speed_limits.json", mimetype="application/json") + def _speed_limits_status_payload(): + status = params_memory.get("UpdateSpeedLimitsStatus", encoding="utf-8") or "" + processing = bool(status and status != "Completed!") + enabled = params.get_bool("SpeedLimitFiller") + is_onroad = params.get_bool("IsOnroad") + time_valid = system_time_valid() + + network_connected = True + try: + sm = messaging.SubMaster(["deviceState"], poll="deviceState") + sm.update(0) + network_connected = sm["deviceState"].networkType != log.DeviceState.NetworkType.none + except Exception: + pass + + overpass_requests = {} + try: + overpass_requests = json.loads(params.get("OverpassRequests", encoding="utf-8") or "{}") + except Exception: + pass + + current_day = datetime.now(timezone.utc).day + saved_day = int(overpass_requests.get("day", current_day) or current_day) + total_requests = int(overpass_requests.get("total_requests", 0) or 0) + max_requests = int(overpass_requests.get("max_requests", 10000) or 10000) + if saved_day != current_day: + total_requests = 0 + api_limit_hit = total_requests >= max_requests + + reason = "" + if not enabled: + reason = "Enable Speed Limit Filler on the device first." + elif processing: + reason = status + elif is_onroad: + reason = "Processing is only available while parked." + elif not time_valid: + reason = "System time is not valid yet." + elif not network_connected: + reason = "Connect the device to the internet first." + elif api_limit_hit: + reason = "Today's Overpass API request limit has been reached." + + return { + "apiLimitHit": api_limit_hit, + "canProcessNow": enabled and not processing and not is_onroad and time_valid and network_connected and not api_limit_hit, + "enabled": enabled, + "isOnroad": is_onroad, + "networkConnected": network_connected, + "processing": processing, + "reason": reason, + "status": status or "Idle", + "timeValid": time_valid, + "totalRequests": total_requests, + "maxRequests": max_requests, + } + + @app.route("/api/speed_limits/status", methods=["GET"]) + def speed_limits_status(): + return jsonify(_speed_limits_status_payload()), 200 + + @app.route("/api/speed_limits/process", methods=["POST"]) + def process_speed_limits(): + payload = _speed_limits_status_payload() + if not payload["canProcessNow"]: + return jsonify({"error": payload["reason"] or "Speed limit processing is unavailable right now."}), 409 + + params_memory.put("UpdateSpeedLimitsStatus", "Calculating...") + params_memory.put_bool("UpdateSpeedLimits", True) + + return jsonify({"message": "Speed limit processing started.", "status": "Calculating..."}), 202 + @app.route("/api/stats", methods=["GET"]) def get_stats(): build_metadata = get_build_metadata()