From dcd382ffb8ff924d7cf2417290dcb0ed32b79d72 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 3 Oct 2025 19:42:46 +0200 Subject: [PATCH 01/10] sunnylink: enhanced param keys fetch with data type (#1308) * refactor: enhance parameter handling and add new parameter retrieval method - Refactored `get_param_as_byte` for better modularity and added `_to_bytes` helper function. - Introduced `getParamsAllKeysV1` to retrieve all keys with enhanced metadata. * refactor: update parameter handling and response structure in sunnylink - Modified `_to_bytes` to accept `bytes` type for improved type consistency. - Adjusted response keys in `sunnylinkd` for clarity. * fix: update `get_param_as_byte` to use corrected method for default values - Replaced `get_default` with `get_default_value` for accurate param retrieval. - Ensures consistent handling of default parameter values. * refactor: remove redundant `None` check in `sunnylinkd.py` - Streamlined parameter iteration by eliminating unnecessary `None` validation. - Simplifies logic for constructing `params_dict`. * refactor: streamline `sunnylinkd` response by removing redundant `keys_v1` field - Simplified return structure by excluding unused `keys_v1` key. - Reduces response payload for improved efficiency. * refactor: split `getParamsAllKeys` into distinct functions for improved clarity - Added `getParamsAllKeysV1` to preserve original metadata-rich behavior. - Revised `getParamsAllKeys` to return a simplified list of parameter keys. * style: remove extraneous newline in `sunnylinkd.py` - Ensures consistent formatting and adheres to style guidelines. * Test --- sunnypilot/sunnylink/athena/sunnylinkd.py | 19 +++++++++++++++++- sunnypilot/sunnylink/utils.py | 24 +++++++++++++---------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index 25a77c367..d1b38d656 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -178,12 +178,29 @@ def getParamsAllKeys() -> list[str]: return keys +@dispatcher.add_method +def getParamsAllKeysV1() -> dict[str, str]: + available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] + + params_dict: dict[str, list[dict[str, str | bool | int | None]]] = {"params": []} + for key in available_keys: + value = get_param_as_byte(key, get_default=True) + params_dict["params"].append({ + "key": key, + "type": int(params.get_type(key).value), + "default_value": base64.b64encode(value).decode('utf-8') if value else None, + }) + + return {"keys": json.dumps(params_dict.get("params", []))} + + @dispatcher.add_method def getParams(params_keys: list[str], compression: bool = False) -> str | dict[str, str]: params = Params() + available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] try: - param_keys_validated = [key for key in params_keys if key in getParamsAllKeys()] + param_keys_validated = [key for key in params_keys if key in available_keys] params_dict: dict[str, list[dict[str, str | bool | int]]] = {"params": []} for key in param_keys_validated: value = get_param_as_byte(key) diff --git a/sunnypilot/sunnylink/utils.py b/sunnypilot/sunnylink/utils.py index 91c078879..70791c4ce 100644 --- a/sunnypilot/sunnylink/utils.py +++ b/sunnypilot/sunnylink/utils.py @@ -60,21 +60,25 @@ def get_api_token(): print(f"API Token: {token}") -def get_param_as_byte(param_name: str, params=None) -> bytes | None: - """Get a parameter as bytes. Returns None if the parameter does not exist.""" - params = params or Params() # Use existing Params instance if provided - param = params.get(param_name) - if param is None: +def _to_bytes(value: bytes | None, param_type: ParamKeyType) -> bytes | None: + """Convert a parameter value to bytes based on its type.""" + if value is None: return None - param_type = params.get_type(param_name) if param_type == ParamKeyType.BYTES: - return bytes(param) - elif param_type == ParamKeyType.JSON: - return json.dumps(param).encode('utf-8') - return str(param).encode('utf-8') + return bytes(value) + if param_type == ParamKeyType.JSON: + return json.dumps(value).encode("utf-8") + return str(value).encode("utf-8") +def get_param_as_byte(param_name: str, params=None, get_default=False) -> bytes | None: + """Get a parameter as bytes. Returns None if the parameter does not exist.""" + params = params or Params() + value = params.get(param_name) if not get_default else params.get_default_value(param_name) + param_type = params.get_type(param_name) + return _to_bytes(value, param_type) + def save_param_from_base64_encoded_string(param_name: str, base64_encoded_data: str, is_compressed=False) -> None: """Save a parameter from bytes. Overwrites the parameter if it already exists.""" params = Params() From 1bfecbc9c2e81d0fb7879ba5502b401053824b09 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 3 Oct 2025 19:43:13 +0200 Subject: [PATCH 02/10] sunnylink: Enabled by default (#1312) Enable sunnylink by default --- common/params_keys.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/params_keys.h b/common/params_keys.h index 0f5d9cf44..0844fff2b 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -192,7 +192,7 @@ inline static std::unordered_map keys = { {"SunnylinkCache_Users", {PERSISTENT, STRING}}, {"SunnylinkDongleId", {PERSISTENT, STRING}}, {"SunnylinkdPid", {PERSISTENT, INT}}, - {"SunnylinkEnabled", {PERSISTENT, BOOL}}, + {"SunnylinkEnabled", {PERSISTENT, BOOL, "1"}}, // Backup Manager params {"BackupManager_CreateBackup", {PERSISTENT, BOOL}}, From e999839a5715dbfadda42cace0aa50e01954bf30 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 3 Oct 2025 17:46:13 -0400 Subject: [PATCH 03/10] ui: replace `static_cast` with `std::nearbyint` for convert & rounding (#1313) * ui: replace `static_cast` with `std::nearbyint` for precise rounding * revert --- selfdrive/ui/sunnypilot/qt/widgets/controls.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h index 03cb46138..e9e1e4bb8 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -500,7 +501,7 @@ private: int getParamValueScaled() { const auto param_value = QString::fromStdString(params.get(key)); - return static_cast(param_value.toFloat() * 100); + return std::nearbyint(param_value.toFloat() * 100.0f); } void setParamValueScaled(const int new_value) { From 1e7fc15a040dca7f3a3e7143edb6333086d224ff Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 3 Oct 2025 18:24:24 -0400 Subject: [PATCH 04/10] Speed Limit Assist: Update state machine for PCM long cars (#1311) * Speed Limit Assist: Update state machine for PCM long cars * new test to catch this wrong transition * tldr --- .../lib/speed_limit/speed_limit_assist.py | 14 ++++++++------ .../tests/test_speed_limit_assist.py | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index 5d798949a..302d8cd14 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -247,11 +247,11 @@ class SpeedLimitAssist: # PENDING elif self.state == SpeedLimitAssistState.pending: - if self._has_speed_limit: - if self.v_offset < LIMIT_SPEED_OFFSET_TH: - self.state = SpeedLimitAssistState.adapting - else: - self.state = SpeedLimitAssistState.active + if self.target_set_speed_confirmed: + self._update_confirmed_state() + elif self.speed_limit_changed: + self.state = SpeedLimitAssistState.preActive + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) # PRE_ACTIVE elif self.state == SpeedLimitAssistState.preActive: @@ -275,9 +275,11 @@ class SpeedLimitAssist: elif self.long_engaged_timer <= 0: if self.target_set_speed_confirmed: self._update_confirmed_state() - else: + elif self._has_speed_limit: self.state = SpeedLimitAssistState.preActive self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + else: + self.state = SpeedLimitAssistState.pending enabled = self.state in ENABLED_STATES active = self.state in ACTIVE_STATES diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index 3b2716265..d2c7a4716 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -96,6 +96,12 @@ class TestSpeedLimitAssist: assert self.sla.state == SpeedLimitAssistState.preActive assert self.sla.is_enabled and not self.sla.is_active + def test_transition_disabled_to_pending_no_speed_limit_not_max_initial_set_speed(self): + for _ in range(int(3. / DT_MDL)): + self.sla.update(True, False, SPEED_LIMITS['highway'], 0, SPEED_LIMITS['city'], 0, 0, False, 0, self.events_sp) + assert self.sla.state == SpeedLimitAssistState.pending + assert self.sla.is_enabled and not self.sla.is_active + def test_preactive_to_active_with_max_speed_confirmation(self): self.sla.state = SpeedLimitAssistState.preActive self.sla.update(True, False, SPEED_LIMITS['city'], 0, self.pcm_long_max_set_speed, SPEED_LIMITS['highway'], @@ -120,12 +126,20 @@ class TestSpeedLimitAssist: def test_pending_to_active_when_speed_limit_available(self): self.sla.state = SpeedLimitAssistState.pending - self.sla.update(True, False, SPEED_LIMITS['city'], 0, self.pcm_long_max_set_speed, SPEED_LIMITS['city'], SPEED_LIMITS['city'], True, 0, self.events_sp) + self.sla.v_cruise_cluster_prev = self.pcm_long_max_set_speed + self.sla.prev_v_cruise_cluster_conv = round(self.pcm_long_max_set_speed * self.speed_conv) + + self.sla.update(True, False, SPEED_LIMITS['highway'], 0, self.pcm_long_max_set_speed, + SPEED_LIMITS['highway'], SPEED_LIMITS['highway'], True, 0, self.events_sp) assert self.sla.state == SpeedLimitAssistState.active def test_pending_to_adapting_when_below_speed_limit(self): self.sla.state = SpeedLimitAssistState.pending - self.sla.update(True, False, SPEED_LIMITS['city'] + 5, 0, self.pcm_long_max_set_speed, SPEED_LIMITS['city'], SPEED_LIMITS['city'], True, 0, self.events_sp) + self.sla.v_cruise_cluster_prev = self.pcm_long_max_set_speed + self.sla.prev_v_cruise_cluster_conv = round(self.pcm_long_max_set_speed * self.speed_conv) + + self.sla.update(True, False, SPEED_LIMITS['highway'] + 5, 0, self.pcm_long_max_set_speed, + SPEED_LIMITS['highway'], SPEED_LIMITS['highway'], True, 0, self.events_sp) assert self.sla.state == SpeedLimitAssistState.adapting assert self.sla.is_enabled and self.sla.is_active From 282a8b093ddb8f611d71bfee7bcde9e2e3281ee2 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 4 Oct 2025 07:42:44 +0200 Subject: [PATCH 05/10] Revert "sunnylink: enhanced param keys fetch with data type" (#1316) Revert "sunnylink: enhanced param keys fetch with data type (#1308)" This reverts commit dcd382ffb8ff924d7cf2417290dcb0ed32b79d72. --- sunnypilot/sunnylink/athena/sunnylinkd.py | 19 +---------------- sunnypilot/sunnylink/utils.py | 26 ++++++++++------------- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index d1b38d656..25a77c367 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -178,29 +178,12 @@ def getParamsAllKeys() -> list[str]: return keys -@dispatcher.add_method -def getParamsAllKeysV1() -> dict[str, str]: - available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] - - params_dict: dict[str, list[dict[str, str | bool | int | None]]] = {"params": []} - for key in available_keys: - value = get_param_as_byte(key, get_default=True) - params_dict["params"].append({ - "key": key, - "type": int(params.get_type(key).value), - "default_value": base64.b64encode(value).decode('utf-8') if value else None, - }) - - return {"keys": json.dumps(params_dict.get("params", []))} - - @dispatcher.add_method def getParams(params_keys: list[str], compression: bool = False) -> str | dict[str, str]: params = Params() - available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] try: - param_keys_validated = [key for key in params_keys if key in available_keys] + param_keys_validated = [key for key in params_keys if key in getParamsAllKeys()] params_dict: dict[str, list[dict[str, str | bool | int]]] = {"params": []} for key in param_keys_validated: value = get_param_as_byte(key) diff --git a/sunnypilot/sunnylink/utils.py b/sunnypilot/sunnylink/utils.py index 70791c4ce..91c078879 100644 --- a/sunnypilot/sunnylink/utils.py +++ b/sunnypilot/sunnylink/utils.py @@ -60,24 +60,20 @@ def get_api_token(): print(f"API Token: {token}") -def _to_bytes(value: bytes | None, param_type: ParamKeyType) -> bytes | None: - """Convert a parameter value to bytes based on its type.""" - if value is None: +def get_param_as_byte(param_name: str, params=None) -> bytes | None: + """Get a parameter as bytes. Returns None if the parameter does not exist.""" + params = params or Params() # Use existing Params instance if provided + param = params.get(param_name) + if param is None: return None - if param_type == ParamKeyType.BYTES: - return bytes(value) - if param_type == ParamKeyType.JSON: - return json.dumps(value).encode("utf-8") - return str(value).encode("utf-8") - - -def get_param_as_byte(param_name: str, params=None, get_default=False) -> bytes | None: - """Get a parameter as bytes. Returns None if the parameter does not exist.""" - params = params or Params() - value = params.get(param_name) if not get_default else params.get_default_value(param_name) param_type = params.get_type(param_name) - return _to_bytes(value, param_type) + if param_type == ParamKeyType.BYTES: + return bytes(param) + elif param_type == ParamKeyType.JSON: + return json.dumps(param).encode('utf-8') + return str(param).encode('utf-8') + def save_param_from_base64_encoded_string(param_name: str, base64_encoded_data: str, is_compressed=False) -> None: """Save a parameter from bytes. Overwrites the parameter if it already exists.""" From cca3be3a9682c5df0b542044a87f960e5d186b78 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 4 Oct 2025 10:08:08 +0200 Subject: [PATCH 06/10] relock after inplace metadrive update (#1321) relock after inplace metadrive update (#36256) * relock after inplace metadrive update * Revert "relock after inplace metadrive update" This reverts commit 18193ffe34b66085e18605e6c9289ddcd658844d. * just the hash (cherry picked from commit 4d53a26a06973a7d6fa4fef1e238a3bb9e64e67c) Co-authored-by: Armand du Parc Locmaria --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 25d2b626c..b871cc8da 100644 --- a/uv.lock +++ b/uv.lock @@ -945,7 +945,7 @@ dependencies = [ { name = "yapf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ - { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:fbf0ea9be67e65cd45d38ff930e3d49f705dd76c9ddbd1e1482e3f87b61efcef" }, + { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, ] [package.metadata] From 18abe218d9115ac2608a6c6aaf458896120b61c7 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 4 Oct 2025 18:19:16 +0200 Subject: [PATCH 07/10] ci: Disable homebrew cache for MacOS builds (#1322) * maybe Revert "fuck" This reverts commit cb072a73b1b44ac3abeb50196be0956f3b7a7e14. Revert "i dunno anymore" This reverts commit 4d3c84ecb0e7830a552237e824995bc255007a1b. i dunno anymore fuck * maybe * try to fix the pipelines --- .github/workflows/selfdrive_tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 41d8156ca..85b7c61e5 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -115,7 +115,9 @@ jobs: - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - name: Homebrew cache uses: ./.github/workflows/auto-cache + if: false # disabling the cache for now because it is breaking macos builds... with: + save: false # No need save here if we manually save it later conditionally path: ~/Library/Caches/Homebrew key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | @@ -136,6 +138,7 @@ jobs: - name: Getting scons cache uses: ./.github/workflows/auto-cache with: + save: false # No need save here if we manually save it later conditionally path: /tmp/scons_cache key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | From 520649b89361f9145818b019c6a659ad2a5f9f67 Mon Sep 17 00:00:00 2001 From: dzid26 Date: Sun, 5 Oct 2025 08:34:12 +0100 Subject: [PATCH 08/10] params: Fix loading delay on startup (#1297) params frame counter init make sure param_store.update gets processed on first call Co-authored-by: Jason Wen --- sunnypilot/selfdrive/controls/lib/param_store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunnypilot/selfdrive/controls/lib/param_store.py b/sunnypilot/selfdrive/controls/lib/param_store.py index 65a017534..785fe9c8f 100644 --- a/sunnypilot/selfdrive/controls/lib/param_store.py +++ b/sunnypilot/selfdrive/controls/lib/param_store.py @@ -22,7 +22,7 @@ class ParamStore: self.keys = universal_params + brand_params self._params = {} - self.frame = 0 + self.frame = -1 def update(self, params: Params) -> None: self.frame += 1 From 5a309daee6a1949eaf4ae0490ae9ec6fcfb3c152 Mon Sep 17 00:00:00 2001 From: dzid26 Date: Sun, 5 Oct 2025 08:38:49 +0100 Subject: [PATCH 09/10] docs: clarify pedal press (#1289) Co-authored-by: Jason Wen --- sunnypilot/mads/mads.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sunnypilot/mads/mads.py b/sunnypilot/mads/mads.py index 21e7f7d3e..7eab55e6e 100644 --- a/sunnypilot/mads/mads.py +++ b/sunnypilot/mads/mads.py @@ -61,6 +61,7 @@ class ModularAssistiveDrivingSystem: self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode") def pedal_pressed_non_gas_pressed(self, CS: structs.CarState) -> bool: + # ignore `pedalPressed` events caused by gas presses if self.events.has(EventName.pedalPressed) and not (CS.gasPressed and not self.selfdrive.CS_prev.gasPressed and self.disengage_on_accelerator): return True From abfac8289a5a029c22d0083f23a63e9e46f41585 Mon Sep 17 00:00:00 2001 From: infiniteCable2 Date: Sun, 5 Oct 2025 13:41:49 +0200 Subject: [PATCH 10/10] Update opendbc_repo --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 385bc579f..f6b479a78 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 385bc579fa634572ea447674ede861896d5c2078 +Subproject commit f6b479a78749b7169097215828011be1f4db844f