fix(iobroker.teslemetry): fix startup abort and non-existent SDK method calls#50
Merged
Conversation
…he real camelCase SDK surface
The adapter was runtime-broken end to end: registerVehicle/registerSite
called the SDK's unconditional constructors (teslemetry.vehicle()/
energySite()) after createProducts() had already discovered the same
VIN/site via the get-or-create accessor, throwing "already exists" and
aborting onReady before any product registered. Past that, every vehicle
command and energy write called a non-existent snake_case method name
(the SDK surface is camelCase) because the handlers stored SDK instances
in Map<string, any>, which hid the wrong names from tsc. Vehicle/energy
reads were also broken: wrong method names, and response-shape mismatches
between the REST vehicleData()/getLiveStatus() `{ response: {...} }`
wrapper and the SSE stream's flat PascalCase signal map.
Fixes: startup registration, all vehicle commands (wake/lock/unlock/
climate/charging/lights/horn/frunk/trunk), all energy writes (storm
mode/operation mode/backup reserve/off-grid reserve), vehicle state+data
reads, energy live-status+site-info reads, SSE data-event parsing, and
the SSE alert-event destructuring. Handler maps are now typed to the
real SDK classes instead of `any` so tsc catches this class of bug again.
Adds a startup-registration regression test plus handler-dispatch and
state-parsing tests for both vehicle and energy paths, and an SSE
data/alert dispatch test - the prior suite only covered state-ID string
parsing and never exercised a real handler-to-SDK call.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
iobroker.teslemetrywas runtime-broken end to end - it compiled green, linted clean, and its 3 tests passed, yet the adapter could not completeonReadyand, even patched past that, no command or data read worked. Evidence base: an independent tier-3 correctness audit (e15-tier3-audit-c6) that traced every core capability from dispatch down to the actual SDK call and verified the SDK's method surface by hand.registerVehicle/registerSitecalled the SDK's unconditional constructors (teslemetry.vehicle(vin)/teslemetry.energySite(id)) aftercreateProducts()had already discovered the same VIN/site via the get-or-create accessor - the constructor's duplicate guard threw"Vehicle already exists"insideonReady's try block, aborting startup at the first product.teslemetry.api.getVehicle(vin)/teslemetry.api.getEnergySite(id)(the same get-or-create pathcreateProducts()uses).vehicle.wake(),site.storm_mode({enabled}), etc.) against an SDK that is camelCase (wakeUp(),setStormMode(enabled), etc.), with several also passing an object body where the SDK wants positional args.Map<string, any>in the handlers erased the type contract that would have caught this at compile time.TeslemetryVehicleApi/TeslemetryEnergyApiinstead ofany, sotsccatches a wrong method name again.vehicle.data(),site.live_status()) plus response-shape mismatches - RESTvehicleData()/getLiveStatus()wrap in{ response: {...} }, while the SSEdatastream event is a flat PascalCase signal map ({BatteryLevel, InsideTemp, Locked, ...}), not the nested snake_case REST shape the old parser assumed.StateManagernow has two distinct parsers (updateVehicleDatafor the REST shape,updateVehicleDataFromSignalsfor the SSE shape) instead of one parser fed two incompatible shapes.{vin, name, endedAt}off the top-level event instead of{vin, alerts}(an array), so alerts were never logged - fixed in passing.getSiteInfo()(those fields live there, not ingetLiveStatus()) since the existing state tree already expected them - a small, directly-related addition, not new scope.What Changed
lib/VehicleHandler.ts- get-or-create registration; all 9 commands rewired to real SDK methods;setTemps/setChargeLimit/setSentryModefixed to correct positional signatures;fetchVehicleDatareadsvehicle.state()'sresponse.stateand callsvehicle.vehicleData()(not.data()).lib/EnergyHandler.ts- get-or-create registration;setStormMode/setOperationMode/setBackupReserve/setOffGridVehicleChargingReservereplace the non-existent snake_case calls;fetchSiteDatanow callsgetLiveStatus()+getSiteInfo()(not.live_status()).lib/StateManager.ts-updateVehicleDataunwraps the REST{response: {...}}shape; newupdateVehicleDataFromSignalshandles the SSE flat PascalCase map;updateEnergySiteDatareads the real (unwrapped, merged) live-status/site-info fields; droppedbattery.total_pack_energy(no such field exists anywhere in the SDK - was permanently dead); addedlive.grid_status(part of the documented core read set, previously missing entirely).lib/StreamHandler.ts- routes SSEdataevents through the new signal parser; fixed the alert-event destructuring.test/- newfakeAdapter.tshelper plusVehicleHandler.test.ts,EnergyHandler.test.ts,StateManager.test.ts,StreamHandler.test.ts(22 new tests). The prior suite (parseStateId.test.ts) only covered state-ID string parsing and never exercised a real handler-to-SDK call - exactly the untested surface where every defect above lived.Risk Assessment
private: true), so there's no downstream consumer beyond a locally-run ioBroker instance. The change makes previously non-functional commands/reads actually work; it doesn't change any public interface other packages depend on.Testing
pnpm --filter iobroker.teslemetry check(tsc --noEmit) - cleanpnpm --filter iobroker.teslemetry test- 30/30 pass (8 pre-existing + 22 new)pnpm --filter iobroker.teslemetry build- cleanpnpm lint(whole monorepo) - cleanpnpm -r --no-bail tsc- clean across all packagesgit stashback to the broken handlers): they fail with the exact"Vehicle already exists"/TypeError: ... is not a functionerrors the audit documented, confirming they actually catch this bug class.Full narrative / original brief
A tier-3 correctness audit (
e15-tier3-audit-c6, evidence anchoredpath:line, most severe finding hand-verified against the SDK source) foundiobroker.teslemetryruntime-broken: adapter aborts at startup (registerVehicle/registerSitethrow "already exists"), and even patched past that, the entire handler layer calls a snake_case SDK surface that doesn't exist (the real SDK is camelCase), plus response-shape mismatches on every read path (REST.responsewrapper vs. SSE's flat PascalCase signal map). This is the same bug class PR46 fixed forparseStateId(wired to wrong names, invisible to typecheck because ofMap<string, any>, untested) but pervasive across the whole handler layer. The task: make the core set (auth, discovery, key vehicle commands, vehicle state read, energy read, energy write) actually work, following the audit's ranked defect list (D1-D8), and extend the PR46-era test harness pattern (tsx --test, minimal fake objects) with a startup smoke test and handler-dispatch tests so this class of totally-broken-but-green-CI cannot silently recur.