Skip to content

fix(iobroker.teslemetry): fix startup abort and non-existent SDK method calls#50

Merged
Bre77 merged 1 commit into
mainfrom
fm/iob-corefix-r4
Jul 12, 2026
Merged

fix(iobroker.teslemetry): fix startup abort and non-existent SDK method calls#50
Bre77 merged 1 commit into
mainfrom
fm/iob-corefix-r4

Conversation

@Bre77

@Bre77 Bre77 commented Jul 12, 2026

Copy link
Copy Markdown
Member

Intent

  • iobroker.teslemetry was runtime-broken end to end - it compiled green, linted clean, and its 3 tests passed, yet the adapter could not complete onReady and, 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.
    • Startup abort: registerVehicle/registerSite called the SDK's unconditional constructors (teslemetry.vehicle(vin) / teslemetry.energySite(id)) after createProducts() had already discovered the same VIN/site via the get-or-create accessor - the constructor's duplicate guard threw "Vehicle already exists" inside onReady's try block, aborting startup at the first product.
      • Fix: register via teslemetry.api.getVehicle(vin) / teslemetry.api.getEnergySite(id) (the same get-or-create path createProducts() uses).
    • Every vehicle command and energy write called a method that doesn't exist: the handlers called a snake_case surface (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.
      • Fix: rewrote every command/write to the real method name and signature (see SDK method table in the audit); handler maps are now typed to TeslemetryVehicleApi/TeslemetryEnergyApi instead of any, so tsc catches a wrong method name again.
    • All reads were broken two ways: non-existent method names (vehicle.data(), site.live_status()) plus response-shape mismatches - REST vehicleData()/getLiveStatus() wrap in { response: {...} }, while the SSE data stream event is a flat PascalCase signal map ({BatteryLevel, InsideTemp, Locked, ...}), not the nested snake_case REST shape the old parser assumed.
      • Fix: corrected method names; StateManager now has two distinct parsers (updateVehicleData for the REST shape, updateVehicleDataFromSignals for the SSE shape) instead of one parser fed two incompatible shapes.
    • Low-severity: the SSE alert handler destructured {vin, name, endedAt} off the top-level event instead of {vin, alerts} (an array), so alerts were never logged - fixed in passing.
    • Scope boundary: this is the well-scoped fix the audit recommended (get-or-create registration + real SDK surface + response-shape parsing), not a feature-completeness pass. Energy operation-mode/backup-reserve read-back now also pulls getSiteInfo() (those fields live there, not in getLiveStatus()) 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/setSentryMode fixed to correct positional signatures; fetchVehicleData reads vehicle.state()'s response.state and calls vehicle.vehicleData() (not .data()).
  • lib/EnergyHandler.ts - get-or-create registration; setStormMode/setOperationMode/setBackupReserve/setOffGridVehicleChargingReserve replace the non-existent snake_case calls; fetchSiteData now calls getLiveStatus() + getSiteInfo() (not .live_status()).
  • lib/StateManager.ts - updateVehicleData unwraps the REST {response: {...}} shape; new updateVehicleDataFromSignals handles the SSE flat PascalCase map; updateEnergySiteData reads the real (unwrapped, merged) live-status/site-info fields; dropped battery.total_pack_energy (no such field exists anywhere in the SDK - was permanently dead); added live.grid_status (part of the documented core read set, previously missing entirely).
  • lib/StreamHandler.ts - routes SSE data events through the new signal parser; fixed the alert-event destructuring.
  • test/ - new fakeAdapter.ts helper plus VehicleHandler.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

  • Low - this package is unpublished (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) - clean
  • pnpm --filter iobroker.teslemetry test - 30/30 pass (8 pre-existing + 22 new)
  • pnpm --filter iobroker.teslemetry build - clean
  • pnpm lint (whole monorepo) - clean
  • pnpm -r --no-bail tsc - clean across all packages
  • Regression-checked the new tests against the pre-fix code (git stash back to the broken handlers): they fail with the exact "Vehicle already exists" / TypeError: ... is not a function errors the audit documented, confirming they actually catch this bug class.
Full narrative / original brief

A tier-3 correctness audit (e15-tier3-audit-c6, evidence anchored path:line, most severe finding hand-verified against the SDK source) found iobroker.teslemetry runtime-broken: adapter aborts at startup (registerVehicle/registerSite throw "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 .response wrapper vs. SSE's flat PascalCase signal map). This is the same bug class PR46 fixed for parseStateId (wired to wrong names, invisible to typecheck because of Map<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.

…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.
@Bre77
Bre77 merged commit 2aa35e6 into main Jul 12, 2026
1 check passed
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