Compare commits

...

14 Commits

Author SHA1 Message Date
Jason Wen 7985bd8e13 chore: update CHANGELOG for 2026.001.007 2026-05-27 17:35:08 -04:00
Jason Wen 9b5f339759 Revert "DM: Lancia Delta HF Integrale model" (#1849)
Revert "DM: Lancia Delta HF Integrale model (#37696)"

This reverts commit d8569b07eb.
2026-05-27 17:34:57 -04:00
Jason Wen 5360b879a0 chore: bump version to 2026.001.007 2026-05-27 17:34:53 -04:00
Jason Wen ac05b4efb6 chore: update CHANGELOG for 2026.001.006 2026-05-17 20:42:44 -04:00
Jason Wen 9d6fbb6324 sunnylink SDUI: tweak DisableUpdate param for clarity (#1842)
* sunnylink SDUI: tweak DisableUpdate param for clarity

* sync
2026-05-17 20:42:34 -04:00
Jason Wen edc4950ea7 chore: bump version to 2026.001.006 2026-05-17 20:42:29 -04:00
Jason Wen c7e57a1bc1 chore: update CHANGELOG for 2026.001.005 2026-05-13 02:22:55 -04:00
Jason Wen 353ed2a9e1 sunnylink: add CarParams fallback for brand-specific capabilities (#1839)
Brand-specific capabilities (hyundai_alpha_long_available,
subaru_has_sng) only resolved from CarPlatformBundle, which requires
manual car selection. Auto-fingerprinted vehicles had no bundle,
leaving these capabilities at default false — hiding vehicle settings
on the dashboard despite working on the device UI.

Add _resolve_brand_capabilities() with bundle-first, CP-fallback
pattern matching the device UI layouts (hyundai.py, subaru.py).

Fixes https://community.sunnypilot.ai/t/5126
2026-05-13 01:54:58 -04:00
Jason Wen 1db8b82f16 version: bump to 2026.001.005 2026-05-13 01:54:53 -04:00
Jason Wen e8964ce7ae chore: update CHANGELOG for 2026.001.004 2026-05-10 01:19:44 -04:00
Jason Wen ad799442a8 chore: bump version to 2026.001.004 2026-05-10 01:14:07 -04:00
Jason Wen 592f062326 ci: simplify cereal validation to sparse-checkout + pycapnp, drop scons (#1836)
* ci: simplify cereal validation to sparse-checkout + pycapnp, drop scons build

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* more

* fix: resolve cereal_dir to absolute path before passing to capnp.load

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* ci: init opendbc submodule after sparse checkout to resolve car.capnp symlink

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* try to break it

* Revert "try to break it"

This reverts commit 79ce135c5f.

* try to break it

* Revert "try to break it"

This reverts commit 1eaa9e79e6.

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 01:14:00 -04:00
Nayan 70fd56f69c sunnylink: fix max time offroad values (#1835)
fix sunnylink values
2026-05-10 01:13:57 -04:00
DevTekVE 29cd05d6ed sunnylink: switch athena domain (#1826)
use new domain
2026-05-10 01:13:54 -04:00
17 changed files with 192 additions and 132 deletions
+30 -43
View File
@@ -23,56 +23,43 @@ env:
CI: 1
jobs:
generate_cereal_artifact:
name: Generate cereal validation artifacts
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
submodules: true
- run: ./tools/op.sh setup
- name: Build openpilot
run: scons -j$(nproc) cereal
- name: Dump sunnypilot schema
run: |
export PYTHONPATH=${{ github.workspace }}
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py -g -f schema.json
- name: 'Prepare artifact'
run: |
mkdir -p "cereal/messaging/tests/cereal_validations"
cp cereal/messaging/tests/validate_sp_cereal_upstream.py "cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py"
cp schema.json "cereal/messaging/tests/cereal_validations/schema.json"
- name: 'Upload Artifact'
uses: actions/upload-artifact@v4
with:
name: cereal_validations
path: cereal/messaging/tests/cereal_validations
validate_cereal_with_upstream:
name: Validate cereal with Upstream
runs-on: ubuntu-24.04
needs: generate_cereal_artifact
steps:
- name: Checkout sunnypilot
- name: Checkout sunnypilot cereal
uses: actions/checkout@v6
- name: Checkout upstream openpilot
with:
sparse-checkout: cereal
- name: Init sunnypilot opendbc submodule
run: git submodule update --init --depth 1 opendbc_repo
- name: Checkout upstream openpilot cereal
uses: actions/checkout@v6
with:
repository: 'commaai/openpilot'
path: openpilot
submodules: true
path: upstream_openpilot
sparse-checkout: cereal
ref: "refs/heads/master"
- run: ./tools/op.sh setup
- name: Build openpilot
working-directory: openpilot
run: scons -j$(nproc) cereal
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: cereal_validations
path: openpilot/cereal/messaging/tests/cereal_validations
- name: 'Validate sunnypilot schema against upstream'
- name: Init upstream opendbc submodule
working-directory: upstream_openpilot
run: git submodule update --init --depth 1 opendbc_repo
- name: Install uv
run: pip install uv
- name: Generate sunnypilot schema
run: |
export PYTHONPATH=${{ github.workspace }}/openpilot
chmod +x openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py
python3 openpilot/cereal/messaging/tests/cereal_validations/validate_sp_cereal_upstream.py -r -f openpilot/cereal/messaging/tests/cereal_validations/schema.json
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
-g -f /tmp/sp_schema.json --cereal-dir cereal
- name: Validate against upstream
run: |
PYCAPNP_VER=$(python3 -c "import re; m=re.search(r'name = \"pycapnp\"\nversion = \"([^\"]+)\"', open('uv.lock').read()); print(m.group(1))")
uv run --isolated --with "pycapnp==${PYCAPNP_VER}" \
python3 cereal/messaging/tests/validate_sp_cereal_upstream.py \
-r -f /tmp/sp_schema.json --cereal-dir upstream_openpilot/cereal
+22 -1
View File
@@ -1,7 +1,28 @@
sunnypilot Version 2026.001.007 (2026-05-27)
========================
* What's Changed (sunnypilot/sunnypilot)
* Revert "DM: Lancia Delta HF Integrale model" by @sunnyhaibin
sunnypilot Version 2026.001.006 (2026-05-17)
========================
* What's Changed (sunnypilot/sunnypilot)
* sunnylink SDUI: tweak DisableUpdate param for clarity by @sunnyhaibin
sunnypilot Version 2026.001.005 (2026-05-13)
========================
* What's Changed (sunnypilot/sunnypilot)
* sunnylink: add CarParams fallback for brand-specific capabilities by @sunnyhaibin
sunnypilot Version 2026.001.004 (2026-05-10)
========================
* What's Changed (sunnypilot/sunnypilot)
* sunnylink: switch athena domain by @DevTekVE
* sunnylink: fix max time offroad values by @nayan8teen
sunnypilot Version 2026.001.003 (2026-05-08)
========================
* What's Changed (sunnypilot/sunnypilot)
* manager: disable DEVELOPMENT_ONLY reset by @jason-wen
* manager: disable DEVELOPMENT_ONLY reset by @sunnyhaibin
sunnypilot Version 2026.001.002 (2026-05-07)
========================
+5 -7
View File
@@ -2054,16 +2054,14 @@ struct DriverStateV2 {
facePosition @2 :List(Float32);
facePositionStd @3 :List(Float32);
faceProb @4 :Float32;
eyesVisibleProb @14 :Float32;
eyesClosedProb @15 :Float32;
leftEyeProb @5 :Float32;
rightEyeProb @6 :Float32;
leftBlinkProb @7 :Float32;
rightBlinkProb @8 :Float32;
sunglassesProb @9 :Float32;
phoneProb @13 :Float32;
deprecated :group {
leftEyeProb @5 :Float32;
rightEyeProb @6 :Float32;
leftBlinkProb @7 :Float32;
rightBlinkProb @8 :Float32;
sunglassesProb @9 :Float32;
notReadyProb @12 :List(Float32);
occludedProb @10 :Float32;
readyProb @11 :List(Float32);
@@ -13,6 +13,7 @@ from __future__ import annotations
import argparse
import json
import os
import sys
from typing import Any
@@ -104,8 +105,15 @@ def collect_schema(root: Any) -> dict[str, dict]:
return structs
def dump_schema(path: str) -> None:
from cereal import log
def load_log(cereal_dir: str) -> Any:
import capnp
cereal_dir = os.path.abspath(cereal_dir)
capnp.remove_import_hook()
return capnp.load(os.path.join(cereal_dir, "log.capnp"), imports=[cereal_dir])
def dump_schema(cereal_dir: str, path: str) -> None:
log = load_log(cereal_dir)
payload = {
"root": hex_id(log.Event.schema.node.id),
"structs": collect_schema(log.Event.schema),
@@ -206,8 +214,8 @@ def load_peer(path: str) -> dict:
return json.load(handle)
def run_read(peer_path: str) -> int:
from cereal import log
def run_read(cereal_dir: str, peer_path: str) -> int:
log = load_log(cereal_dir)
peer_dump = load_peer(peer_path)
local_dump = {
"root": hex_id(log.Event.schema.node.id),
@@ -235,16 +243,13 @@ def main() -> int:
mode.add_argument("-g", "--generate", action="store_true", help="dump local schema to JSON")
mode.add_argument("-r", "--read", action="store_true", help="load peer JSON and diff against local")
parser.add_argument("-f", "--file", default="schema.json", help="JSON file path (default: schema.json)")
parser.add_argument("--cereal-dir", required=True, help="path to cereal directory containing log.capnp")
args = parser.parse_args()
try:
if args.generate:
dump_schema(args.file)
return 0
return run_read(args.file)
except ImportError as exc:
print(f"error: cannot import cereal ({exc}). did scons build cereal?")
return 2
if args.generate:
dump_schema(args.cereal_dir, args.file)
return 0
return run_read(args.cereal_dir, args.file)
if __name__ == "__main__":
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:56de402482b5987ed9a0ff3f793a1c89f857304b34fbb8a3deb5b5d4a332be1c
size 3688
+6 -3
View File
@@ -83,7 +83,7 @@ def parse_model_output(model_output):
face_descs = model_output[f'face_descs_{ds_suffix}']
parsed[f'face_descs_{ds_suffix}'] = face_descs[:, :-6]
parsed[f'face_descs_{ds_suffix}_std'] = safe_exp(face_descs[:, -6:])
for key in ['face_prob', 'eyes_visible_prob', 'eyes_closed_prob', 'using_phone_prob']:
for key in ['face_prob', 'left_eye_prob', 'right_eye_prob','left_blink_prob', 'right_blink_prob', 'sunglasses_prob', 'using_phone_prob']:
parsed[f'{key}_{ds_suffix}'] = sigmoid(model_output[f'{key}_{ds_suffix}'])
return parsed
@@ -93,8 +93,11 @@ def fill_driver_data(msg, model_output, ds_suffix):
msg.facePosition = model_output[f'face_descs_{ds_suffix}'][0, 3:5].tolist()
msg.facePositionStd = model_output[f'face_descs_{ds_suffix}_std'][0, 3:5].tolist()
msg.faceProb = model_output[f'face_prob_{ds_suffix}'][0, 0].item()
msg.eyesVisibleProb = model_output[f'eyes_visible_prob_{ds_suffix}'][0, 0].item()
msg.eyesClosedProb = model_output[f'eyes_closed_prob_{ds_suffix}'][0, 0].item()
msg.leftEyeProb = model_output[f'left_eye_prob_{ds_suffix}'][0, 0].item()
msg.rightEyeProb = model_output[f'right_eye_prob_{ds_suffix}'][0, 0].item()
msg.leftBlinkProb = model_output[f'left_blink_prob_{ds_suffix}'][0, 0].item()
msg.rightBlinkProb = model_output[f'right_blink_prob_{ds_suffix}'][0, 0].item()
msg.sunglassesProb = model_output[f'sunglasses_prob_{ds_suffix}'][0, 0].item()
msg.phoneProb = model_output[f'using_phone_prob_{ds_suffix}'][0, 0].item()
def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_time: float, gpu_exec_time: float):
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2fd471febb6e973313ac0d0c6755f6410c1937ba92230b58a433761e8c883072
size 7364290
oid sha256:7aff7ff1dc08bbaf562a8f77380ab5e5914f8557dba2f760d87e4d953f5604b0
size 7307246
+14 -5
View File
@@ -32,8 +32,9 @@ class DRIVER_MONITOR_SETTINGS:
self._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6.
self._FACE_THRESHOLD = 0.7
self._EYE_THRESHOLD = 0.5
self._BLINK_THRESHOLD = 0.5
self._EYE_THRESHOLD = 0.65
self._SG_THRESHOLD = 0.9
self._BLINK_THRESHOLD = 0.865
self._PHONE_THRESH = 0.5
self._POSE_PITCH_THRESHOLD = 0.3133
@@ -110,6 +111,11 @@ class DriverProb:
self.prob_offseter = RunningStatFilter(raw_priors=raw_priors, max_trackable=max_trackable)
self.prob_calibrated = False
class DriverBlink:
def __init__(self):
self.left = 0.
self.right = 0.
# model output refers to center of undistorted+leveled image
EFL = 598.0 # focal length in K
@@ -144,7 +150,7 @@ class DriverMonitoring:
wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2)
self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT)
self.pose = DriverPose(settings=self.settings)
self.blink_prob = 0.
self.blink = DriverBlink()
self.phone_prob = 0.
self.always_on = always_on
@@ -247,7 +253,7 @@ class DriverMonitoring:
if pitch_error > pitch_threshold or yaw_error > yaw_threshold:
distracted_types.append(DistractedType.DISTRACTED_POSE)
if self.blink_prob > self.settings._BLINK_THRESHOLD:
if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD:
distracted_types.append(DistractedType.DISTRACTED_BLINK)
if self.phone_prob > self.settings._PHONE_THRESH:
@@ -288,7 +294,10 @@ class DriverMonitoring:
self.pose.yaw_std = driver_data.faceOrientationStd[1]
model_std_max = max(self.pose.pitch_std, self.pose.yaw_std)
self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD
self.blink_prob = driver_data.eyesClosedProb * (driver_data.eyesVisibleProb > self.settings._EYE_THRESHOLD)
self.blink.left = driver_data.leftBlinkProb * (driver_data.leftEyeProb > self.settings._EYE_THRESHOLD) \
* (driver_data.sunglassesProb < self.settings._SG_THRESHOLD)
self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \
* (driver_data.sunglassesProb < self.settings._SG_THRESHOLD)
self.phone_prob = driver_data.phoneProb
self.distracted_types = self._get_distracted_types()
+4 -2
View File
@@ -20,8 +20,10 @@ def make_msg(face_detected, distracted=False, model_uncertain=False):
ds.leftDriverData.faceOrientation = [0., 0., 0.]
ds.leftDriverData.facePosition = [0., 0.]
ds.leftDriverData.faceProb = 1. * face_detected
ds.leftDriverData.eyesVisibleProb = 1.
ds.leftDriverData.eyesClosedProb = 1. * distracted
ds.leftDriverData.leftEyeProb = 1.
ds.leftDriverData.rightEyeProb = 1.
ds.leftDriverData.leftBlinkProb = 1. * distracted
ds.leftDriverData.rightBlinkProb = 1. * distracted
ds.leftDriverData.faceOrientationStd = [1.*model_uncertain, 1.*model_uncertain, 1.*model_uncertain]
ds.leftDriverData.facePositionStd = [1.*model_uncertain, 1.*model_uncertain]
# TODO: test both separately when e2e is used
@@ -76,7 +76,7 @@ def generate_report(proposed, master, tmp, commit):
(lambda x: get_idx_if_non_empty(x.wheelOnRightProb), "wheelOnRightProb"),
(lambda x: get_idx_if_non_empty(x.leftDriverData.faceProb), "leftDriverData.faceProb"),
(lambda x: get_idx_if_non_empty(x.leftDriverData.faceOrientation, 0), "leftDriverData.faceOrientation0"),
(lambda x: get_idx_if_non_empty(x.leftDriverData.eyesClosedProb), "leftDriverData.eyesClosedProb"),
(lambda x: get_idx_if_non_empty(x.leftDriverData.leftBlinkProb), "leftDriverData.leftBlinkProb"),
(lambda x: get_idx_if_non_empty(x.leftDriverData.phoneProb), "leftDriverData.phoneProb"),
(lambda x: get_idx_if_non_empty(x.rightDriverData.faceProb), "rightDriverData.faceProb"),
], "driverStateV2")
@@ -39,6 +39,8 @@ class BaseDriverCameraDialog(Widget):
self._eye_fill_texture = None
self._eye_orange_texture = None
self._eye_size = 74
self._glasses_texture = None
self._glasses_size = 171
self._load_eye_textures()
@@ -152,6 +154,8 @@ class BaseDriverCameraDialog(Widget):
self._eye_fill_texture = gui_app.texture("icons_mici/onroad/eye_fill.png", self._eye_size, self._eye_size)
if self._eye_orange_texture is None:
self._eye_orange_texture = gui_app.texture("icons_mici/onroad/eye_orange.png", self._eye_size, self._eye_size)
if self._glasses_texture is None:
self._glasses_texture = gui_app.texture("icons_mici/onroad/glasses.png", self._glasses_size, self._glasses_size)
def _draw_face_detection(self, rect: rl.Rectangle):
dm_state = ui_state.sm["driverMonitoringState"]
@@ -198,21 +202,31 @@ class BaseDriverCameraDialog(Widget):
eye_offset_x = 10
eye_offset_y = 10
eye_spacing = self._eye_size + 15
eyes_prob = driver_data.eyesVisibleProb
left_eye_x = rect.x + eye_offset_x
left_eye_y = rect.y + eye_offset_y
left_eye_prob = driver_data.leftEyeProb
right_eye_x = rect.x + eye_offset_x + eye_spacing
right_eye_y = rect.y + eye_offset_y
right_eye_prob = driver_data.rightEyeProb
# Draw eyes with opacity based on probability
fill_opacity = eyes_prob
orange_opacity = 1.0 - eyes_prob
for eye_x, eye_y in [(left_eye_x, left_eye_y), (right_eye_x, right_eye_y)]:
for eye_x, eye_y, eye_prob in [(left_eye_x, left_eye_y, left_eye_prob), (right_eye_x, right_eye_y, right_eye_prob)]:
fill_opacity = eye_prob
orange_opacity = 1.0 - eye_prob
rl.draw_texture_v(self._eye_orange_texture, (eye_x, eye_y), rl.Color(255, 255, 255, int(255 * orange_opacity)))
rl.draw_texture_v(self._eye_fill_texture, (eye_x, eye_y), rl.Color(255, 255, 255, int(255 * fill_opacity)))
# Draw sunglasses indicator based on sunglasses probability
# Position glasses centered between the two eyes at top left
glasses_x = rect.x + eye_offset_x - 4
glasses_y = rect.y
glasses_pos = rl.Vector2(glasses_x, glasses_y)
glasses_prob = driver_data.sunglassesProb
rl.draw_texture_v(self._glasses_texture, glasses_pos, rl.Color(70, 80, 161, int(255 * glasses_prob)))
class DriverCameraDialog(NavWidget, BaseDriverCameraDialog):
def __init__(self):
+1 -1
View File
@@ -1 +1 @@
#define SUNNYPILOT_VERSION "2026.001.003"
#define SUNNYPILOT_VERSION "2026.001.007"
+1 -1
View File
@@ -34,7 +34,7 @@ from openpilot.sunnypilot.sunnylink.utils import sunnylink_need_register, sunnyl
from openpilot.sunnypilot.sunnylink.capabilities import generate_capabilities, CAPABILITY_LABELS
from openpilot.sunnypilot.sunnylink.tools.generate_settings_schema import generate_schema
SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://ws.stg.api.sunnypilot.ai')
SUNNYLINK_ATHENA_HOST = os.getenv('SUNNYLINK_ATHENA_HOST', 'wss://athena.sunnylink.ai')
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
LOCAL_PORT_WHITELIST = {8022}
SUNNYLINK_LOG_ATTR_NAME = "user.sunny.upload"
+35 -17
View File
@@ -78,6 +78,38 @@ def _bundle_field(bundle: dict | None, key: str) -> str:
return bundle.get(key, "") if isinstance(bundle, dict) else ""
def _resolve_brand_capabilities(caps: dict, bundle_platform: str, CP) -> None:
"""Set brand-specific capabilities from bundle platform or CarParams fallback.
Bundle (manual car selection) is a pre-fingerprint approximation.
CarParams (auto-fingerprint) is the authoritative post-fingerprint source.
Mirrors the per-brand update_settings() logic in device UI layouts.
"""
brand = caps["brand"]
if brand == "hyundai":
if bundle_platform:
try:
unsupported = set().union(*UNSUPPORTED_LONGITUDINAL_CAR.values())
caps["hyundai_alpha_long_available"] = HYUNDAI_CAR[bundle_platform] not in unsupported
except KeyError:
cloudlog.exception(f"capabilities: unknown hyundai platform {bundle_platform!r}")
elif CP is not None:
caps["hyundai_alpha_long_available"] = bool(CP.alphaLongitudinalAvailable)
elif brand == "subaru":
if bundle_platform:
try:
flags = SUBARU_CAR[bundle_platform].config.flags
caps["subaru_has_sng"] = not bool(flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID))
caps["has_stop_and_go"] = caps["subaru_has_sng"]
except KeyError:
cloudlog.exception(f"capabilities: unknown subaru platform {bundle_platform!r}")
elif CP is not None:
caps["subaru_has_sng"] = not bool(CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID))
caps["has_stop_and_go"] = caps["subaru_has_sng"]
def generate_capabilities(params: Params | None = None) -> dict:
"""Generate a SettingsCapabilities dict from CarParams + boolean params.
@@ -108,6 +140,7 @@ def generate_capabilities(params: Params | None = None) -> dict:
caps["brand"] = bundle_brand
# CarParams-derived capabilities
CP = None
CP_bytes = params.get("CarParamsPersistent")
if CP_bytes is not None:
try:
@@ -129,6 +162,7 @@ def generate_capabilities(params: Params | None = None) -> dict:
# Generic SnG fallback. Brand-specific opaque flags below override.
caps["has_stop_and_go"] = bool(CP.openpilotLongitudinalControl)
except Exception:
CP = None
cloudlog.exception("capabilities: failed to deserialize CarParamsPersistent")
# CarParamsSP-derived capabilities
@@ -142,23 +176,7 @@ def generate_capabilities(params: Params | None = None) -> dict:
except Exception:
cloudlog.exception("capabilities: failed to deserialize CarParamsSPPersistent")
# Brand-specific opaque flags. Mirror Raylib brand-settings logic so the
# device and the dashboard agree on per-platform availability without
# leaking the platform identifier over the wire.
if caps["brand"] == "subaru" and bundle_platform:
try:
flags = SUBARU_CAR[bundle_platform].config.flags
caps["subaru_has_sng"] = not bool(flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID))
caps["has_stop_and_go"] = caps["subaru_has_sng"]
except KeyError:
cloudlog.exception(f"capabilities: unknown subaru platform {bundle_platform!r}")
if caps["brand"] == "hyundai" and bundle_platform:
try:
unsupported = set().union(*UNSUPPORTED_LONGITUDINAL_CAR.values())
caps["hyundai_alpha_long_available"] = HYUNDAI_CAR[bundle_platform] not in unsupported
except KeyError:
cloudlog.exception(f"capabilities: unknown hyundai platform {bundle_platform!r}")
_resolve_brand_capabilities(caps, bundle_platform, CP)
return caps
+13 -13
View File
@@ -1593,47 +1593,47 @@
"label": "Always On"
},
{
"value": 1,
"value": 5,
"label": "5m"
},
{
"value": 2,
"value": 10,
"label": "10m"
},
{
"value": 3,
"value": 15,
"label": "15m"
},
{
"value": 4,
"value": 30,
"label": "30m"
},
{
"value": 5,
"value": 60,
"label": "1h"
},
{
"value": 6,
"value": 120,
"label": "2h"
},
{
"value": 7,
"value": 180,
"label": "3h"
},
{
"value": 8,
"value": 300,
"label": "5h"
},
{
"value": 9,
"value": 600,
"label": "10h"
},
{
"value": 10,
"value": 1440,
"label": "24h"
},
{
"value": 11,
"value": 1800,
"label": "30h (Default)"
}
]
@@ -1664,13 +1664,13 @@
{
"id": "updates",
"title": "Updates",
"description": "Control automatic software updates",
"description": "Control software updates",
"items": [
{
"key": "DisableUpdates",
"widget": "toggle",
"title": "Disable Updates",
"description": "When enabled, automatic software updates will be off. This requires a reboot to take effect.",
"description": "When enabled, software updates will be off. This requires a reboot to take effect.",
"enablement": [
{
"type": "offroad_only"
@@ -37,27 +37,27 @@ sections:
options:
- value: 0
label: Always On
- value: 1
label: 5m
- value: 2
label: 10m
- value: 3
label: 15m
- value: 4
label: 30m
- value: 5
label: 1h
- value: 6
label: 2h
- value: 7
label: 3h
- value: 8
label: 5h
- value: 9
label: 10h
label: 5m
- value: 10
label: 10m
- value: 15
label: 15m
- value: 30
label: 30m
- value: 60
label: 1h
- value: 120
label: 2h
- value: 180
label: 3h
- value: 300
label: 5h
- value: 600
label: 10h
- value: 1440
label: 24h
- value: 11
- value: 1800
label: 30h (Default)
- id: language
title: Language
@@ -9,12 +9,12 @@ description: Software update preferences
sections:
- id: updates
title: Updates
description: Control automatic software updates
description: Control software updates
items:
- key: DisableUpdates
widget: toggle
title: Disable Updates
description: When enabled, automatic software updates will be off. This requires a reboot to take effect.
description: When enabled, software updates will be off. This requires a reboot to take effect.
enablement:
- $ref: '#/macros/offroad'
- $ref: '#/macros/advanced_only'