Skip to content

fix(EU): correct CCSP Stamp so remote commands work across Europe - #47

Open
RJvdBurg wants to merge 1 commit into
schmidtwmark:mainfrom
RJvdBurg:fix/eu-ccsp-stamp
Open

fix(EU): correct CCSP Stamp so remote commands work across Europe#47
RJvdBurg wants to merge 1 commit into
schmidtwmark:mainfrom
RJvdBurg:fix/eu-ccsp-stamp

Conversation

@RJvdBurg

@RJvdBurg RJvdBurg commented Aug 5, 2026

Copy link
Copy Markdown

Problem

Remote commands failed for European Hyundai/Kia vehicles: login and status reads worked, but every control command (lock/unlock, climate, charge) came back HTTP 403 Forbidden. Reported in #62 (Ioniq 5) and #68 (Santa Fe, Netherlands), where the community specifically asked how Home Assistant sends commands to the EU API.

Root cause: the Stamp header was generated with HMAC-SHA256 over an ISO8601 timestamp. That isn't the CCSP scheme — the EU servers tolerate it on read endpoints but reject it with 403 on control endpoints. Verified against hyundai_kia_connect_api (Home Assistant), which is the reference implementation for this API.

Changes (all ported from hyundai_kia_connect_api)

  1. Stamp generation — base64(CFB XOR "<appId>:<unixSeconds>"), XOR truncated to the shorter operand, instead of HMAC-SHA256/ISO8601. The authCfb secret already in the repo is byte-identical to HA's CFB; only the algorithm was wrong. Output verified byte-for-byte against HA's _get_stamp. Removes the now-unused CryptoKit imports. This is the fix for the 403.

  2. Control-error decoding — the CCSP retCode:"F" / resCode envelope is now translated into typed APIErrors (duplicate request 4004, timeout 4081/9999, unavailable 5031, rate-limited 5091, unsupported 4005, bad deviceId 4002, auth 7501) instead of surfacing a bare HTTP 400: bad request. Mirrors _check_response_for_errors. Guarded on the CCSP envelope, so US/CA/CN clients are unaffected.

  3. CCS2 climate payloaddrvSeatLoc is now derived from the vehicle's distance unit ("L" for km-based LHD markets, "R" for mile-based UK/IE) and the driver/passenger seat channels swap for RHD, instead of being hardcoded to "R". sideRearMirrorHeating is derived from the heating level. Matches start_climate / #1192.

  4. Live status refresh — the cached flag is now honoured on EU CCS2 vehicles. A non-cached refresh wakes the car (GET /ccs2/carstatus), waits ~20s, then reads /latest, so a just-sent command is reflected instead of a stale cached snapshot. Ports _force_refresh_vehicle_state_ccs2 / #1184. bbcli's status action now requests a fresh read.

Testing

  • swift build clean, swift test green (309 tests, incl. new CCSPResponseErrorTests).
  • Verified live against a Hyundai Santa Fe (hybrid) in the Netherlands: lock/unlock and command acceptance now succeed (previously 403); resCode errors surface clearly; force-refresh returns accurate live state.

Note

Remote climate on that specific hybrid Santa Fe does not execute even though the API accepts the command (retCode:"S") — confirmed disabled in the official MyHyundai app for the vehicle, i.e. a manufacturer/market capability limit, not a client issue. Related to the open, unsolved ICE/PHEV remote-start question in #1224. All four changes here are independent of that limitation.

…tus refresh

Ports the proven behaviour of Home Assistant's hyundai_kia_connect_api to
the European Hyundai/Kia clients. Remote actions previously failed across
Europe (e.g. the Netherlands) with HTTP 403; reads worked but every control
command was rejected.

- Stamp: generate the CCSP `Stamp` as base64(CFB XOR "<appId>:<unixSeconds>")
  instead of HMAC-SHA256 over an ISO8601 timestamp. The HMAC form was
  tolerated on read endpoints but rejected with 403 on control endpoints,
  which is what broke lock/unlock/climate/charge across the EU. Verified
  byte-identical to hyundai_kia_connect_api `_get_stamp`. Drops the now-unused
  CryptoKit imports.

- Errors: decode the CCSP `retCode:"F"` / `resCode` envelope into typed,
  user-facing APIErrors (duplicate request, timeout, rate limit, etc.) instead
  of surfacing a bare "HTTP 400: bad request". Mirrors
  `_check_response_for_errors`. Guarded on the CCSP envelope so US/CA/CN are
  unaffected.

- Climate: derive `drvSeatLoc` from the vehicle's distance unit ("L" for
  km-based LHD markets, "R" for mile-based UK/IE) and swap driver/passenger
  seat channels for RHD, instead of hardcoding "R". Also derive
  sideRearMirrorHeating from the heating level. Matches `start_climate`.

- Status: honour the `cached` flag on EU CCS2 vehicles. A non-cached refresh
  now wakes the car (GET /ccs2/carstatus), waits ~20s, then reads /latest, so
  a just-sent command is reflected instead of a stale snapshot. Ports
  `_force_refresh_vehicle_state_ccs2`. bbcli's status action now requests a
  fresh read.

Adds CCSPResponseErrorTests. swift build clean; 309 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants