mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 20:43:46 +08:00
Add a StarPort section to the Developer page with the new pairing switch and an H.265 setting
The Developer page gets a heading row, StarPort, that groups the StarPort settings: the New pairing experience switch (on unless turned off), Stay on Wi-Fi (moved under the heading, same key, label and default) and Allow H.265 video (off by default, daemon setting allow_hevc). The heading is a row whose key starts with heading:, projected with heading=true. It has no choices, is never available and cannot be changed, and Galaxy draws it as a label. The new pairing switch is a plain file next to the other StarPort data rather than a Params key, so it reads and changes with the StarPort service down and needs no device rebuild; absent or unreadable means on.
This commit is contained in:
@@ -6,7 +6,8 @@ from openpilot.starpilot.longitudinal.voacc import KEY, DESCRIPTION
|
||||
from openpilot.starpilot.ui.calibration_reset import calibration_reset
|
||||
from openpilot.starpilot.saved_document import commit_exact
|
||||
from openpilot.starpilot.saved_source import read_saved
|
||||
from openpilot.starpilot.ui.feature_settings_state import FeatureRow, FeatureSettingsState
|
||||
from openpilot.starpilot.system.starport import new_setup
|
||||
from openpilot.starpilot.ui.feature_settings_state import HEADING_PREFIX, FeatureRow, FeatureSettingsState
|
||||
|
||||
|
||||
STARPORT_WIFI_KEY = "StarPortDedicatedWifi"
|
||||
@@ -14,6 +15,13 @@ STARPORT_WIFI_DESCRIPTION = ("Experimental. Join the car's Wi-Fi on a second int
|
||||
"phone hotspot during wireless StarPort. Applies from the next StarPort connection.")
|
||||
CALIBRATION_RESET = "reset_calibration"
|
||||
CALIBRATION_RESET_DESCRIPTION = "Clear camera and steering calibration. The device recalibrates on the next drive."
|
||||
STARPORT_HEADING_KEY = HEADING_PREFIX + "starport"
|
||||
STARPORT_NEW_SETUP_KEY = "StarPortNewSetup"
|
||||
STARPORT_NEW_SETUP_DESCRIPTION = ("Guided StarPort setup with a live checklist of what is being tried. Turn off to use the classic " +
|
||||
"setup screens. Applies the next time the setup screen opens.")
|
||||
STARPORT_HEVC_KEY = "StarPortAllowHevc"
|
||||
STARPORT_HEVC_DESCRIPTION = ("Experimental. Only for a car that offers H.265 and no H.264 video. Cars that offer H.264 are not " +
|
||||
"affected. Applies from the next StarPort connection.")
|
||||
DRIVER_MONITORING_RESET = "reset_driver_monitoring"
|
||||
DRIVER_MONITORING_RESET_DESCRIPTION = "Clear saved wheel-side detection. Automatic detection restarts on the next drive."
|
||||
|
||||
@@ -25,14 +33,40 @@ class DeveloperSettingsOwner:
|
||||
self.panda_wake = panda_wake
|
||||
self.starport = starport # returns the StarPort daemon client
|
||||
|
||||
def _starport_enabled(self):
|
||||
def _starport_status(self):
|
||||
if self.starport is None:
|
||||
return None
|
||||
try:
|
||||
return bool(self.starport().call("status")["status"].get("dedicated_wifi_interface"))
|
||||
status = self.starport().call("status")["status"]
|
||||
return status if isinstance(status, dict) else None
|
||||
except (OSError, RuntimeError, ValueError, KeyError, TypeError):
|
||||
return None # the service is off; there is nothing to configure
|
||||
|
||||
def _starport_enabled(self):
|
||||
status = self._starport_status()
|
||||
return None if status is None else bool(status.get("dedicated_wifi_interface"))
|
||||
|
||||
def _starport_hevc(self):
|
||||
status = self._starport_status()
|
||||
return None if status is None else bool(status.get("allow_hevc"))
|
||||
|
||||
def _starport_rows(self, parked):
|
||||
"""The StarPort section. The switch back to classic setup is always listed; the daemon's settings only while it runs."""
|
||||
new_setup_on = new_setup.enabled()
|
||||
held = "Park the vehicle to change this setting."
|
||||
rows = [FeatureRow(STARPORT_HEADING_KEY, "StarPort", "", available=False),
|
||||
FeatureRow(STARPORT_NEW_SETUP_KEY, "StarPort: New pairing experience", "On" if new_setup_on else "Off",
|
||||
b"1" if new_setup_on else b"0", ("Off", "On"), available=parked,
|
||||
reason=STARPORT_NEW_SETUP_DESCRIPTION if parked else held, default_value="On")]
|
||||
status = self._starport_status()
|
||||
if status is not None:
|
||||
enabled, hevc = bool(status.get("dedicated_wifi_interface")), bool(status.get("allow_hevc"))
|
||||
rows.append(FeatureRow(STARPORT_WIFI_KEY, "StarPort: Stay on Wi-Fi", "On" if enabled else "Off", b"1" if enabled else b"0",
|
||||
("Off", "On"), available=parked, reason=STARPORT_WIFI_DESCRIPTION if parked else held, default_value="On"))
|
||||
rows.append(FeatureRow(STARPORT_HEVC_KEY, "StarPort: Allow H.265 video", "On" if hevc else "Off", b"1" if hevc else b"0",
|
||||
("Off", "On"), available=parked, reason=STARPORT_HEVC_DESCRIPTION if parked else held, default_value="Off"))
|
||||
return tuple(rows)
|
||||
|
||||
def _driver_monitoring_reset_allowed(self):
|
||||
return self.authorized() and not self.params.get_bool("IsDriverViewEnabled")
|
||||
|
||||
@@ -62,11 +96,7 @@ class DeveloperSettingsOwner:
|
||||
firmware_rows = (() if self.panda_wake is None else
|
||||
tuple(replace(item, vehicle_fingerprint=item.capability[0])
|
||||
for item in self.panda_wake.rows(parked) if item.key == "IgnoreIgnitionLine"))
|
||||
enabled = self._starport_enabled()
|
||||
starport_rows = () if enabled is None else (
|
||||
FeatureRow(STARPORT_WIFI_KEY, "StarPort: Stay on Wi-Fi", "On" if enabled else "Off", b"1" if enabled else b"0", ("Off", "On"),
|
||||
available=parked, reason=STARPORT_WIFI_DESCRIPTION if parked else "Park the vehicle to change this setting.",
|
||||
default_value="On"),)
|
||||
starport_rows = self._starport_rows(parked)
|
||||
summary = "Changes apply next drive. Panda firmware changes require a device restart." if firmware_rows else "Changes apply next drive."
|
||||
return FeatureSettingsState("developer", "Developer driving options", summary,
|
||||
(row, reset_row, calibration_row, *firmware_rows, *starport_rows), parked)
|
||||
@@ -96,6 +126,10 @@ class DeveloperSettingsOwner:
|
||||
return self.panda_wake is not None and self.panda_wake.apply(request)
|
||||
if request.key == STARPORT_WIFI_KEY:
|
||||
return self._apply_starport(request)
|
||||
if request.key == STARPORT_HEVC_KEY:
|
||||
return self._apply_hevc(request)
|
||||
if request.key == STARPORT_NEW_SETUP_KEY:
|
||||
return self._apply_new_setup(request)
|
||||
if request.key != KEY or request.value not in ("Off", "On") or request.expected not in (None, b"0", b"1"):
|
||||
return False
|
||||
result = commit_exact(self.params, key=request.key, max_bytes=8, raw=b"1" if request.value == "On" else b"0",
|
||||
@@ -112,3 +146,20 @@ class DeveloperSettingsOwner:
|
||||
except (OSError, RuntimeError, ValueError):
|
||||
return False
|
||||
return self._starport_enabled() == (request.value == "On")
|
||||
|
||||
def _apply_hevc(self, request):
|
||||
enabled, client = self._starport_hevc(), self.starport
|
||||
if (enabled is None or client is None or request.value not in ("Off", "On") or request.expected != (b"1" if enabled else b"0") or
|
||||
not self.authorized()):
|
||||
return False
|
||||
try:
|
||||
client().call("set_allow_hevc", enabled=request.value == "On")
|
||||
except (OSError, RuntimeError, ValueError):
|
||||
return False
|
||||
return self._starport_hevc() == (request.value == "On")
|
||||
|
||||
def _apply_new_setup(self, request):
|
||||
current = new_setup.enabled()
|
||||
if request.value not in ("Off", "On") or request.expected != (b"1" if current else b"0") or not self.authorized():
|
||||
return False
|
||||
return new_setup.set_enabled(request.value == "On")
|
||||
|
||||
@@ -90,6 +90,10 @@ DESCRIPTIONS = {
|
||||
'RemoteStartBootsComma': 'Wakes comma during GM remote start.',
|
||||
'HKGRemoteStartBootsComma': 'Wakes comma during Hyundai, Kia or Genesis remote climate operation.',
|
||||
'IgnoreIgnitionLine': 'Uses GM CAN ignition instead of the harness ignition signal.',
|
||||
'heading:starport': 'Settings for connecting Android Auto to the car through StarPort.',
|
||||
'StarPortNewSetup': 'Shows the guided StarPort setup with a live checklist instead of the classic setup screens.',
|
||||
'StarPortAllowHevc': 'Lets a car that offers only H.265 video receive H.265; a car with an H.264 mode keeps H.264.',
|
||||
'StarPortDedicatedWifi': "Joins the car's Wi-Fi on a second interface so the comma stays on its home Wi-Fi or phone hotspot.",
|
||||
'TeslaWakeOnCAN': 'Wakes comma when the Tesla wakes up, before you press the brake.',
|
||||
'RadarRecoveryAlert': 'Play a chime and show a brief message when a radar fault clears. You must re-engage manually.',
|
||||
'CameraView': 'Chooses the camera view shown on the driving screen.',
|
||||
@@ -158,6 +162,9 @@ DESCRIPTIONS = {
|
||||
|
||||
# These are initialization explanations, not a second defaults table.
|
||||
INITIALIZATION = {
|
||||
'StarPortNewSetup': 'Applies the next time the setup screen opens.',
|
||||
'StarPortAllowHevc': 'Applies from the next StarPort connection.',
|
||||
'StarPortDedicatedWifi': 'Applies from the next StarPort connection.',
|
||||
'LateralControllerSelection': ('Applies when driving processes next start: the controller class, vehicle policy and ' +
|
||||
'PID/filter state are constructed together.'),
|
||||
'UseStarPilotLongitudinalPlanner': 'Applies when the planner next starts: each choice runs a different planner process with its own subscriptions and state.',
|
||||
|
||||
@@ -27,7 +27,7 @@ from openpilot.starpilot.audio.alert_volume import SPECS as SOUND_SPECS
|
||||
from openpilot.starpilot.ui.presentation import Profile
|
||||
from openpilot.common.hardware import HARDWARE
|
||||
from openpilot.starpilot.ui.feature_settings_state import (
|
||||
FeatureRow, FeatureSettingsRequest, FEATURE_CONFIRM_ACTIONS, is_long_confirm_action, row_change, row_default,
|
||||
FeatureRow, FeatureSettingsRequest, FEATURE_CONFIRM_ACTIONS, is_heading, is_long_confirm_action, row_change, row_default,
|
||||
)
|
||||
from openpilot.starpilot.saved_source import read_saved
|
||||
from openpilot.starpilot.galaxy.developer_settings import CALIBRATION_RESET, DRIVER_MONITORING_RESET, DeveloperSettingsOwner
|
||||
@@ -392,7 +392,8 @@ def _projection(row: FeatureRow) -> dict:
|
||||
"confirm": bool(row.key in CONFIRM_ACTIONS or is_long_confirm_action(row.key) or
|
||||
row.key in (PIP_RESET, PIP_EDITOR, VASM_RESET, VASM_ANNOTATION) or row.key.startswith(PIP_FORMAT_PREFIX)),
|
||||
"repairValue": row.repair_value, "defaultValue": row.default_value, "resetAvailable": row_default(row) is not None,
|
||||
"displayScale": "lane-strength" if row.key == "LaneCenteringStrength" else None}
|
||||
"displayScale": "lane-strength" if row.key == "LaneCenteringStrength" else None,
|
||||
"heading": is_heading(row.key)}
|
||||
|
||||
|
||||
def _question(row: FeatureRow, request: FeatureSettingsRequest) -> str:
|
||||
|
||||
@@ -57,13 +57,6 @@
|
||||
"LoudBlindspotAlert",
|
||||
"LoudBlindspotAlertWhenDisengaged",
|
||||
"NostalgiaMode",
|
||||
"slc:offset:1",
|
||||
"slc:offset:2",
|
||||
"slc:offset:3",
|
||||
"slc:offset:4",
|
||||
"slc:offset:5",
|
||||
"slc:offset:6",
|
||||
"slc:offset:7",
|
||||
"PIPPreviewEnabled",
|
||||
"PIPPreviewInvert",
|
||||
"PIPPreviewShowOnBSM",
|
||||
@@ -106,6 +99,9 @@
|
||||
"StandardJerkSpeed",
|
||||
"StandardJerkSpeedDecrease",
|
||||
"StarPilotDisplayPreferencesEnabled",
|
||||
"StarPortAllowHevc",
|
||||
"StarPortDedicatedWifi",
|
||||
"StarPortNewSetup",
|
||||
"SteerFriction",
|
||||
"SteerLatAccel",
|
||||
"StockConfidenceBallWidget",
|
||||
@@ -166,6 +162,7 @@
|
||||
"display:auto:ScreenBrightness",
|
||||
"display:auto:ScreenBrightnessOnroad",
|
||||
"enable_human_turn_detection_curv",
|
||||
"heading:starport",
|
||||
"lane_change:auto",
|
||||
"lane_change:close_gap",
|
||||
"lane_change:close_gap_seconds",
|
||||
@@ -249,7 +246,6 @@
|
||||
"profile:aggressive:following:8",
|
||||
"profile:aggressive:following:9",
|
||||
"profile:global_acceleration",
|
||||
"profile:stopped_gap",
|
||||
"profile:global_braking",
|
||||
"profile:relaxed:acceleration",
|
||||
"profile:relaxed:acceleration:0",
|
||||
@@ -317,6 +313,7 @@
|
||||
"profile:standard:following:7",
|
||||
"profile:standard:following:8",
|
||||
"profile:standard:following:9",
|
||||
"profile:stopped_gap",
|
||||
"profile:traffic:acceleration",
|
||||
"profile:traffic:acceleration:0",
|
||||
"profile:traffic:acceleration:1",
|
||||
@@ -357,6 +354,13 @@
|
||||
"sentry:reset",
|
||||
"sentry:sensitivity",
|
||||
"sentry:warning",
|
||||
"slc:offset:1",
|
||||
"slc:offset:2",
|
||||
"slc:offset:3",
|
||||
"slc:offset:4",
|
||||
"slc:offset:5",
|
||||
"slc:offset:6",
|
||||
"slc:offset:7",
|
||||
"slc_reset",
|
||||
"sounds:auto:DisengageVolume",
|
||||
"sounds:auto:EngageVolume",
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
from typing import cast
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from openpilot.starpilot.galaxy.developer_settings import CALIBRATION_RESET, DRIVER_MONITORING_RESET, STARPORT_WIFI_KEY, DeveloperSettingsOwner
|
||||
from openpilot.starpilot.galaxy.developer_settings import (CALIBRATION_RESET, DRIVER_MONITORING_RESET, STARPORT_HEADING_KEY,
|
||||
STARPORT_HEVC_KEY, STARPORT_NEW_SETUP_KEY, STARPORT_WIFI_KEY,
|
||||
DeveloperSettingsOwner)
|
||||
from openpilot.starpilot.galaxy.settings import AuthorityContext, ContextSource, SettingsChanged, SettingsGateway
|
||||
from openpilot.starpilot.longitudinal.voacc import KEY, selected
|
||||
from openpilot.starpilot.system.starport import new_setup
|
||||
from openpilot.starpilot.ui.feature_settings_state import FeatureSettingsRequest
|
||||
|
||||
|
||||
@@ -40,7 +44,7 @@ class FileParams:
|
||||
|
||||
class FakeStarPort:
|
||||
def __init__(self, enabled=False, running=True):
|
||||
self.enabled, self.running = enabled, running
|
||||
self.enabled, self.running, self.hevc = enabled, running, False
|
||||
|
||||
def call(self, command, **payload):
|
||||
if not self.running:
|
||||
@@ -48,7 +52,10 @@ class FakeStarPort:
|
||||
if command == "set_dedicated_wifi":
|
||||
self.enabled = payload["enabled"]
|
||||
return {"ok": True}
|
||||
return {"ok": True, "status": {"dedicated_wifi_interface": self.enabled}}
|
||||
if command == "set_allow_hevc":
|
||||
self.hevc = payload["enabled"]
|
||||
return {"ok": True}
|
||||
return {"ok": True, "status": {"dedicated_wifi_interface": self.enabled, "allow_hevc": self.hevc}}
|
||||
|
||||
|
||||
class DeveloperSettingsTest(unittest.TestCase):
|
||||
@@ -60,6 +67,11 @@ class DeveloperSettingsTest(unittest.TestCase):
|
||||
self.cp = SimpleNamespace(openpilotLongitudinalControl=True, notCar=False, dashcamOnly=False, passive=False)
|
||||
self.parked = True
|
||||
self.owner = DeveloperSettingsOwner(self.params, lambda: self.parked)
|
||||
flag = Path(directory.name) / "new_setup"
|
||||
patch = mock.patch.object(new_setup, "FLAG_PATH", flag)
|
||||
patch.start()
|
||||
self.addCleanup(patch.stop)
|
||||
self.flag = flag
|
||||
|
||||
def test_default_off_strict_selection_and_vehicle_admission(self):
|
||||
self.assertEqual(self.owner.snapshot().rows[0].value, "Off")
|
||||
@@ -172,6 +184,52 @@ class DeveloperSettingsTest(unittest.TestCase):
|
||||
self.assertFalse(self.owner.apply(FeatureSettingsRequest(DRIVER_MONITORING_RESET, b"1", "confirm", confirmation=True)))
|
||||
self.assertTrue(Path(self.params.get_param_path("IsRhdDetected")).exists())
|
||||
|
||||
def test_starport_section_has_a_heading_and_the_escape_hatch_even_with_the_service_off(self):
|
||||
rows = self.owner.snapshot().rows
|
||||
keys = [row.key for row in rows]
|
||||
heading = rows[keys.index(STARPORT_HEADING_KEY)]
|
||||
self.assertEqual((heading.label, heading.value, heading.available, heading.choices), ("StarPort", "", False, ()))
|
||||
self.assertEqual(keys[keys.index(STARPORT_HEADING_KEY) + 1], STARPORT_NEW_SETUP_KEY) # the heading groups what follows
|
||||
self.assertNotIn(STARPORT_WIFI_KEY, keys)
|
||||
self.assertNotIn(STARPORT_HEVC_KEY, keys)
|
||||
self.assertFalse(self.owner.apply(FeatureSettingsRequest(STARPORT_HEADING_KEY, b"", "On"))) # a heading is never a control
|
||||
|
||||
def test_new_pairing_experience_defaults_on_and_the_classic_screens_are_one_switch_away(self):
|
||||
row = next(row for row in self.owner.snapshot().rows if row.key == STARPORT_NEW_SETUP_KEY)
|
||||
self.assertEqual((row.value, row.source, row.choices, row.default_value, row.available), ("On", b"1", ("Off", "On"), "On", True))
|
||||
self.assertFalse(self.owner.apply(FeatureSettingsRequest(STARPORT_NEW_SETUP_KEY, b"0", "On"))) # stale view
|
||||
self.assertFalse(self.owner.apply(FeatureSettingsRequest(STARPORT_NEW_SETUP_KEY, b"1", "maybe")))
|
||||
self.assertTrue(self.owner.apply(FeatureSettingsRequest(STARPORT_NEW_SETUP_KEY, b"1", "Off")))
|
||||
self.assertFalse(new_setup.enabled())
|
||||
self.assertEqual(next(r for r in self.owner.snapshot().rows if r.key == STARPORT_NEW_SETUP_KEY).value, "Off")
|
||||
self.parked = False
|
||||
self.assertFalse(self.owner.apply(FeatureSettingsRequest(STARPORT_NEW_SETUP_KEY, b"0", "On")))
|
||||
self.parked = True
|
||||
self.assertTrue(self.owner.apply(FeatureSettingsRequest(STARPORT_NEW_SETUP_KEY, b"0", "On")))
|
||||
self.assertTrue(new_setup.enabled())
|
||||
|
||||
def test_unreadable_or_odd_flag_means_on(self):
|
||||
self.flag.write_text("garbage")
|
||||
self.assertTrue(new_setup.enabled())
|
||||
self.flag.unlink()
|
||||
self.assertTrue(new_setup.enabled())
|
||||
|
||||
def test_allow_h265_toggle_defaults_off_and_follows_the_daemon(self):
|
||||
service = FakeStarPort()
|
||||
owner = DeveloperSettingsOwner(self.params, lambda: self.parked, starport=lambda: service)
|
||||
rows = owner.snapshot().rows
|
||||
keys = [row.key for row in rows]
|
||||
self.assertEqual(keys[keys.index(STARPORT_HEADING_KEY):], [STARPORT_HEADING_KEY, STARPORT_NEW_SETUP_KEY, STARPORT_WIFI_KEY, STARPORT_HEVC_KEY])
|
||||
row = rows[keys.index(STARPORT_HEVC_KEY)]
|
||||
self.assertEqual((row.value, row.default_value, row.available), ("Off", "Off", True))
|
||||
self.assertTrue(owner.apply(FeatureSettingsRequest(STARPORT_HEVC_KEY, row.source, "On")))
|
||||
self.assertTrue(service.hevc)
|
||||
self.assertFalse(owner.apply(FeatureSettingsRequest(STARPORT_HEVC_KEY, row.source, "Off"))) # stale view
|
||||
self.parked = False
|
||||
self.assertFalse(owner.apply(FeatureSettingsRequest(STARPORT_HEVC_KEY, b"1", "Off")))
|
||||
self.parked = True
|
||||
service.running = False
|
||||
self.assertFalse(owner.apply(FeatureSettingsRequest(STARPORT_HEVC_KEY, b"1", "Off")))
|
||||
|
||||
class CalibrationResetSettingsTest(unittest.TestCase):
|
||||
setUp = DeveloperSettingsTest.setUp
|
||||
|
||||
@@ -227,3 +227,15 @@ for (const row of [{ value: "30", minimum: 1, maximum: 30, step: 1, unit: "h" },
|
||||
{ value: "11.8", minimum: 11.8, maximum: 12.5, step: .1, unit: "V" }]) {
|
||||
assert.equal(settingControl(row), "slider")
|
||||
}
|
||||
|
||||
// A section heading (for example "StarPort" on the Developer page) is a label only: no control, no readout, no default button.
|
||||
{
|
||||
const heading = { label: "StarPort", value: "", heading: true, available: false, choices: [], key: "heading:starport" }
|
||||
assert.equal(settingControl(heading), "readout")
|
||||
const template = GalaxySettingRow.template
|
||||
assert.match(template, /'gx-row--heading': row\.heading/)
|
||||
assert.match(template, /v-else-if="!row\.heading" class="gx-row__value gx-row__readout"/)
|
||||
assert.match(template, /showDefault && !row\.heading/)
|
||||
assert.match(template, /presentedRow\.reason && !row\.heading/)
|
||||
}
|
||||
console.log("Settings controls: section headings render as labels only")
|
||||
|
||||
@@ -1711,6 +1711,9 @@ input[type="checkbox"]:disabled { opacity: .4; cursor: not-allowed; }
|
||||
.gx-inset { margin: var(--sp-4); max-width: calc(100% - 2 * var(--sp-4)); }
|
||||
.gx-text-danger { color: var(--error); }
|
||||
.gx-row.gx-row--borderless { border-top: 0; }
|
||||
/* A section title inside a settings page: a label only, never dimmed like an unavailable control. */
|
||||
.gx-row.gx-row--heading, .gx-row.gx-row--heading.disabled { opacity: 1; padding-top: 1.25rem; }
|
||||
.gx-row--heading .gx-row__label { font-weight: 700; letter-spacing: .04em; text-transform: uppercase; opacity: .75; }
|
||||
.gx-row--wrap { flex-wrap: wrap; }
|
||||
.gx-row__info.gx-row__info--wide { flex: 1 1 180px; }
|
||||
.gx-filter-field { flex: 1 1 14rem; min-width: 0; }
|
||||
|
||||
@@ -215,14 +215,14 @@ export const GalaxySettingRow = {
|
||||
},
|
||||
beforeUnmount() { this.clearHoldTimer() },
|
||||
template: `
|
||||
<div class="gx-row" :class="{ disabled: dimmed, 'gx-row--stack': control === 'slider' }" :inert="busy || updating" :aria-busy="busy || updating || undefined">
|
||||
<div class="gx-row" :class="{ disabled: dimmed, 'gx-row--stack': control === 'slider', 'gx-row--heading': row.heading }" :role="row.heading ? 'heading' : undefined" :aria-level="row.heading ? 3 : undefined" :inert="busy || updating" :aria-busy="busy || updating || undefined">
|
||||
<div class="gx-row__info">
|
||||
<span class="gx-row__label">{{ row.label }}</span>
|
||||
<span v-if="presentedRow.reason" class="gx-row__desc">{{ ' ' + presentedRow.reason.replace('https://firestar.link/discord', '') }}</span>
|
||||
<span v-if="presentedRow.reason && !row.heading" class="gx-row__desc">{{ ' ' + presentedRow.reason.replace('https://firestar.link/discord', '') }}</span>
|
||||
<a v-if="presentedRow.reason?.includes('https://firestar.link/discord')" class="gx-btn gx-btn--tonal" href="https://firestar.link/discord" target="_blank" rel="noopener">StarPilot Discord</a>
|
||||
<span v-if="control === 'group' || control === 'action'" class="gx-row__desc">{{ presentedRow.value }}</span>
|
||||
</div>
|
||||
<button v-if="control !== 'slider' && showDefault" type="button"
|
||||
<button v-if="control !== 'slider' && showDefault && !row.heading" type="button"
|
||||
class="gx-btn gx-btn--tonal gx-setting-default" :disabled="dimmed || !row.resetAvailable"
|
||||
:aria-label="'Reset ' + row.label + ' to default'" :title="'Default: ' + presentedRow.defaultValue"
|
||||
@click="$emit('reset-default', index)">Default</button>
|
||||
@@ -249,6 +249,6 @@ export const GalaxySettingRow = {
|
||||
</GalaxySelect>
|
||||
<button v-else-if="control === 'group'" type="button" class="gx-btn gx-btn--tonal" :disabled="dimmed" @click="$emit('open', row.page)">Manage</button>
|
||||
<button v-else-if="control === 'action' && row.action" type="button" class="gx-btn gx-btn--tonal" :disabled="dimmed" @click="$emit('review', index, row.confirm ? 0 : 1)">{{ row.repairValue === 'Reset' || /reset|default/i.test(row.label) ? 'Reset' : row.repairValue ? 'Set ' + row.repairValue : row.label }}</button>
|
||||
<span v-else class="gx-row__value gx-row__readout">{{ displayValue }}</span>
|
||||
<span v-else-if="!row.heading" class="gx-row__value gx-row__readout">{{ displayValue }}</span>
|
||||
</div>`,
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@ def handle(supervisor: Supervisor, request: dict[str, Any], verifier: StarPortSo
|
||||
supervisor.set_auto_connect(bool(request.get("enabled", True)))
|
||||
elif command == "set_dedicated_wifi":
|
||||
supervisor.set_dedicated_wifi(request.get("enabled") is True)
|
||||
elif command == "set_allow_hevc":
|
||||
supervisor.set_allow_hevc(request.get("enabled") is True)
|
||||
elif command == "select_receiver":
|
||||
supervisor.select_receiver(str(request.get("address", "")), str(request.get("name", "")))
|
||||
elif command == "forget_receiver":
|
||||
|
||||
@@ -141,6 +141,7 @@ DEFAULT_CONFIG = {
|
||||
"render_profile_kb": 256, # size cap per render_profile file
|
||||
"wifi_interface": "wlan0",
|
||||
"dedicated_wifi_interface": True, # join the car on its own aa0 station so wlan0 stays on home Wi-Fi
|
||||
"allow_hevc": False, # offer H.265 when the car offers no usable H.264 mode (Galaxy Developer > StarPort)
|
||||
"device_name": "StarPilot",
|
||||
"version_status": 0, # WifiVersionResponse status (0 = success) for receivers that negotiate a version
|
||||
"phone_class": True, # while pairing/connecting, present as a phone: HFP gateway + smartphone Class of Device
|
||||
@@ -189,6 +190,7 @@ def load_config(path: Path | None = None) -> dict:
|
||||
if config["usb_mode"] not in ("auto", "handshake", "direct"):
|
||||
config["usb_mode"] = "auto"
|
||||
config["bitrate_kbps"] = max(1000, min(12000, int(config["bitrate_kbps"])))
|
||||
config["allow_hevc"] = config["allow_hevc"] is True
|
||||
if config["rate_control"] not in ("cbr", "vbr"):
|
||||
config["rate_control"] = "cbr"
|
||||
config["render_profile_kb"] = max(16, min(4096, int(config["render_profile_kb"])))
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""The "New pairing experience" switch (Galaxy Developer > StarPort), on unless the owner turned it off.
|
||||
|
||||
A plain file beside the other StarPort data, not a Params key: it is the way back to the classic screens, so it must
|
||||
read and change with the StarPort service down or broken, and it needs no device rebuild.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from openpilot.starpilot.system.starport.identity import DATA_DIR
|
||||
|
||||
FLAG_PATH = DATA_DIR / "new_setup"
|
||||
|
||||
|
||||
def enabled(path: Path | None = None) -> bool:
|
||||
"""Absent, unreadable or unrecognised means on; only an explicit 0 turns the new flow off."""
|
||||
try:
|
||||
return (path or FLAG_PATH).read_text().strip() != "0"
|
||||
except (OSError, UnicodeError):
|
||||
return True
|
||||
|
||||
|
||||
def set_enabled(value: bool, path: Path | None = None) -> bool:
|
||||
target = path or FLAG_PATH
|
||||
try:
|
||||
target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
temporary = target.with_name(f".{target.name}.tmp")
|
||||
temporary.write_text("1" if value else "0")
|
||||
temporary.replace(target)
|
||||
except OSError:
|
||||
return False
|
||||
return enabled(target) == value
|
||||
@@ -69,7 +69,7 @@ LINK_CHECK_SLOW = 1.0 # a link check slower than this is logged (NetworkM
|
||||
STREAM_STALL_LOG = 1.0 # a streaming loop pass slower than this is logged
|
||||
REPORTED_SETTINGS = ("connection", "usb_mode", "view", "encoder", "fps", "bitrate_kbps", "rate_control", "gpu_nv12",
|
||||
"async_readback", "wifi_interface", "dedicated_wifi_interface", "verify_head_unit", "version_status",
|
||||
"rfcomm_channel", "phone_class", "auto_connect")
|
||||
"rfcomm_channel", "phone_class", "auto_connect", "allow_hevc")
|
||||
|
||||
|
||||
def encoder_preference(configured: str) -> str:
|
||||
@@ -405,6 +405,7 @@ class Supervisor:
|
||||
status["pairing_ready"] = self._pairing_active()
|
||||
status["auto_connect"] = bool(self.config["auto_connect"])
|
||||
status["dedicated_wifi_interface"] = bool(self.config["dedicated_wifi_interface"])
|
||||
status["allow_hevc"] = bool(self.config["allow_hevc"])
|
||||
status["auto_paused"] = self.auto.suppressed
|
||||
status["recent"] = list(self.log.recent)[-8:]
|
||||
return status
|
||||
@@ -433,6 +434,13 @@ class Supervisor:
|
||||
identity_store.save_config(self.config)
|
||||
self.log("dedicated_wifi_selected", enabled=bool(enabled))
|
||||
|
||||
def set_allow_hevc(self, enabled: bool) -> None:
|
||||
"""Let a car that offers only H.265 get H.265 video; used from the next session. Cars with an H.264 mode never change."""
|
||||
with self._lock:
|
||||
self.config["allow_hevc"] = bool(enabled)
|
||||
identity_store.save_config(self.config)
|
||||
self.log("allow_hevc_selected", enabled=bool(enabled))
|
||||
|
||||
def _adapter_choice_needed(self) -> bool:
|
||||
return self.car_type() == "adapter" and not self.config.get("car_profile_override")
|
||||
|
||||
|
||||
@@ -75,6 +75,14 @@ FEATURE_CONFIRM_ACTIONS = (TORQUE_CONFIRM_ACTIONS | SLC_CONFIRM_ACTIONS | LANE_C
|
||||
CURVE_CONFIRM_ACTIONS | CONDITIONAL_CONFIRM_ACTIONS | {"reset_profiles", "pip:reset", "sentry:reset"})
|
||||
|
||||
|
||||
HEADING_PREFIX = "heading:"
|
||||
|
||||
|
||||
def is_heading(key: str) -> bool:
|
||||
"""A section title inside a page: shown like a row but never a control, and never a change request."""
|
||||
return key.startswith(HEADING_PREFIX)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FeatureRow:
|
||||
key: str
|
||||
|
||||
Reference in New Issue
Block a user