Skip to content

Migrate vehicle driver to Polestar C3 gRPC backend (v3.0.0)#8

Open
kaohlive wants to merge 69 commits intoCoderaxx:masterfrom
kaohlive:feat/c3-backend
Open

Migrate vehicle driver to Polestar C3 gRPC backend (v3.0.0)#8
kaohlive wants to merge 69 commits intoCoderaxx:masterfrom
kaohlive:feat/c3-backend

Conversation

@kaohlive
Copy link
Copy Markdown
Contributor

Summary

Replaces the legacy @andysmithfal/polestar.js GraphQL client with a
purpose-built C3 gRPC client that talks the same backend the official
Polestar mobile app uses. Unlocks every read and write the app exposes,
and adds corresponding Homey device tiles and flow cards.

  • Reads: battery + charge state, power/current/voltage, session/lifetime/driving kWh, odometer, range, health + tyre pressures, exterior (locks + per-closure open alarms), climate state + target + interior temperature, minutes-to-climate-off, GPS location, OTA update availability + state + new version.
  • Writes: start/stop charging, set charge limit, set amp limit, lock/unlock, unlock trunk, honk + flash, start/stop parking climatization (with temperature + 4 seats + steering-wheel heating), open/close windows.
  • UX: device tiles for every write, 11 flow-action cards, 3 condition cards, 4 device settings (writes master switch, charge-limit slot, windows supported, climate defaults group), automatic hiding of unsupported features per model (e.g. amp limit on Polestar 4).
  • Housekeeping: polestar-2-csv driver marked deprecated via Homey's official flag; tyre pressure capabilities migrated to standard measure_pressure.* sub-ids; measure_power no longer flagged as approximated since C3 returns real power data.

Zero native deps — pure Node http2 + a minimal varint protobuf codec, so nothing extra to load on Homey Pro.

Test plan

  • PoC script (test-c3-poc.js) exercises all 12 gRPC calls against a live Polestar 4
  • Codec round-trip tests for every request/response schema
  • Homey app run validates tile population (battery/charging/odometer/health/exterior/climate/location/OTA)
  • Live write test: set charge limit (Polestar 4 requires Custom slot setting)
  • Live write test: lock/unlock, honk/flash, unlock trunk, climate start
  • Polestar 2 owner feedback on C3 compatibility (test release)
  • Polestar 3 owner feedback

Breaking-change notes

  • App version bumps to 3.0.0 — the new driver auto-adds ~25 capabilities on first run, which migrates existing devices in place.
  • New auth uses client_id=lp8dyrd_10 (mobile-app style). First launch triggers OIDC/PKCE login with stored credentials; tokens refresh transparently afterwards.
  • A c3_backend_disabled setting remains as a rollback lever should any account run into C3 issues.

Changelog: Added support for charge speed details
Changelog: Changed polestar vehicle to new device class car
Changelog: Fix for trigger events
Changelog: hotfix on the csv driver
Changelog: Improved insights on power measurements
Changelog: update to fix api changes
Changelog: Update npm modules
Changelog: Widget, service warning and login bug fix
Changelog: preview images
Changelog: fix widget update on device state change
Changelog: stability improvement
Changelog: service warning improvement
kaohlive and others added 28 commits April 21, 2025 01:29
Changelog: Another login fix
Changelog: Fine tune the login error message
Changelog: Added support for new ev charging states
Changelog: Fixed CSV receiver driver
Changelog: Added upgrade path for existing cars for homey energy
Changelog: More rebost energy upgrade detection
Changelog: Changed api, no linger supports images
Changelog: Changed api, no linger supports images
Changelog: Added support to switch to miles
Changelog: Rounding and stability improvements
Replace the legacy @andysmithfal/polestar.js GraphQL path with a
purpose-built C3 client (clone_modules/polestar-c3/) that talks raw
HTTP/2 gRPC against the same backend the Polestar mobile app uses.
This unlocks fields the public GraphQL stopped returning (power,
current, voltage, charge type) and brings new telemetry (tyre
pressures, lifetime driving consumption, per-session charge kWh).

Restored capabilities: measure_current, measure_power, meter_power
(the last one now accumulates cleanly during CHARGING instead of
being overwritten by unrelated driving-consumption values).

New capabilities: measure_voltage, measure_polestarChargingType,
measure_polestarDrivingKwh, measure_polestarSessionKwh,
alarm_polestarTyrePressure, measure_polestarTyrePressureFL/FR/RL/RR.

The new client is zero-native-deps (Node's built-in http2 + a
minimal protobuf varint codec) so it drops straight onto Homey Pro.
A c3_backend_disabled setting falls back to the legacy client as a
rollback lever for any account where C3 misbehaves.

Bumps to 2.8.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the C3 chronos services (ChargeNow, TargetSoc, AmpLimit) through the
JS client so users can actually control their car from Homey, not just
read status. Adds flow action + condition cards and in-tile sliders for
charge limit and amp limit plus momentary Start/Stop charging buttons.

Device setting "Allow remote commands" is a master-switch kill-flag —
writes fail instantly when turned off. Replaces an earlier PIN prototype
that produced ugly titleFormatted strings.

Device setting "Charge limit slot" (Daily/LongTrip/Custom/Unspecified)
is exposed because Polestar 4 silently rejects writes to the Daily slot
but commits to Custom — and we don't yet know which slot other models
prefer. Default stays on Daily to match the Python reference; Polestar 4
users switch once. Writes that don't land surface a log warning pointing
at this setting.

gRPC reliability: HTTP/2 keep-alive ping every 30 s, goaway-event handler
that nulls the session eagerly, and one automatic retry on transient
errors (INTERNAL/UNAVAILABLE/GOAWAY/ECONNRESET/ETIMEDOUT) with a fresh
session. Resolves the cold-session GOAWAY hit we saw on the first write
after idle.

UNIMPLEMENTED responses are now captured in a persistent device-store
flag so unsupported features (e.g. Polestar 4 returns "Functionality not
supported: AMP_LIMIT") hide the slider tile and skip further calls on
subsequent runs instead of spamming the log forever.

Charge limit polling moved to the fast 60 s cycle (down from 15 min) so
changes made in the Polestar app or in-car menu show up within a minute.
Amp limit stays on the slow 15 min cycle — it rarely changes and is
skipped entirely on vehicles that don't support it.

Set-response parsing no longer trusts the echoed level: the P4 chronos
envelope returns a stale value, so we rely on a delayed Get 3 s after
the write to reflect the committed state. The slider stays on the
user-chosen value until that re-read either confirms or corrects it,
avoiding the disorienting snap-back-then-forward UX.

The custom measure_polestarTyrePressure{FL,FR,RL,RR} capabilities are
also migrated to measure_pressure.{front_left,front_right,rear_left,
rear_right} with a kPa unit override — the standard capability gets
better UI + Insights integration. Legacy JSON files remain with a
_deprecated marker so existing devices can migrate via removeCapability;
they will be deleted in a future release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire two more C3 services so the car's external closures and parking
climatization show up as Homey tiles:

  ExteriorService/GetLatestExterior  — locks, per-door/window open state,
  hood, tailgate, sunroof, charging-port cover
  ParkingClimatizationService/GetLatestParkingClimatization — running
  state, target temperature, interior temperature, minutes remaining

Both are unary calls on the 60 s fast cycle so a user walking up to the
car sees door-opened alarms within the minute.

Homey capabilities chosen so they can be switched to setable later
without a rename, once lock/unlock + climate start/stop writes land:

  locked                     — central lock state (setable:false for now)
  onoff.climate              — climatization running/not (setable:false)
  target_temperature         — last requested temp (setable:false)
  measure_temperature        — interior temperature (inherently read-only)
  measure_polestarClimateRemaining — minutes left on the 30-min parking
                               climatization session; blank when idle
  alarm_contact.door_{fl,fr,rl,rr} — per-door open alarms
  alarm_contact.window_any   — aggregate window-open alarm
  alarm_contact.{tailgate,hood,sunroof,tank_lid} — body + charge port
                               open alarms

The capabilitiesOptions setable:false overrides keep the tiles visible
as read-only indicators. When writes arrive they just drop the override
and register listeners.

DigitalTwin flat-field format was reverse-engineered against a live
Polestar 4 session: exterior fields 2-16 hold ints (LockStatus enum on
2/16, OpenStatus 1/2/3 on all others), climate fields 2,3,7,8 are the
ones we trust (running_status, time_remaining in minutes, current and
requested temperature as float32). ventilation_only and request_type
are still speculative per-model so they stay in debug logs only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Complete the invocation surface so every write Polestar's mobile app
offers is available from Homey flows and device tiles, plus a couple
of reads that round out the picture.

Writes (via /invocation.InvocationService):
  lock / unlock                  — wired to the setable `locked` tile
  unlock_trunk                   — tile button + flow action
  honk_flash                     — tile button (flash-only default) +
                                   flow action with HONK/FLASH/BOTH
  climate_start / climate_stop   — onoff.climate toggle reads seat and
                                   steering-wheel heating defaults from
                                   new device settings; a separate flow
                                   action exposes all 6 params per-call
                                   for one-off custom sessions
  windows_open / windows_close   — flow actions (OPEN_ALL / CLOSE_ALL —
                                   the API doesn't support per-window)

Reads:
  Location (/dtlinternet.DtlInternetService/GetLastKnownLocation) —
    parser handles three documented response layouts, populates a
    measure_polestarLocation string tile on the 15-min slow cycle.
    A get_location flow action refreshes on-demand and exposes
    latitude, longitude, and a formatted "lat, lng" string as tokens
    for downstream cards (push notifications with a map link).
  OTA (/ota_mobcache.OtaDiscoveryService/GetSoftwareInfo) —
    alarm_polestarOtaAvailable fires on DOWNLOAD_READY /
    DOWNLOAD_COMPLETED / INSTALLATION_DEFERRED / INSTALLATION_SCHEDULED.
    State text falls back to a friendly "No pending update" when the
    car returns an empty CarSoftwareInfo (no update queued).
    Write side (schedule/install/cancel) deliberately omitted — the
    risk of a misconfigured flow triggering an install mid-drive
    outweighs the convenience over the Polestar app.

Flow cards:
  11 action cards (charge ±, limits, lock ±, trunk, honk, climate ±,
  windows ±, get_location)
  3 conditions (is_locked, target_soc_is, amp_limit_is)
  1 condition trigger (is_locked) + standard triggers from button.*,
  locked, onoff.climate capabilities.

Housekeeping:
  The four legacy measure_polestarTyrePressure{FL,FR,RL,RR} JSONs are
  removed — they never shipped in a release and the migration loop
  that would auto-remove them from existing devices is gone too, since
  no one has those capabilities in their device state. All current and
  future users take the standard measure_pressure.* sub-IDs directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps to 3.0.0 and wraps up the C3-migration work.

- Mark the legacy polestar-2-csv driver as deprecated via Homey's
  official driver flag so new pairings are rejected but existing
  devices keep working. The Car Stats Viewer webhook still supplies
  live telemetry the C3 backend doesn't expose (speed, gear,
  ignition, battery temperature, trip summaries) so it stays around
  for P2 owners who rely on it.

- Rewrite README.md with real feature list and deprecation note,
  rewrite README.txt (app-store listing) with concrete read/write
  capabilities and a privacy section, and refresh the
  .homeycompose/app.json description + tags to reflect what the
  app actually does now instead of generic marketing copy.

- Drop the `measure_power: { approximated: true }` override. The
  new C3 client returns real power_watts from the car, not a
  calculated estimate, so Homey Energy can treat it as a real
  sensor reading.

- Add a `windows_supported` device setting and wire it into the
  same OPTIONAL_FEATURES machinery that already auto-hides
  amp_limit on Polestar 4. Unchecking hides the two window
  buttons; the backend auto-disables the feature when it returns
  UNIMPLEMENTED.

- Make `target_temperature` fall back to the configured climate
  default when the C3 climatization response has no
  requested_temp (happens whenever climate isn't running). Tile
  no longer stays empty on a fresh device.

- After every state-changing write (lock, climate, windows,
  charging, trunk) schedule two follow-up reads at +3 s and +10 s
  so the UI reflects the actual state faster than the 60 s regular
  poll cycle. Window opens/closes use a wider +15 s second refresh
  because physical movement takes longer to complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Point every custom capability used by the active vehicle driver at an
SVG in drivers/vehicle/assets/ so the driver stops depending on files
that live in the deprecated polestar-2-csv driver's folder. Copies
battery-75, battery-100, powerdc, hvbattery, location and update from
the CSV driver into the vehicle driver's own asset folder.

Add tire-pressure.svg and wire it to alarm_polestarTyrePressure —
Homey threw a render error on the iconless alarm tile.

Fill in the two remaining missing icons (alarm_polestarOtaAvailable,
measure_polestarOtaVersion) with update.svg so all three OTA
capabilities share the same glyph.

CSV-only capabilities (speed, gear, ignition, temperature, etc.)
keep pointing at their own driver's assets — they live together and
will move as a set when that driver is eventually removed.

Adds a 3.0.0 changelog entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Changelog: Polish on icons etc
@kaohlive kaohlive marked this pull request as draft April 18, 2026 14:08
@kaohlive kaohlive marked this pull request as ready for review April 18, 2026 14:10
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.

1 participant