Skip to content

Reused one of your capabilities and added more translations, remove ps2 - #6

Open
kaohlive wants to merge 95 commits into
Coderaxx:masterfrom
kaohlive:master
Open

Reused one of your capabilities and added more translations, remove ps2#6
kaohlive wants to merge 95 commits into
Coderaxx:masterfrom
kaohlive:master

Conversation

@kaohlive

Copy link
Copy Markdown
Contributor

No description provided.

@kaohlive

Copy link
Copy Markdown
Contributor Author

Here is a new pull request, it cleans up the vehicle driver and adds translations for NL in your driver.
I also switched to the polestarbattery measurement instead of the measure_battery. For a car the number view is more usefull than the bar icon and the battery warning also is not usefull IMO.

@kaohlive

Copy link
Copy Markdown
Contributor Author

@Coderaxx can you merge this request please?
This version should be ready for a release IMO and would like to see it released.

@kaohlive

Copy link
Copy Markdown
Contributor Author

@Coderaxx hey, I added support for the new feature of showing the charing KW and amps to the vehicle driver. Very nice that it is now available.
Can you please merge this pull request?

kaohlive added 19 commits May 6, 2024 23:09
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
kaohlive and others added 30 commits April 18, 2026 02:45
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
Polls kept firing after a device was removed or while the app was
restarting, so each in-flight updateVehicleState() eventually hit
setCapabilityValue on a device Homey had already forgotten about —
surfacing as 'Not Found: Device with ID …' in the app log.

Adds a _cleanup() that clearIntervals both poll timers and closes the
gRPC session, invoked from onDeleted() and onUninit(). Every update
method (vehicle, health, exterior, climate, location, ota) and the
post-write _scheduleStateRefresh closures now early-return when
_destroyed is set, so in-flight promises can't race past the cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CHARGING_STATUS_DONE now surfaces as 'plugged_in' instead of
  'plugged_in_paused'. A completed session is not a pause — the
  Polestar app shows "Finished" for this state, and users were
  getting "Paused" on their Homey tile.

- Honk/flash flow card retitled to "Find my car — [action]". The
  previous titleFormatted was just the action value, making the
  card hard to identify in a flow list.

- Added a 'Start climate (defaults)' flow action that takes no
  arguments and reads temperature + seat/steering heating from
  device settings. Mirrors the one-tap climate behaviour of the
  Polestar mobile app; complements the existing argument-driven
  climate_start action for custom sessions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Car Stats Viewer Android app sometimes pushes driving points with
no altitude or state-of-charge field (GPS dropout, sensor delay).
trip-ended trigger then crashed on undefined.toFixed(0).

Replaces the bare .toFixed() calls with a fmt() helper that checks
Number.isFinite first and falls back to the existing "Unavailable"
localization. energy_delta accumulator also now defaults a missing
field to 0 so a single bad point can't NaN the whole sum.

Touches only the deprecated CSV driver; the new vehicle driver is
unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues spotted in a tester's pair-flow log.

- The settings-changed listener was logging user_email and
  user_password values verbatim. Even though the password is
  encrypted-at-rest, dropping the ciphertext in a debug log is the
  kind of thing that ends up in a screenshot. Now we log only
  '[N chars]' or '[empty]' for user_email, user_password, and
  polestar_token.

- testlogin returned a plain boolean, so the pair UI couldn't tell
  the difference between 'login failed' and 'login OK but the
  account has no vehicles'. Both paths showed the same red
  'Login validation failed' alert, which confused at least one
  tester whose login was actually fine. testlogin now returns
  { ok, reason } so the HTML can show a specific 'no vehicles'
  message that points at account selection or a transient backend
  hiccup as likely causes. Old boolean return is still accepted
  by the pair view as a fallback.

While in the area, fixes a small bug where a 'Credential test ok'
log line referenced `vehicles` before its var was declared.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark_Beech (self-hosted Homey) hit an unhandledRejection during
pairing because his network interfaces are not named wlan0/eth0/eth1
— the salt derivation in lib/homeycrypt.js fell off the if-else chain
with mac=null and crashed on null.join('').

Adds a fallback that iterates os.networkInterfaces() and picks the
first non-internal interface with a non-zero MAC, sorted by name so
the same interface keeps being chosen across reboots. Preserves the
preferred wlan0/eth0/eth1 order so existing users' encrypted
passwords still decrypt with the same MAC they were salted with.

If no usable MAC can be found at all (extremely rare, would only
happen in a stripped-down container), throws an explicit error
instead of the cryptic null.join crash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Translations: added de, fr, it, sv, es, da, ru, pl, ko, ja to every
capability title/unit, driver name, driver capabilitiesOptions, flow
cards (titles, hints, titleFormatted, dropdown labels, tokens), and
device settings (labels, hints, dropdown values). Source en/no/nl
strings preserved verbatim. AI-generated — non-elegant phrasing can
be reported and fixed in follow-ups.

Widget: the charging block now mirrors the orange-label / white-value
pattern of charge and range. Power (kW) is the primary number; current
and time-remaining form a subtitle. Numbers gracefully fall back to
"— kW" / omitted parts when capability values are missing, instead of
showing "0 A | 0 kW · 0h 0m".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The legacy Car Stats Viewer driver stored a webhook_slug only when the
deprecated trip-image API was used. Newer pairings leave it null, but
the trip-ended handler still ran `base64url.encode(this.slug)` as the
first step. That throws ERR_INVALID_ARG_TYPE on null and aborts the
trigger before any flow ever fires.

The encoded slug fed only image URLs that are now fully commented out,
so the line is removed entirely along with its commented-out block.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3.2.2 left the lastTrip/tripInfo/tripScore image tokens in the flow
definition (removing them would break user flows referencing those
tokens) but the trigger payload no longer supplied them, so every
trip-ended event threw "Invalid value for token lastTrip. Expected
object but got undefined".

Create three Homey images on device init, all pointing at the driver
brand asset. The token payload is schema-valid again; existing flows
keep firing. Per-trip renders are gone with the deprecated backend, so
notifications now show the Polestar driver image instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The auto-generated alarm_contact flow cards render as "{title} is open" /
"{title} is not open". With the previous titles ending in "open" ("Door
FL open", "Raam open", "Laadklep open") the card text read awkwardly
("Door FL open is open") or the UI dropped the redundant suffix, leaving
several "is open" cards in a flow indistinguishable from each other.

Renamed all nine alarm_contact sub-capabilities to a sensor noun only —
e.g. "Front-left door", "Side window", "Charge port lid" — so the auto-
generated cards read naturally and identify the sensor in the flow
editor. Applied across all 13 languages. Doors switched from FL/FR/RL/RR
abbreviations to fully-spelled positions for legibility in the flow
card library.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Homey's auto-generated alarm_contact triggers all show "Contact alarm
turned on for [Duration]" in the card library without any sub-cap
discriminator, making the nine alarm_contact triggers visually identical
in the picker. Renaming the capability titles in 3.2.4 fixed the device
tile but did not help here — the auto-card title comes from the base
capability text.

Add three explicit Polestar-owned flow cards instead:
- contact_opened (trigger)
- contact_closed (trigger)
- contact_is_open (condition)

Each carries a dropdown that lists the nine alarm_contact sub-caps with
their localized sensor labels (all 13 languages). titleFormatted renders
e.g. "Front-left door opened" once the user picks the sensor.

Wiring: _setContact() in device.js wraps setCapabilityValue, detects
false→true / true→false transitions, and fires the matching trigger
with state.sensor = sub-cap id. The driver registers run-listeners that
gate by args.sensor === state.sensor.

The auto-generated Contact alarm cards still exist (we can't hide them
without breaking existing flows that already use them) but users are
now nudged toward the unambiguous explicit cards.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A 2021 Polestar 2 owner reported "no car found" during pairing. Logs
showed C3 login succeeded but getVehicles() returned an empty list —
the new C3 backend doesn't list every older P2. The c3_backend_disabled
escape hatch exists but the user has no way to discover it from the
pair dialog, only by spelunking through app settings.

testlogin (pair + repair) now silently retries with the legacy backend
when C3 returns 0 vehicles. If legacy finds vehicles, c3_backend_disabled
is persisted so all later discovery / refresh / write calls go through
the legacy client too. Only when both backends return empty does the
"no_vehicles" friendly error reach the UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A user reporting "no vehicles found" sent a log with the line:
  "Credential test ok, vehicle count:"
ending mid-sentence. Cause: app.log uses `if (data)` to decide whether
to append the data argument, so 0 / false / '' silently fall through to
the no-data branch. We logged the right call but the count never made
it to disk.

Switched to a nullish check (data !== null && data !== undefined). Also
guarded the `data.message` access so a non-object/non-error data value
no longer throws. Next time we ask a user for a log, "vehicle count: 0"
will actually say 0 instead of leaving us guessing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace GraphQL GetVDMSCars vehicle discovery with the C3 gRPC
GetMyCars service. Discovery now uses the same session / auth / retry
machinery as every other C3 call and drops the pc-api.polestar.com
app-backend dependency (with its versioned user-agent and apollo
headers). New GetMyCars response fields userIsLinked and userIsOwner
are surfaced on each vehicle for future diagnostics.

Includes the first fixture-based test under test/ — hex-encoded
GetMyCars proto response for a linked non-owner Polestar 3, verified
via node:test.

Tested by olemarkus on a non-owner Polestar 3.
Follow-up to olemarkus' PR #3. Two small additions on top of the merge:

1. package.json: "test" script runs the new fixture suite via
   `node --test test/*.test.js`. Future PRs can drop a hex proto
   response in test/ and get regression coverage for free.

2. driver.js discover_vehicles: log `linked:<y/n> owner:<y/n>` per
   vehicle at pair time. Non-owner accounts (lease, secondhand
   without ownership transfer) can now pair thanks to the C3
   GetMyCars endpoint, but we don't yet know which features — if
   any — require owner rights. Logging the state so we can correlate
   any feature-availability reports later. No user-facing message
   for now; only add one once we see actual missing capabilities.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
--- 'Car at home' feature ---

New boolean capability measure_polestarAtHome that flips true whenever
the vehicle's last-known location is within a configurable radius of
Homey's own geolocation. Default radius 150 m; adjustable 25–2000 m
via a new device setting home_radius_m.

Three explicit flow cards (custom capabilities don't get auto-generated
ones — corrected from my earlier assumption in the interim commit):
- Trigger "Car came home"   — fires on false→true transition
- Trigger "Car left home"   — fires on true→false transition
- Condition "Car is / is not at home" — invertable via the !{{is|is not}}
  tokens so the same card serves both semantics

Wiring: _evaluateAtHome() runs at the end of every successful location
refresh, reads Homey's lat/lng via the geolocation manager (new
`homey:manager:geolocation` permission), Haversines the distance, and
fires the appropriate trigger card on transition. Only fires if the
previous capability value was a boolean, so the first-ever set at boot
does not spuriously trigger "Car came home". If Homey has no usable
geolocation (permission missing / manual mode with no fix) the eval
skips silently rather than pinning the tile to false.

Titles + hints + settings label/hint translated across all 13 languages.
fixCapabilities() adds the new cap to existing paired devices.

--- Location refresh diagnostics ---

A user reported their location tile stopped updating after pair. Their
log covered 30 minutes / two slow-cycles but contained zero location-
related lines, because updateLocationState was silent on every non-
throw path. Added a "Retrieve vehicle location" start marker (mirrors
updateHealthState), distinct DEBUG lines for null-result vs. non-
finite-coords early returns (with raw payload for the latter),
UNIMPLEMENTED handling, and a success line. Next log we get will tell
us which of the four cases applies.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Audited every boolean capability in the app against the flow cards in
driver.flow.compose.json. Five caps had gaps that made obvious "when X
happens" / "if X is true" flows impossible to build. This commit fills
them all in — 6 new triggers + 5 new conditions, 13 languages each.

Filled gaps:
- alarm_polestarOtaAvailable: no trigger, no condition
  → 'New software update available' / 'Software update no longer
    pending' triggers + 'Software update is/is not available' condition.
- alarm_polestarTyrePressure: no trigger, no condition
  → 'Tyre pressure warning raised' / 'cleared' triggers + condition.
- onoff.climate (sub-cap, no auto-gen per SDK): no trigger, no condition
  → 'Climate started' / 'Climate stopped' triggers + condition.
- measure_vehicleChargeState: had triggers, no condition
  → 'Car is/is not charging' condition.
- measure_vehicleConnected: had triggers, no condition
  → 'Car is/is not connected to a charger' condition.

Wiring: new _setBoolAndTrigger(capId, val, trueKey, falseKey) helper in
device.js wraps setCapabilityValue and fires the matching device-trigger
card on real boolean transitions (skips when the previous value was not
yet a boolean, so device boot doesn't spuriously fire "climate started").
updateOtaState, updateHealthState (tyre pressure), and updateClimateState
now call this helper instead of raw setCapabilityValue. driver.js holds
refs to the six new trigger cards + registers five bool-reading condition
run-listeners with a small shared closure.

All condition titles use !{{is|is not}} tokens so users can invert them
in the flow editor without extra cards.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two housekeeping changes prompted by PR #4 review.

1. Removed the @andysmithfal/polestar.js entry from dependencies. The
   legacy client has always loaded from the local kaohlive/polestar.js
   fork under clone_modules/polestar.js — the npm package sat in the
   tree unused. Its presence made two identically-named, differently-
   patched copies look interchangeable, which triggered PR #4. Added
   LEGACY_CLIENT_FORK_NOTES.md at the repo root documenting exactly
   what our fork changes vs upstream 1.8.0 (CarTelematicsV2 migration,
   GraphQL error surfacing, getAccessToken/getVehicleVin helpers) so
   the next contributor doesn't need to diff to find out.

2. Security bumps. axios floor raised to ^1.7.4 (installed 1.18.1) to
   cover CVE-2024-39338 (SSRF). qs and ws direct deps refreshed via
   npm audit fix to patched versions (qs 6.15.3, ws 8.21.1). Four
   moderate CVEs remain in transitive deps of homey-api (parseuri,
   engine.io-client, socket.io-client) — all in the AthomCloudAPI
   OAuth path used only by the deprecated Car Stats Viewer driver
   and, within that, only the REST portion, not the socket.io
   real-time transport where the vulnerabilities live. Will clear
   when Athom bumps their transport stack.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ev_charging_state (cars) and evcharger_charging_state (chargers) are the
same five values in homey-lib, so a car and the charger it is plugged into
should report the same value for the same physical moment. Checked against
the Tesla car app, the Tesla Wall Connector, Easee and SolarEdge; three
mappings disagreed with all of them:

- SMART_CHARGING reported plugged_in_paused while power was flowing.
- DISCHARGING reported plugged_in, discarding plugged_in_discharging even
  though C3 reports the state explicitly.
- UNSPECIFIED and any unrecognised status reported plugged_out with the
  connector still in — the one value consumers act on as "nothing here".

Two coupled defects shared a root cause: "is it charging?" was derived
twice. The energy accrual gate compared the raw status against
CHARGING_STATUS_CHARGING, so smart-charging sessions accrued no
meter_power or session kWh at all, and the switch set
measure_vehicleChargeState per-branch, letting it contradict
ev_charging_state within a poll.

Resolve the plug state once per poll in a pure lib module and let the
accrual gate, measure_vehicleChargeState, measure_vehicleConnected and
ev_charging_state all read it. Connection status now wins outright, so
the device can no longer report plugged_in_charging alongside
measure_vehicleConnected=false.

Also drops dead code: CHARGING_STATUS_FAULT is a ChargerConnectionStatus
value, never a ChargingStatus, so its case and set entry were unreachable;
and the unconditional plugged_out pre-set was always overwritten by the
switch, firing ev_charging_state_changed twice per poll where the two
disagreed.

ERROR stays at plugged_in: chargers report plugged_out on a fault, but
both car apps keep it at plugged_in because the car knows its own cable
is still in.

Charging and smart charging remain distinct statuses that happen to share
a plug state — isSmartCharging() keeps that axis addressable.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Merges PRs #6, #8 and #9 from olemarkus, plus the parts of #1 (kvolden)
and #5 (olemarkus) worth keeping.

ev_charging_state (#9) — "is it charging?" was derived twice, and the
energy accrual gate only recognised CHARGING_STATUS_CHARGING, so smart
charging sessions accrued no meter_power or session kWh at all. Plug
state now resolves once per poll in lib/evChargingState.js and every
consumer reads it. Also maps V2L/V2G to plugged_in_discharging and
fails toward plugged_in on an unrecognised status.

Pairing (#8) — vehicles without a registrationNo paired as
"Polestar 3 (null)"; they now use the bare model name.

Submodule (#6) — adds the .gitmodules entry for clone_modules/polestar.js
pointing at our own fork (kaohlive/polestar.js @ 75dcf9a, verified to
match the committed gitlink), so fresh clones can initialise it.

measure_polestarBattery — declared deprecated rather than removed, which
is what #5 proposed. No longer added to newly paired vehicles, still fed
on existing ones: removeCapability would drop their Insights history and
break tiles and widgets already pointing at it. The capability definition
stays regardless — polestar-2-csv still uses it. Took #5's widget fix, so
the dashboard reads measure_battery and works on new devices too.

Norwegian (#1) — cherry-picked the native-speaker wording over our
current strings; the rest of that PR had been overtaken by the locale
expansion since May 2025.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dashboard widget keyed on the registration number, which the Polestar
API treats as optional — cars that report none (notably some Polestar 3
accounts) were unaddressable, and getVehicleStatus threw Vehicle Not Found
for them. The registration is also mutable in a way a VIN is not: import,
re-registration or new plates would orphan a configured widget.

The VIN was already the device identity (pairing sets id and data.vin);
the widget simply never used it.

The identity is decided in app.js, not in the widget API: the settings
picker is served by the autocomplete listener registered there, and Homey
stores the entry it returns verbatim. That listener returned
{name, registration}, so a widget's settings could never contain a VIN no
matter what the widget API offered. It now returns the VIN as well.

Resolution order is VIN first, registration second. The fallback is
permanent — a widget cannot rewrite its own settings, so widgets already
configured out there hold only a registration and would otherwise have to
be set up again by hand.

Both keys are validated as non-empty strings before matching. Without
that, an absent key matches a car whose registration is null, which would
bind the widget to whichever car happens to lack a plate.

Also drops an unguarded `devices[0].getData().registration` debug line
that would throw on an account with no paired vehicles, and logged a plate
on every keystroke in the picker. getVehicles in the widget API returns an
explicit {id, vin, registration, name} shape rather than raw Device
instances, and now says in a comment that it is not what feeds the picker.

Verified on a real Homey: an existing widget keeps working untouched, and
re-picking the car stores name|vin|registration.

Rides along with 3.3.0, which is pushed but not yet published.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merges #7 (olemarkus): the preconditioning capability, its trigger and its
condition, decoded from C3 battery field 29.

On top of that:

Preconditioning resolves last in the battery poll instead of first. It is a
secondary reading, and running it ahead of the core telemetry meant a throw
there was caught by the battery block's handler and cost that poll its SoC,
range, power, meter_power and charging state. The trigger-card lookup moved
inside the guard with the trigger for the same reason.

The trigger token was the hardcoded English label while everything around
it shipped in 13 locales. It now goes through homey.__() with the same
wording as the capability's enum titles, so a flow token and the device
tile cannot disagree.

Device exposes getName(), not a `name` property, so every log call passing
this.name handed the logger undefined and the `instance` parameter fell
back to its 'Polestar App' default — which is why Exterior, Climate and
Location lines were filed under the app rather than the car. Set at the top
of onInit, ahead of the early returns, and refreshed in onRenamed.

A test now cross-checks every getConditionCard / getActionCard /
getDeviceTriggerCard id against the built app.json. `homey app validate`
never reads the app's JavaScript — verified by pointing a getter at a
nonexistent id and still getting "App validated successfully against level
`publish`". Registrations run in sequence with no per-card isolation, so
one bad id silently takes down every card after it. Catches a typo, a
one-sided rename, a deleted card still registered, and the wrong getter
family; all four confirmed to fail it.

normalizeManualPreconditioning keeps its original behaviour, now with a
comment saying why: absence of field 29 is the only signal that a car lacks
the feature. A present-but-empty submessage means the car supports it and
has nothing to say right now — proto3 omits zero values — and must keep
falling through to 'unknown', or the capability never attaches.

Co-Authored-By: Claude Opus 5 (1M context) <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