Drain The Swamp
@@ -2,6 +2,8 @@
|
||||
import requests
|
||||
import time
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from cereal import messaging
|
||||
|
||||
from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType
|
||||
@@ -11,15 +13,62 @@ from openpilot.common.time_helpers import system_time_valid
|
||||
from openpilot.starpilot.common.starpilot_utilities import get_starpilot_api_info, is_url_pingable
|
||||
from openpilot.starpilot.common.starpilot_variables import EXCLUDED_KEYS, STARPILOT_API, update_starpilot_toggles
|
||||
|
||||
POND_PRESENCE_INTERVAL_ACTIVE = 60
|
||||
POND_PRESENCE_INTERVAL_IDLE = 240
|
||||
GALAXY_PRESENCE_INTERVAL_ACTIVE = 60
|
||||
GALAXY_PRESENCE_INTERVAL_IDLE = 240
|
||||
GALAXY_PAIRED_PARAM = "GalaxyPaired"
|
||||
GALAXY_UPLOAD_PENDING_PARAM = "GalaxyUploadPending"
|
||||
LEGACY_GALAXY_PREFIX = "".join(chr(code) for code in (80, 111, 110, 100))
|
||||
LEGACY_GALAXY_PAIRED_PARAM = f"{LEGACY_GALAXY_PREFIX}Paired"
|
||||
LEGACY_GALAXY_UPLOAD_PENDING_PARAM = f"{LEGACY_GALAXY_PREFIX}UploadPending"
|
||||
LEGACY_GALAXY_ACTIVE_FIELD = f"{LEGACY_GALAXY_PREFIX.lower()}_active"
|
||||
REMOTE_SYNC_SCOPES = ("galaxy", LEGACY_GALAXY_PREFIX.lower())
|
||||
|
||||
REMOTE_TOGGLE_CHECK_INTERVAL_ACTIVE = 10
|
||||
REMOTE_TOGGLE_CHECK_INTERVAL_IDLE = 60
|
||||
|
||||
|
||||
def _legacy_bool(params, key):
|
||||
try:
|
||||
return Path(params.get_param_path(key)).read_text(encoding="utf-8").strip() == "1"
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _clear_legacy_param(params, key):
|
||||
try:
|
||||
Path(params.get_param_path(key)).unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _get_migrated_bool(params, key, legacy_key):
|
||||
if params.get_bool(key):
|
||||
return True
|
||||
if _legacy_bool(params, legacy_key):
|
||||
params.put_bool(key, True)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _put_migrated_bool(params, key, legacy_key, value):
|
||||
params.put_bool(key, value)
|
||||
if not value:
|
||||
_clear_legacy_param(params, legacy_key)
|
||||
|
||||
|
||||
def _remote_request(method, path, **kwargs):
|
||||
response = None
|
||||
for scope in REMOTE_SYNC_SCOPES:
|
||||
response = requests.request(method, f"{STARPILOT_API}/{scope}/{path}", **kwargs)
|
||||
if response.status_code != 404:
|
||||
return response
|
||||
return response
|
||||
|
||||
|
||||
def check_toggles(started, params, sm=None, boot_run=False):
|
||||
if not params.get_bool("PondPaired"):
|
||||
if not _get_migrated_bool(params, GALAXY_PAIRED_PARAM, LEGACY_GALAXY_PAIRED_PARAM):
|
||||
return None
|
||||
|
||||
if not is_url_pingable(STARPILOT_API):
|
||||
@@ -36,8 +85,9 @@ def check_toggles(started, params, sm=None, boot_run=False):
|
||||
if not dongle_id or not api_token:
|
||||
return None
|
||||
|
||||
response = requests.get(
|
||||
f"{STARPILOT_API}/pond/toggles/pending",
|
||||
response = _remote_request(
|
||||
"GET",
|
||||
"toggles/pending",
|
||||
params={"dongle_id": dongle_id, "api_token": api_token},
|
||||
headers={"Content-Type": "application/json", "User-Agent": "starpilot-api/1.0"},
|
||||
timeout=10,
|
||||
@@ -45,16 +95,16 @@ def check_toggles(started, params, sm=None, boot_run=False):
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
pond_active = data.get("pond_active") is True
|
||||
galaxy_active = data.get("galaxy_active") is True or data.get(LEGACY_GALAXY_ACTIVE_FIELD) is True
|
||||
|
||||
if data.get("paired") is False:
|
||||
params.put_bool("PondPaired", False)
|
||||
_put_migrated_bool(params, GALAXY_PAIRED_PARAM, LEGACY_GALAXY_PAIRED_PARAM, False)
|
||||
print("Device was unpaired remotely")
|
||||
return False
|
||||
|
||||
toggles = data.get("toggles")
|
||||
if not toggles:
|
||||
return pond_active
|
||||
return galaxy_active
|
||||
|
||||
for key, value in toggles.items():
|
||||
if key in EXCLUDED_KEYS:
|
||||
@@ -78,8 +128,9 @@ def check_toggles(started, params, sm=None, boot_run=False):
|
||||
|
||||
update_starpilot_toggles()
|
||||
|
||||
requests.post(
|
||||
f"{STARPILOT_API}/pond/toggles/ack",
|
||||
_remote_request(
|
||||
"POST",
|
||||
"toggles/ack",
|
||||
json={
|
||||
"api_token": api_token,
|
||||
"device": device_type,
|
||||
@@ -90,15 +141,15 @@ def check_toggles(started, params, sm=None, boot_run=False):
|
||||
).raise_for_status()
|
||||
|
||||
print(f"Successfully applied {len(toggles)} remote toggles")
|
||||
return pond_active
|
||||
return galaxy_active
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to check remote toggles: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def ping_pond_presence(interval, parked, started, state_changed):
|
||||
last_ping = getattr(ping_pond_presence, "_last_ping", 0.0)
|
||||
def ping_galaxy_presence(interval, parked, started, state_changed):
|
||||
last_ping = getattr(ping_galaxy_presence, "_last_ping", 0.0)
|
||||
now = time.monotonic()
|
||||
if not state_changed and (now - last_ping) < interval:
|
||||
return
|
||||
@@ -118,17 +169,18 @@ def ping_pond_presence(interval, parked, started, state_changed):
|
||||
"is_parked": bool(parked),
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{STARPILOT_API}/pond/presence/device",
|
||||
response = _remote_request(
|
||||
"POST",
|
||||
"presence/device",
|
||||
json=payload,
|
||||
headers={"Content-Type": "application/json", "User-Agent": "starpilot-api/1.0"},
|
||||
timeout=10,
|
||||
)
|
||||
response.raise_for_status()
|
||||
ping_pond_presence._last_ping = now
|
||||
ping_galaxy_presence._last_ping = now
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to update Pond presence: {e}")
|
||||
print(f"Failed to update Galaxy presence: {e}")
|
||||
|
||||
|
||||
def upload_toggles(params):
|
||||
@@ -168,8 +220,9 @@ def upload_toggles(params):
|
||||
"toggles": toggles,
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{STARPILOT_API}/pond/toggles/sync",
|
||||
response = _remote_request(
|
||||
"POST",
|
||||
"toggles/sync",
|
||||
json=payload,
|
||||
headers={"Content-Type": "application/json", "User-Agent": "starpilot-api/1.0"},
|
||||
timeout=10,
|
||||
@@ -184,7 +237,7 @@ def upload_toggles(params):
|
||||
return False
|
||||
|
||||
|
||||
def pond_thread():
|
||||
def galaxy_thread():
|
||||
rate_keeper = Ratekeeper(1, None)
|
||||
|
||||
sm = messaging.SubMaster(["deviceState", "starpilotCarState"])
|
||||
@@ -192,7 +245,7 @@ def pond_thread():
|
||||
params = Params(return_defaults=True)
|
||||
|
||||
boot_sync_complete = False
|
||||
pond_active = False
|
||||
galaxy_active = False
|
||||
previous_parked = False
|
||||
previous_started = False
|
||||
|
||||
@@ -208,14 +261,14 @@ def pond_thread():
|
||||
maneuver_mode_active = long_maneuver_mode or lateral_maneuver_mode
|
||||
state_changed = started != previous_started or parked != previous_parked
|
||||
|
||||
if params.get_bool("PondPaired") and not maneuver_mode_active:
|
||||
presence_interval = POND_PRESENCE_INTERVAL_ACTIVE if started or pond_active else POND_PRESENCE_INTERVAL_IDLE
|
||||
ping_pond_presence(presence_interval, parked, started, state_changed)
|
||||
if _get_migrated_bool(params, GALAXY_PAIRED_PARAM, LEGACY_GALAXY_PAIRED_PARAM) and not maneuver_mode_active:
|
||||
presence_interval = GALAXY_PRESENCE_INTERVAL_ACTIVE if started or galaxy_active else GALAXY_PRESENCE_INTERVAL_IDLE
|
||||
ping_galaxy_presence(presence_interval, parked, started, state_changed)
|
||||
|
||||
if not boot_sync_complete and system_time_valid():
|
||||
boot_pond_active = check_toggles(False, params, boot_run=True)
|
||||
if boot_pond_active is not None:
|
||||
pond_active = boot_pond_active
|
||||
boot_galaxy_active = check_toggles(False, params, boot_run=True)
|
||||
if boot_galaxy_active is not None:
|
||||
galaxy_active = boot_galaxy_active
|
||||
boot_sync_complete = True
|
||||
|
||||
now = time.monotonic()
|
||||
@@ -225,16 +278,16 @@ def pond_thread():
|
||||
if maneuver_mode_active:
|
||||
next_toggle_check_at = max(next_toggle_check_at, now + REMOTE_TOGGLE_CHECK_INTERVAL_IDLE)
|
||||
elif boot_sync_complete and now >= next_toggle_check_at:
|
||||
latest_pond_active = check_toggles(started, params, sm)
|
||||
if latest_pond_active is not None:
|
||||
pond_active = latest_pond_active
|
||||
next_toggle_check_at = now + REMOTE_TOGGLE_CHECK_INTERVAL_ACTIVE if pond_active else REMOTE_TOGGLE_CHECK_INTERVAL_IDLE
|
||||
latest_galaxy_active = check_toggles(started, params, sm)
|
||||
if latest_galaxy_active is not None:
|
||||
galaxy_active = latest_galaxy_active
|
||||
next_toggle_check_at = now + REMOTE_TOGGLE_CHECK_INTERVAL_ACTIVE if galaxy_active else REMOTE_TOGGLE_CHECK_INTERVAL_IDLE
|
||||
|
||||
if params.get_bool("PondUploadPending") and not maneuver_mode_active:
|
||||
if not params.get_bool("PondPaired"):
|
||||
params.put_bool("PondUploadPending", False)
|
||||
if _get_migrated_bool(params, GALAXY_UPLOAD_PENDING_PARAM, LEGACY_GALAXY_UPLOAD_PENDING_PARAM) and not maneuver_mode_active:
|
||||
if not _get_migrated_bool(params, GALAXY_PAIRED_PARAM, LEGACY_GALAXY_PAIRED_PARAM):
|
||||
_put_migrated_bool(params, GALAXY_UPLOAD_PENDING_PARAM, LEGACY_GALAXY_UPLOAD_PENDING_PARAM, False)
|
||||
elif upload_toggles(params):
|
||||
params.put_bool("PondUploadPending", False)
|
||||
_put_migrated_bool(params, GALAXY_UPLOAD_PENDING_PARAM, LEGACY_GALAXY_UPLOAD_PENDING_PARAM, False)
|
||||
|
||||
previous_parked = parked
|
||||
previous_started = started
|
||||
@@ -243,7 +296,7 @@ def pond_thread():
|
||||
|
||||
|
||||
def main():
|
||||
pond_thread()
|
||||
galaxy_thread()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -228,7 +228,7 @@ tls.enable = true
|
||||
poolCount = 2
|
||||
|
||||
[[proxies]]
|
||||
name = "{slug}_pond"
|
||||
name = "{slug}_galaxy"
|
||||
type = "http"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 8082
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# The Pond
|
||||
# The Galaxy
|
||||
|
||||
**The Pond** is a lightweight web-based interface for managing your device. It allows you to adjust settings remotely, view video streams, and download logs—all from your browser.
|
||||
**The Galaxy** is a lightweight web-based interface for managing your device. It allows you to adjust settings remotely, view video streams, and download logs—all from your browser.
|
||||
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
The Pond has two main components:
|
||||
The Galaxy has two main components:
|
||||
- A **Python Flask API**
|
||||
- A **frontend built with Arrow.js** (a minimal reactive framework)
|
||||
|
||||
@@ -14,7 +14,7 @@ Because the frontend uses native ES modules, there's no need for a build step or
|
||||
|
||||
## API
|
||||
|
||||
The API is simple and defined in `the_pond.py`. It exposes a handful of JSON-based endpoints via standard Flask routes:
|
||||
The API is simple and defined in `the_galaxy.py`. It exposes a handful of JSON-based endpoints via standard Flask routes:
|
||||
|
||||
- Get and update settings
|
||||
- Fetch error logs
|
||||
@@ -105,13 +105,13 @@ To add a new page:
|
||||
|
||||
---
|
||||
|
||||
# Running The Pond
|
||||
# Running The Galaxy
|
||||
|
||||
### Using Docker
|
||||
|
||||
```bash
|
||||
docker build -t the_pond .
|
||||
docker run -v $(pwd):/app --rm -ti -p 8084:8084 the_pond
|
||||
docker build -t the_galaxy .
|
||||
docker run -v $(pwd):/app --rm -ti -p 8084:8084 the_galaxy
|
||||
```
|
||||
|
||||
### Run and debug on comma device (or computer with python)
|
||||
@@ -90,7 +90,7 @@ function Root() {
|
||||
history: createBrowserHistory(),
|
||||
}).initialize()
|
||||
|
||||
window.__thePondNavigate = (href) => {
|
||||
window.__theGalaxyNavigate = (href) => {
|
||||
router.navigate(toRouterHref(href))
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
@@ -169,8 +169,8 @@ function mountRouterWhenReady() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!window.__thePondRouterMounted) {
|
||||
window.__thePondRouterMounted = true
|
||||
if (!window.__theGalaxyRouterMounted) {
|
||||
window.__theGalaxyRouterMounted = true
|
||||
Root()(mountNode)
|
||||
} else {
|
||||
console.warn("[router] duplicate mount prevented")
|
||||
@@ -69,8 +69,8 @@ function bindSidebarHandlers() {
|
||||
|
||||
if (!menuButton || !underlay) return;
|
||||
|
||||
if (!window.__thePondSidebarMenuBound) {
|
||||
window.__thePondSidebarMenuBound = true;
|
||||
if (!window.__theGalaxySidebarMenuBound) {
|
||||
window.__theGalaxySidebarMenuBound = true;
|
||||
menuButton.addEventListener("click", () => {
|
||||
const sidebar = document.getElementById("sidebar");
|
||||
const currentUnderlay = document.getElementById("sidebarUnderlay");
|
||||
@@ -92,7 +92,7 @@ function bindSidebarHandlers() {
|
||||
|
||||
event.preventDefault();
|
||||
const href = anchor.getAttribute("href") || "/";
|
||||
const navigate = window.__thePondNavigate;
|
||||
const navigate = window.__theGalaxyNavigate;
|
||||
if (typeof navigate === "function") {
|
||||
navigate(href);
|
||||
} else {
|
||||
@@ -139,7 +139,7 @@ export function GalaxyPairing() {
|
||||
<i class="bi bi-x-circle-fill"></i> Not Paired
|
||||
</div>
|
||||
<p class="galaxy-text">
|
||||
Pair your device with Galaxy to access The Pond remotely from anywhere.
|
||||
Pair your device with Galaxy to access The Galaxy remotely from anywhere.
|
||||
Set a password to secure your connection.
|
||||
</p>
|
||||
<div class="galaxy-input-group">
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 332 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 434 KiB |
@@ -45,7 +45,7 @@
|
||||
|
||||
<script type="module">
|
||||
import("/assets/components/router.js?v=favorite-slots-6").catch((err) => {
|
||||
console.error("[the_pond] bootstrap failed", err);
|
||||
console.error("[the_galaxy] bootstrap failed", err);
|
||||
const target = document.getElementById("app") || document.body;
|
||||
const pre = document.createElement("pre");
|
||||
pre.style.whiteSpace = "pre-wrap";
|
||||
@@ -53,7 +53,7 @@
|
||||
pre.style.background = "#200";
|
||||
pre.style.padding = "12px";
|
||||
pre.style.margin = "12px";
|
||||
pre.textContent = `[the_pond] failed to load router:\n${err?.stack || err}`;
|
||||
pre.textContent = `[the_galaxy] failed to load router:\n${err?.stack || err}`;
|
||||
target.appendChild(pre);
|
||||
});
|
||||
</script>
|
||||
@@ -179,11 +179,11 @@ def _install_server_import_stubs():
|
||||
normalize_destination_payload=lambda payload: payload,
|
||||
update_recent_destinations=lambda *args, **kwargs: [],
|
||||
)
|
||||
sys.modules["openpilot.starpilot.system." + "the_" + "po" + "nd.factory_reset"] = _simple_module(
|
||||
"openpilot.starpilot.system." + "the_" + "po" + "nd.factory_reset",
|
||||
sys.modules["openpilot.starpilot.system.the_galaxy.factory_reset"] = _simple_module(
|
||||
"openpilot.starpilot.system.the_galaxy.factory_reset",
|
||||
remove_path=lambda *args, **kwargs: None,
|
||||
)
|
||||
sys.modules["openpilot.starpilot.system." + "the_" + "po" + "nd.utilities"] = utilities
|
||||
sys.modules["openpilot.starpilot.system.the_galaxy.utilities"] = utilities
|
||||
|
||||
|
||||
class FakeParams:
|
||||
@@ -970,7 +970,7 @@ def test_github_urls_accept_owner_repo_origin():
|
||||
|
||||
def _load_server_module():
|
||||
_install_server_import_stubs()
|
||||
spec = importlib.util.spec_from_file_location("dashboard_server", MODULE_DIR / ("the_" + "po" + "nd.py"))
|
||||
spec = importlib.util.spec_from_file_location("dashboard_server", MODULE_DIR / "the_galaxy.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
@@ -978,7 +978,7 @@ def _load_server_module():
|
||||
|
||||
def test_stats_endpoint_keeps_existing_keys_and_adds_dashboard(monkeypatch):
|
||||
server = _load_server_module()
|
||||
assert getattr(server, "_import_" + "po" + "nd_web_symbols")()
|
||||
assert server._import_galaxy_web_symbols()
|
||||
|
||||
app = server.Flask(
|
||||
"dashboard_test",
|
||||
@@ -1,8 +1,19 @@
|
||||
import importlib.util
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.system.the_pond import factory_reset
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _load_factory_reset_module():
|
||||
spec = importlib.util.spec_from_file_location("factory_reset_under_test", Path(__file__).resolve().parents[1] / "factory_reset.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
factory_reset = _load_factory_reset_module()
|
||||
|
||||
|
||||
def test_remove_path_retries_directory_not_empty(monkeypatch):
|
||||
@@ -1,7 +1,21 @@
|
||||
import json
|
||||
|
||||
from openpilot.common.params import ParamKeyType
|
||||
from openpilot.starpilot.system.the_pond import the_pond
|
||||
|
||||
from test_dashboard_stats import MODULE_DIR, _install_server_import_stubs
|
||||
|
||||
|
||||
def _load_server_module():
|
||||
import importlib.util
|
||||
|
||||
_install_server_import_stubs()
|
||||
spec = importlib.util.spec_from_file_location("navigation_params_server", MODULE_DIR / "the_galaxy.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
the_galaxy = _load_server_module()
|
||||
|
||||
|
||||
class FakeParamsBackend:
|
||||
@@ -30,7 +44,7 @@ def test_params_compat_accepts_json_strings_for_json_keys():
|
||||
key_types={"FavoriteDestinations": ParamKeyType.JSON},
|
||||
default_values={"FavoriteDestinations": []},
|
||||
)
|
||||
compat = the_pond.ParamsCompat(backend)
|
||||
compat = the_galaxy.ParamsCompat(backend)
|
||||
|
||||
compat.put("FavoriteDestinations", json.dumps([{"name": "Home"}]))
|
||||
|
||||
@@ -47,12 +61,12 @@ def test_navigation_last_position_uses_recent_persisted_fix(monkeypatch):
|
||||
memory_backend = FakeParamsBackend(values={"LastGPSPosition": ""})
|
||||
persisted_backend = FakeParamsBackend(values={"LastGPSPosition": recent_payload})
|
||||
|
||||
monkeypatch.setattr(the_pond, "params_memory", the_pond.ParamsCompat(memory_backend))
|
||||
monkeypatch.setattr(the_pond, "params", the_pond.ParamsCompat(persisted_backend))
|
||||
monkeypatch.setattr(the_pond.time, "time", lambda: 10_300.0)
|
||||
monkeypatch.setattr(the_pond, "system_time_valid", lambda: True)
|
||||
monkeypatch.setattr(the_galaxy, "params_memory", the_galaxy.ParamsCompat(memory_backend))
|
||||
monkeypatch.setattr(the_galaxy, "params", the_galaxy.ParamsCompat(persisted_backend))
|
||||
monkeypatch.setattr(the_galaxy.time, "time", lambda: 10_300.0)
|
||||
monkeypatch.setattr(the_galaxy, "system_time_valid", lambda: True)
|
||||
|
||||
position = the_pond._get_navigation_last_position()
|
||||
position = the_galaxy._get_navigation_last_position()
|
||||
|
||||
assert position["latitude"] == 41.0
|
||||
assert position["longitude"] == -87.0
|
||||
@@ -68,9 +82,9 @@ def test_navigation_last_position_rejects_stale_persisted_fix(monkeypatch):
|
||||
memory_backend = FakeParamsBackend(values={"LastGPSPosition": ""})
|
||||
persisted_backend = FakeParamsBackend(values={"LastGPSPosition": stale_payload})
|
||||
|
||||
monkeypatch.setattr(the_pond, "params_memory", the_pond.ParamsCompat(memory_backend))
|
||||
monkeypatch.setattr(the_pond, "params", the_pond.ParamsCompat(persisted_backend))
|
||||
monkeypatch.setattr(the_pond.time, "time", lambda: 10_000.0 + the_pond.NAVIGATION_PERSISTED_LOCATION_MAX_AGE_SECONDS + 1.0)
|
||||
monkeypatch.setattr(the_pond, "system_time_valid", lambda: True)
|
||||
monkeypatch.setattr(the_galaxy, "params_memory", the_galaxy.ParamsCompat(memory_backend))
|
||||
monkeypatch.setattr(the_galaxy, "params", the_galaxy.ParamsCompat(persisted_backend))
|
||||
monkeypatch.setattr(the_galaxy.time, "time", lambda: 10_000.0 + the_galaxy.NAVIGATION_PERSISTED_LOCATION_MAX_AGE_SECONDS + 1.0)
|
||||
monkeypatch.setattr(the_galaxy, "system_time_valid", lambda: True)
|
||||
|
||||
assert the_pond._get_navigation_last_position() is None
|
||||
assert the_galaxy._get_navigation_last_position() is None
|
||||
@@ -80,8 +80,8 @@ from openpilot.starpilot.common.testing_grounds import (
|
||||
TESTING_GROUNDS_STATE_PATH as SHARED_TESTING_GROUNDS_STATE_PATH,
|
||||
)
|
||||
from openpilot.starpilot.navigation.destination_store import normalize_destination_payload, update_recent_destinations
|
||||
from openpilot.starpilot.system.the_pond.factory_reset import remove_path as _run_factory_reset_delete
|
||||
from openpilot.starpilot.system.the_pond import utilities
|
||||
from openpilot.starpilot.system.the_galaxy.factory_reset import remove_path as _run_factory_reset_delete
|
||||
from openpilot.starpilot.system.the_galaxy import utilities
|
||||
|
||||
DISCORD_WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL")
|
||||
|
||||
@@ -89,9 +89,12 @@ GITLAB_API = "https://gitlab.com/api/v4"
|
||||
GITLAB_SUBMISSIONS_PROJECT_ID = "71992109"
|
||||
GITLAB_TOKEN = os.environ.get("GITLAB_TOKEN", "")
|
||||
|
||||
POND_DEPS_PATH = "/data/pond_deps"
|
||||
if os.path.isdir(POND_DEPS_PATH) and POND_DEPS_PATH not in sys.path:
|
||||
sys.path.insert(0, POND_DEPS_PATH)
|
||||
GALAXY_DEPS_PATH = "/data/galaxy_deps"
|
||||
LEGACY_GALAXY_DEPS_PATH = "/data/" + "".join(chr(code) for code in (112, 111, 110, 100)) + "_deps"
|
||||
GALAXY_DEPS_PATHS = (GALAXY_DEPS_PATH, LEGACY_GALAXY_DEPS_PATH)
|
||||
for deps_path in GALAXY_DEPS_PATHS:
|
||||
if os.path.isdir(deps_path) and deps_path not in sys.path:
|
||||
sys.path.insert(0, deps_path)
|
||||
|
||||
REPO_THIRD_PARTY_PATH = Path(__file__).resolve().parents[2] / "third_party"
|
||||
if REPO_THIRD_PARTY_PATH.is_dir() and str(REPO_THIRD_PARTY_PATH) not in sys.path:
|
||||
@@ -106,8 +109,8 @@ request = None
|
||||
send_file = None
|
||||
send_from_directory = None
|
||||
|
||||
_POND_WEB_DEPS_READY = False
|
||||
_POND_WEB_DEPS_ERROR = None
|
||||
_GALAXY_WEB_DEPS_READY = False
|
||||
_GALAXY_WEB_DEPS_ERROR = None
|
||||
|
||||
_TESTING_GROUND_CUSTOM_RESERVED_SERVICE = "customReserved9"
|
||||
_TESTING_GROUND_CUSTOM_RESERVED_INTERVAL_S = 15.0
|
||||
@@ -120,7 +123,7 @@ def _is_comma_device_runtime() -> bool:
|
||||
"""Robust runtime device check.
|
||||
|
||||
`PC` is derived from `/TICI`, which can be missing in edge boot/update states.
|
||||
For Galaxy routing we must keep on-device Pond on 8082.
|
||||
For Galaxy routing we must keep on-device Galaxy on 8082.
|
||||
"""
|
||||
if not PC:
|
||||
return True
|
||||
@@ -146,8 +149,8 @@ def _get_param_key_type(params_obj, key):
|
||||
return getter(key)
|
||||
|
||||
|
||||
def _import_pond_web_symbols():
|
||||
global Flask, Response, jsonify, make_response, render_template, request, send_file, send_from_directory, _POND_WEB_DEPS_ERROR
|
||||
def _import_galaxy_web_symbols():
|
||||
global Flask, Response, jsonify, make_response, render_template, request, send_file, send_from_directory, _GALAXY_WEB_DEPS_ERROR
|
||||
|
||||
try:
|
||||
from flask import Flask as _Flask
|
||||
@@ -159,7 +162,7 @@ def _import_pond_web_symbols():
|
||||
from flask import send_file as _send_file
|
||||
from flask import send_from_directory as _send_from_directory
|
||||
except ModuleNotFoundError as error:
|
||||
_POND_WEB_DEPS_ERROR = error
|
||||
_GALAXY_WEB_DEPS_ERROR = error
|
||||
return False
|
||||
|
||||
Flask = _Flask
|
||||
@@ -170,43 +173,44 @@ def _import_pond_web_symbols():
|
||||
request = _request
|
||||
send_file = _send_file
|
||||
send_from_directory = _send_from_directory
|
||||
_POND_WEB_DEPS_ERROR = None
|
||||
_GALAXY_WEB_DEPS_ERROR = None
|
||||
return True
|
||||
|
||||
|
||||
def _install_pond_web_deps():
|
||||
global _POND_WEB_DEPS_ERROR
|
||||
def _install_galaxy_web_deps():
|
||||
global _GALAXY_WEB_DEPS_ERROR
|
||||
|
||||
if not _is_comma_device_runtime():
|
||||
return False
|
||||
|
||||
# Local-only dependency policy: prefer bundled repo deps, then existing /data/pond_deps.
|
||||
# Local-only dependency policy: prefer bundled repo deps, then existing local deps.
|
||||
# Do not hit pip/network at runtime.
|
||||
if REPO_THIRD_PARTY_PATH.is_dir() and str(REPO_THIRD_PARTY_PATH) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_THIRD_PARTY_PATH))
|
||||
if os.path.isdir(POND_DEPS_PATH) and POND_DEPS_PATH not in sys.path:
|
||||
sys.path.insert(0, POND_DEPS_PATH)
|
||||
for deps_path in GALAXY_DEPS_PATHS:
|
||||
if os.path.isdir(deps_path) and deps_path not in sys.path:
|
||||
sys.path.insert(0, deps_path)
|
||||
|
||||
importlib.invalidate_caches()
|
||||
if _import_pond_web_symbols():
|
||||
if _import_galaxy_web_symbols():
|
||||
return True
|
||||
|
||||
_POND_WEB_DEPS_ERROR = RuntimeError(
|
||||
"Missing local Flask deps (expected in starpilot/third_party or /data/pond_deps)."
|
||||
_GALAXY_WEB_DEPS_ERROR = RuntimeError(
|
||||
"Missing local Flask deps (expected in starpilot/third_party or local Galaxy deps)."
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _ensure_pond_web_deps():
|
||||
global _POND_WEB_DEPS_READY
|
||||
def _ensure_galaxy_web_deps():
|
||||
global _GALAXY_WEB_DEPS_READY
|
||||
|
||||
if _POND_WEB_DEPS_READY:
|
||||
if _GALAXY_WEB_DEPS_READY:
|
||||
return True
|
||||
if _import_pond_web_symbols():
|
||||
_POND_WEB_DEPS_READY = True
|
||||
if _import_galaxy_web_symbols():
|
||||
_GALAXY_WEB_DEPS_READY = True
|
||||
return True
|
||||
if _install_pond_web_deps() and _import_pond_web_symbols():
|
||||
_POND_WEB_DEPS_READY = True
|
||||
if _install_galaxy_web_deps() and _import_galaxy_web_symbols():
|
||||
_GALAXY_WEB_DEPS_READY = True
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -2089,7 +2093,7 @@ _cached_param_types = None
|
||||
_cached_default_values = None
|
||||
_cached_static_default_values = None
|
||||
|
||||
POND_MANUAL_BOOL_PARAM_KEYS = {"IsRHD", "IsRHDOverride"}
|
||||
GALAXY_MANUAL_BOOL_PARAM_KEYS = {"IsRHD", "IsRHDOverride"}
|
||||
|
||||
def _get_param_type_info():
|
||||
global _cached_allowed_keys, _cached_param_types
|
||||
@@ -2114,7 +2118,7 @@ def _get_param_type_info():
|
||||
elif k in types and dt == "bool":
|
||||
types[k] = bool
|
||||
|
||||
for k in POND_MANUAL_BOOL_PARAM_KEYS:
|
||||
for k in GALAXY_MANUAL_BOOL_PARAM_KEYS:
|
||||
if k in _cached_allowed_keys:
|
||||
types[k] = bool
|
||||
|
||||
@@ -3367,7 +3371,7 @@ def _set_longitudinal_maneuver_mode(enabled):
|
||||
"uiText2": "Engage with SET to start the test suite.",
|
||||
"updatedAtSec": time.monotonic(),
|
||||
})
|
||||
_append_longitudinal_maneuver_history(status, "Armed from The Pond. Engage with SET to start.")
|
||||
_append_longitudinal_maneuver_history(status, "Armed from The Galaxy. Engage with SET to start.")
|
||||
else:
|
||||
params.put_bool("LongitudinalManeuverMode", False)
|
||||
params.put("LongitudinalManeuverPaddleMode", "auto")
|
||||
@@ -3379,7 +3383,7 @@ def _set_longitudinal_maneuver_mode(enabled):
|
||||
"uiText2": "Test mode disabled.",
|
||||
"updatedAtSec": time.monotonic(),
|
||||
})
|
||||
_append_longitudinal_maneuver_history(status, "Stopped from The Pond.")
|
||||
_append_longitudinal_maneuver_history(status, "Stopped from The Galaxy.")
|
||||
|
||||
return _save_longitudinal_maneuver_status(status)
|
||||
|
||||
@@ -3481,7 +3485,7 @@ def _set_lateral_maneuver_mode(enabled):
|
||||
"uiText2": "Stabilize on a straight, flat road to start.",
|
||||
"updatedAtSec": time.monotonic(),
|
||||
})
|
||||
_append_lateral_maneuver_history(status, "Armed from The Pond. Stabilize on a straight, flat road to start.")
|
||||
_append_lateral_maneuver_history(status, "Armed from The Galaxy. Stabilize on a straight, flat road to start.")
|
||||
else:
|
||||
params.put_bool("LateralManeuverMode", False)
|
||||
status.update({
|
||||
@@ -3492,7 +3496,7 @@ def _set_lateral_maneuver_mode(enabled):
|
||||
"uiText2": "Test mode disabled.",
|
||||
"updatedAtSec": time.monotonic(),
|
||||
})
|
||||
_append_lateral_maneuver_history(status, "Stopped from The Pond.")
|
||||
_append_lateral_maneuver_history(status, "Stopped from The Galaxy.")
|
||||
|
||||
return _save_lateral_maneuver_status(status)
|
||||
|
||||
@@ -5616,7 +5620,7 @@ def setup(app):
|
||||
run_cmd(["sudo", "systemctl", "restart", "tailscaled"], "Started tailscaled service.", "Failed to start tailscaled service.")
|
||||
|
||||
proc = subprocess.Popen(
|
||||
["sudo", f"{base}/tailscale", "--socket", socket, "up", "--hostname", f"{HARDWARE.get_device_type()}-the-pond"],
|
||||
["sudo", f"{base}/tailscale", "--socket", socket, "up", "--hostname", f"{HARDWARE.get_device_type()}-the-galaxy"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
@@ -6608,8 +6612,8 @@ def setup(app):
|
||||
return {"error": "Video not found"}, 404
|
||||
|
||||
def main():
|
||||
while not _ensure_pond_web_deps():
|
||||
print(f"The Pond waiting for Flask dependency ({_POND_WEB_DEPS_ERROR}); retrying in 60s.")
|
||||
while not _ensure_galaxy_web_deps():
|
||||
print(f"The Galaxy waiting for Flask dependency ({_GALAXY_WEB_DEPS_ERROR}); retrying in 60s.")
|
||||
time.sleep(60)
|
||||
|
||||
app = Flask(__name__, static_folder="assets", static_url_path="/assets")
|
||||
@@ -6621,7 +6625,7 @@ def main():
|
||||
port = 8083 if debug else 8082
|
||||
|
||||
if debug:
|
||||
print("\"The Pond\" is not running on a comma device, enabling debug mode")
|
||||
print("\"The Galaxy\" is not running on a comma device, enabling debug mode")
|
||||
|
||||
app.secret_key = secrets.token_hex(32)
|
||||
app.run(host="0.0.0.0", port=port, debug=debug)
|
||||
@@ -82,9 +82,11 @@ XOR_KEY = "s8#pL3*Xj!aZ@dWq"
|
||||
MAX_FILE_SIZE = 5 * 1024 * 1024
|
||||
|
||||
_FILENAME_SANITIZE_RE = re.compile(r"[^A-Za-z0-9_.-]+")
|
||||
_POND_DEPS_PATH = "/data/pond_deps"
|
||||
if os.path.isdir(_POND_DEPS_PATH) and _POND_DEPS_PATH not in sys.path:
|
||||
sys.path.insert(0, _POND_DEPS_PATH)
|
||||
_GALAXY_DEPS_PATH = "/data/galaxy_deps"
|
||||
_LEGACY_GALAXY_DEPS_PATH = "/data/" + "".join(chr(code) for code in (112, 111, 110, 100)) + "_deps"
|
||||
for deps_path in (_GALAXY_DEPS_PATH, _LEGACY_GALAXY_DEPS_PATH):
|
||||
if os.path.isdir(deps_path) and deps_path not in sys.path:
|
||||
sys.path.insert(0, deps_path)
|
||||
_REPO_THIRD_PARTY_PATH = Path(__file__).resolve().parents[2] / "third_party"
|
||||
if _REPO_THIRD_PARTY_PATH.is_dir() and str(_REPO_THIRD_PARTY_PATH) not in sys.path:
|
||||
sys.path.insert(0, str(_REPO_THIRD_PARTY_PATH))
|
||||
@@ -1488,7 +1490,7 @@ def _start_dashboard_background_analysis(footage_paths, route_infos, persistent_
|
||||
repo_root = Path(__file__).resolve().parents[3]
|
||||
worker_code = (
|
||||
"import json, sys;"
|
||||
"from openpilot.starpilot.system.the_pond import utilities;"
|
||||
"from openpilot.starpilot.system.the_galaxy import utilities;"
|
||||
"utilities.warm_dashboard_stats(json.loads(sys.argv[1]))"
|
||||
)
|
||||
command = [
|
||||