From 9798511e2e81155b9c6c062af085db5f5650c45e Mon Sep 17 00:00:00 2001
From: firestar5683 <168790843+firestar5683@users.noreply.github.com>
Date: Thu, 6 Aug 2026 16:20:50 -0500
Subject: [PATCH] Gate PiP side camera behind Galaxy developer mode
Keep the PiP side-camera contribution from PR #83 opt-in and off-road configurable. Credit to Prabhaav Pillai for the original implementation.
---
selfdrive/ui/onroad/starpilot/pip_sidecam.py | 4 +--
.../the_galaxy/assets/components/sidebar.js | 26 ++++++++++++++++---
.../tools/device_settings_layout.json | 6 ++---
.../tests/test_device_settings_layout.py | 9 +++++--
starpilot/system/the_galaxy/the_galaxy.py | 21 +++++++++++++++
5 files changed, 56 insertions(+), 10 deletions(-)
diff --git a/selfdrive/ui/onroad/starpilot/pip_sidecam.py b/selfdrive/ui/onroad/starpilot/pip_sidecam.py
index d4f03a3da..533f28621 100644
--- a/selfdrive/ui/onroad/starpilot/pip_sidecam.py
+++ b/selfdrive/ui/onroad/starpilot/pip_sidecam.py
@@ -134,7 +134,7 @@ class PipSideCamera:
if not force and now - self._last_param_refresh < PARAM_REFRESH_INTERVAL:
return
self._last_param_refresh = now
- self._enabled = self._params.get_bool("PIPPreviewEnabled")
+ self._enabled = self._params.get_bool("PIPPreviewEnabled") and self._params.get_bool("GalaxyDeveloperMode")
self._show_on_blinker = self._params.get_bool("PIPPreviewShowOnBlinker")
self._show_on_bsm = self._params.get_bool("PIPPreviewShowOnBSM")
try:
@@ -281,4 +281,4 @@ class PipSideCamera:
self.texture_y = None
if self.texture_uv and self.texture_uv.id:
rl.unload_texture(self.texture_uv)
- self.texture_uv = None
\ No newline at end of file
+ self.texture_uv = None
diff --git a/starpilot/system/the_galaxy/assets/components/sidebar.js b/starpilot/system/the_galaxy/assets/components/sidebar.js
index a86858d43..12e6f948f 100644
--- a/starpilot/system/the_galaxy/assets/components/sidebar.js
+++ b/starpilot/system/the_galaxy/assets/components/sidebar.js
@@ -24,7 +24,7 @@ const MENU_ITEMS = {
{ name: "Testing Ground", link: "/testing_ground", icon: "bi-bezier2" },
{ name: "Troubleshoot", link: "/troubleshoot", icon: "bi-tools" },
{ name: "V-Adj Spot Monitor", link: "/manage_v_asm", icon: "bi-eye" },
- { name: "PiP Side Camera", link: "/manage_pip_sidecam", icon: "bi-badge-hd" },
+ { name: "PiP Side Camera", link: "/manage_pip_sidecam", icon: "bi-badge-hd", developerOnly: true },
{ name: "Theme Maker", link: "/theme_maker", icon: "bi-palette-fill" },
{ name: "Tmux Log", link: "/manage_tmux", icon: "bi-terminal" },
{ name: "Backup and Restore", link: "/manage_toggles", icon: "bi-arrow-repeat" },
@@ -33,6 +33,8 @@ const MENU_ITEMS = {
],
};
+let galaxyDeveloperMode = false;
+
function matchesPath(currentPath, link) {
if (link === "/") return currentPath === "/";
if (link === "/tuning" && currentPath === "/lateral_maneuvers") return true;
@@ -40,7 +42,7 @@ function matchesPath(currentPath, link) {
}
function buildSectionMarkup(section, links, currentPath) {
- const linksMarkup = links.map((link) => {
+ const linksMarkup = links.filter((link) => !link.developerOnly || galaxyDeveloperMode).map((link) => {
const active = matchesPath(currentPath, link.link) ? "active" : "";
return `
@@ -66,6 +68,21 @@ function buildSectionMarkup(section, links, currentPath) {
`;
}
+async function refreshGalaxyDeveloperMode() {
+ try {
+ const response = await fetch("/api/params/all", { cache: "no-store" });
+ if (!response.ok) return;
+ const values = await response.json();
+ const next = Boolean(values?.GalaxyDeveloperMode);
+ if (next !== galaxyDeveloperMode) {
+ galaxyDeveloperMode = next;
+ renderSidebarIntoShell();
+ }
+ } catch (error) {
+ console.warn("Unable to determine Galaxy Developer Mode:", error);
+ }
+}
+
function bindSidebarHandlers() {
const menuButton = document.getElementById("menu_button");
const underlay = document.getElementById("sidebarUnderlay");
@@ -136,6 +153,9 @@ function renderSidebarIntoShell(currentPath) {
}
export function Sidebar(currentPath) {
- setTimeout(() => renderSidebarIntoShell(currentPath), 0);
+ setTimeout(() => {
+ renderSidebarIntoShell(currentPath);
+ refreshGalaxyDeveloperMode();
+ }, 0);
return html``;
}
diff --git a/starpilot/system/the_galaxy/assets/components/tools/device_settings_layout.json b/starpilot/system/the_galaxy/assets/components/tools/device_settings_layout.json
index 58e5616a9..75256e6af 100644
--- a/starpilot/system/the_galaxy/assets/components/tools/device_settings_layout.json
+++ b/starpilot/system/the_galaxy/assets/components/tools/device_settings_layout.json
@@ -2147,7 +2147,7 @@
"is_parent_toggle": true,
"requires_nonempty_key": "PIPPreviewMask",
"disabled_reason": "Configure the PiP window mask first in Galaxy > PiP Side Camera",
- "settings_tier": "simple"
+ "settings_tier": "advanced"
},
{
"key": "PIPPreviewShowOnBlinker",
@@ -2156,7 +2156,7 @@
"data_type": "bool",
"ui_type": "toggle",
"parent_key": "PIPPreviewEnabled",
- "settings_tier": "simple"
+ "settings_tier": "advanced"
},
{
"key": "PIPPreviewShowOnBSM",
@@ -2165,7 +2165,7 @@
"data_type": "bool",
"ui_type": "toggle",
"parent_key": "PIPPreviewEnabled",
- "settings_tier": "simple"
+ "settings_tier": "advanced"
},
{
"key": "Compass",
diff --git a/starpilot/system/the_galaxy/tests/test_device_settings_layout.py b/starpilot/system/the_galaxy/tests/test_device_settings_layout.py
index f3f5ce816..ae462087f 100644
--- a/starpilot/system/the_galaxy/tests/test_device_settings_layout.py
+++ b/starpilot/system/the_galaxy/tests/test_device_settings_layout.py
@@ -86,7 +86,10 @@ def test_requested_simple_and_advanced_settings_tiers():
"Wheel Controls",
"Device & Data",
):
- assert {param["settings_tier"] for param in sections[section_name].values()} == {"simple"}
+ params = sections[section_name].values()
+ if section_name == "Visual (Display & UI)":
+ params = [param for param in params if not param["key"].startswith("PIPPreview")]
+ assert {param["settings_tier"] for param in params} == {"simple"}
for key in ("AlwaysOnLateral", "LaneChanges", "QOLLateral"):
assert lateral[key]["settings_tier"] == "simple"
@@ -161,7 +164,9 @@ def test_pip_preview_is_under_driving_screen_widgets_and_configured_only_in_gala
assert visual["PIPPreviewEnabled"]["parent_key"] == "CustomUI"
assert visual["PIPPreviewShowOnBlinker"]["parent_key"] == "PIPPreviewEnabled"
assert visual["PIPPreviewShowOnBSM"]["parent_key"] == "PIPPreviewEnabled"
- assert visual["PIPPreviewEnabled"]["settings_tier"] == "simple"
+ assert visual["PIPPreviewEnabled"]["settings_tier"] == "advanced"
+ assert visual["PIPPreviewShowOnBlinker"]["settings_tier"] == "advanced"
+ assert visual["PIPPreviewShowOnBSM"]["settings_tier"] == "advanced"
assert _declared_default("PIPPreviewEnabled") == "0"
assert _declared_default("PIPPreviewShowOnBlinker") == "0"
diff --git a/starpilot/system/the_galaxy/the_galaxy.py b/starpilot/system/the_galaxy/the_galaxy.py
index 53a38ade2..6a9bdc9dd 100644
--- a/starpilot/system/the_galaxy/the_galaxy.py
+++ b/starpilot/system/the_galaxy/the_galaxy.py
@@ -97,6 +97,7 @@ GITLAB_SUBMISSIONS_PROJECT_ID = "71992109"
GITLAB_TOKEN = os.environ.get("GITLAB_TOKEN", "")
LEGACY_LATERAL_METHOD_API_PREFIX = "/api/" + "".join(("f", "t", "m"))
VASM_CONFIGURATION_KEYS = {"VASMEnabled", "VASMConfidenceThreshold", "VASMSmoothSeconds", "VASMAnnotationConfig"}
+PIP_PREVIEW_CONFIGURATION_KEYS = {"PIPPreviewEnabled", "PIPPreviewMask", "PIPPreviewShowOnBlinker", "PIPPreviewShowOnBSM"}
MODEL_SMOOTHING_KEYS = {"LatSmoothSeconds", "LongSmoothSeconds"}
GALAXY_DEPS_PATH = "/data/galaxy_deps"
@@ -4511,6 +4512,12 @@ def setup(app):
if key in VASM_CONFIGURATION_KEYS and params.get_bool("IsOnroad"):
return jsonify({"error": "Cannot change V-ASM configuration while driving."}), 403
+ if key in PIP_PREVIEW_CONFIGURATION_KEYS:
+ if not params.get_bool("GalaxyDeveloperMode"):
+ return jsonify({"error": "PiP Side Camera is available only with Galaxy Developer Mode enabled."}), 403
+ if params.get_bool("IsOnroad"):
+ return jsonify({"error": "Cannot change PiP Side Camera configuration while driving."}), 403
+
if key in PANDA_FIRMWARE_TOGGLE_KEYS and params.get_bool("IsOnroad"):
return jsonify({"error": "Cannot flash Panda firmware while driving."}), 403
if key in PANDA_FIRMWARE_TOGGLE_KEYS and data.get(PANDA_FIRMWARE_CONFIRMATION_FIELD) is not True:
@@ -7716,6 +7723,10 @@ def setup(app):
@app.route("/api/pip_preview/snapshot", methods=["GET"])
def pip_preview_snapshot():
+ if not params.get_bool("GalaxyDeveloperMode"):
+ return jsonify({"error": "PiP Side Camera is available only with Galaxy Developer Mode enabled."}), 403
+ if params.get_bool("IsOnroad"):
+ return jsonify({"error": "Camera snapshots are unavailable while driving."}), 403
jpeg = _get_live_driver_jpeg()
if jpeg is not None:
return Response(jpeg, mimetype="image/jpeg")
@@ -7723,10 +7734,16 @@ def setup(app):
@app.route("/api/pip_preview/config", methods=["GET"])
def pip_preview_get_config():
+ if not params.get_bool("GalaxyDeveloperMode"):
+ return jsonify({"error": "PiP Side Camera is available only with Galaxy Developer Mode enabled."}), 403
return jsonify(_decode_json_object(params.get("PIPPreviewMask")))
@app.route("/api/pip_preview/config", methods=["POST"])
def pip_preview_save_config():
+ if not params.get_bool("GalaxyDeveloperMode"):
+ return jsonify({"error": "PiP Side Camera is available only with Galaxy Developer Mode enabled."}), 403
+ if params.get_bool("IsOnroad"):
+ return jsonify({"error": "Cannot change PiP Side Camera configuration while driving."}), 403
try:
config = _normalize_pip_preview_config(request.get_json(silent=True))
except ValueError as exc:
@@ -7738,6 +7755,10 @@ def setup(app):
@app.route("/api/pip_preview/config", methods=["DELETE"])
def pip_preview_delete_config():
+ if not params.get_bool("GalaxyDeveloperMode"):
+ return jsonify({"error": "PiP Side Camera is available only with Galaxy Developer Mode enabled."}), 403
+ if params.get_bool("IsOnroad"):
+ return jsonify({"error": "Cannot change PiP Side Camera configuration while driving."}), 403
params.put("PIPPreviewMask", {})
params.put_bool("PIPPreviewEnabled", False)
update_starpilot_toggles()