v5: single `client.car` controller (bridge v2)
SDK v5 collapses every per-family controller into `client.car`. The host's bridge jumps to v2 in lockstep. Per-call mapping table + what's gone.
v5: single client.car controller
5.0.0 is a hard cutover. The eight per-family controllers
(client.climate, .connectivity, .location, .media,
.navigation, .system, .vehicleDiagnostics,
.vehicleEnvironment) and the legacy client.carStatus shape are
gone. A single client.car controller wraps the host's new v2
car.* bridge (car-i99dash, branch feat/bridge-v2,
CarBridgeService).
The host now owns one name-keyed catalog per brand (BYD today —
see the catalog page). Mini-apps read by
name and write by actionId:
// v4
const climate = await client.climate.getSnapshot();
console.log(climate.cabinTempC, climate.fanSpeed);
// v5
const { values } = await client.car.read([
'ac_cabin_temp', 'ac_fan', 'ac_power',
]);
console.log(values.ac_cabin_temp, values.ac_fan);Bridge protocol version: 2.0.0 — echoed on every car.list
response.
The v5 surface
client.car.list({ category?, threeDOnly? }); // catalog
client.car.read(names); // ≤ 64 names
client.car.subscribe({ names, onEvent }); // push stream
client.car.command(actionId, args?, { idempotencyKey? });// write
client.car.identity(); // 3D / brand / model
client.car.asset(path); // bundle bytes
client.car.connectionSubscribe(onChange); // connection FSMReact hooks: useCarSignals(names, opts?) and useCarConnection(opts?).
Per-call mapping
Name set pulled from
byd_public_catalog.dart.
If your mini-app targets a non-BYD brand later, swap the names for
that brand's catalog — the controller surface is brand-agnostic.
| v4 call | v5 equivalent |
|---|---|
client.climate.getSnapshot() → cabinTempC | client.car.read(['ac_cabin_temp']) |
client.climate.getSnapshot() → fanSpeed | client.car.read(['ac_fan']) |
client.climate.getSnapshot() → acPower | client.car.read(['ac_power']) |
client.climate.getSnapshot() → targetTempC | client.car.read(['ac_target_temp']) |
client.vehicleDiagnostics.getSnapshot() → tire psi | client.car.read(['tpms_pressure_lf', 'tpms_pressure_rf', 'tpms_pressure_lr', 'tpms_pressure_rr']) |
client.vehicleDiagnostics.getSnapshot() → odometer | client.car.read(['stat_total_km']) |
client.carStatus.getSnapshot() → speedKmh | client.car.read(['speed_kmh']) |
client.carStatus.getSnapshot() → batteryPct | client.car.read(['battery_pct']) |
client.media.getSnapshot() → playState | host no longer ships a media bridge; consume Android MediaSession via _admin.exec if needed |
client.system.getSnapshot() → ota status | not yet in v2 catalog — file an issue if you depend on it |
client.connectivity.getSnapshot() → networkType | not yet in v2 catalog |
client.location.getSnapshot() → lat/lon | not yet in v2 catalog (PII tier — coming in a follow-up) |
client.navigation.getSnapshot() → destination | not yet in v2 catalog |
client.car.onConnectionChange(...) | client.car.connectionSubscribe(state => ...) — note 4 states now: connected | degraded | disconnected | unknown |
Writes (lights, doors, climate set-points) go through
car.command:
await client.car.command('climate.power.toggle');
await client.car.command('climate.temp.set', { tempC10: 220 });
await client.car.command('lights.lowbeam.toggle');The host returns the CarCommandRouter envelope verbatim
({ ok, code?, data? }) — the integrity, rate-limit, and
stationary-speed gates are unchanged from v4.
React migration
| v4 hook | v5 equivalent |
|---|---|
useCarStatus() | useCarSignals(['speed_kmh', 'battery_pct', /* … */]) |
useClimate() | useCarSignals(['ac_power', 'ac_cabin_temp', 'ac_fan']) |
useMedia() | (removed; route via _admin.exec if needed) |
useVehicleDiagnostics() | useCarSignals(['tpms_pressure_lf', 'stat_total_km']) |
useConnectivity() | (removed) |
useLocation() | (removed; PII tier — follow-up release) |
useNavigation() | (removed) |
useSystem() | (removed) |
useCarStatus()'s connection FSM | useCarConnection() — 4-state classification |
What's gone
Top-level exports dropped in v5:
- Controllers:
CarStatusController,ClimateController,ConnectivityController,LocationController,MediaController,NavigationController,SystemController,VehicleDiagnosticsController,VehicleEnvironmentController - Predicates:
isCarStatusBridge,isClimateBridge,isConnectivityBridge,isLocationBridge,isMediaBridge,isNavigationBridge,isSystemBridge,isVehicleDiagnosticsBridge,isVehicleEnvironmentBridge— replaced byisCarBridge(single predicate covers the whole v2 surface). - Errors:
CarStatusUnavailableError,CarStatusQuotaExceededError,ClimateUnavailableError,ConnectivityUnavailableError,LocationUnavailableError,MediaUnavailableError,NavigationUnavailableError,SystemUnavailableError,VehicleDiagnosticsUnavailableError,VehicleEnvironmentUnavailableError. Bridge-level failures now surface asBridgeTransportError/InvalidResponseError; per- call errors come back inside thecar.*envelope. - Zod schemas + types:
CarStatusSchema,CarStatusStalenessSchema,CarDoorsSchema,CarDoorStateSchema,CarBrandSchema,MediaSnapshotSchema,MediaSourceSchema,MediaPlayStateSchema,ClimateSnapshotSchema,ClimateModeSchema,VehicleDiagnosticsSnapshotSchema,GearPositionSchema,TirePressureSchema,VehicleEnvironmentSnapshotSchema,SystemSnapshotSchema,DistanceUnitSchema,TemperatureUnitSchema,OtaStatusSchema,ConnectivitySnapshotSchema,NetworkTypeSchema,LocationSnapshotSchema,NavigationSnapshotSchema,NavManeuverSchema. - React hooks:
useCarStatus,useMedia,useClimate,useVehicleDiagnostics,useVehicleEnvironment,useSystem,useConnectivity,useLocation,useNavigation.
Native-capability families (display, surface, cursor,
gesture, pkg, boot) are unchanged — those are privileged
ops orthogonal to car data, accessed via _admin.exec.
3D mini-apps
client.car.identity() is the one-call entry-point for any
mini-app that wants to load the active car's 3D model:
const id = await client.car.identity();
// id.brand, id.modelCode, id.modelDisplay, id.modelAssetPath
// id.clips: canonical animation-clip name set
// id.variants: { paint, wheels, glass } — asset name lists
const asset = await client.car.asset(id.modelAssetPath!);
// asset.bytes is a decoded Uint8Array; asset.contentType is the
// inferred MIME (model/gltf-binary for .glb).End-to-end walkthrough at 3D mini-apps.
Why?
One catalog beats nine schemas. Per-family controllers meant the
SDK shipped nine zod schemas, nine *UnavailableError classes,
nine is*Bridge predicates — and every time the host added a new
datum ("outside temperature, please") we needed a coordinated SDK
release to expose it. The v2 bridge inverts that: the host
declares its catalog at runtime via car.list, and a mini-app
reads any name without an SDK bump.
Why no compat shim?
Same posture as every other pre-launch i99dash break: the platform
is small enough that every host, every SDK consumer, and every
mini-app moves in lockstep. A v4 client.climate.getSnapshot()
call against a v2 bridge would 404 the bridge handler — there is
no graceful degradation path, and pretending otherwise would
silently break apps in subtler ways than the obvious type error.
Related
- Host catalog status — what the v2 catalog serves today vs. v5.1.
- Raw host-bridge protocol — the wire the
car.*surface rides on. - Build for L5 + L8 — shipping v5 to Di5.0 and Di5.1 from one bundle.
Help
If you hit a missing signal name or a mapping the table above doesn't cover, file an issue on the SDK repo: github.com/i99dash/i99dash-sdk.
Raw host-bridge protocol
The window globals, calling conventions, and envelopes the SDK wraps — for debugging and for examples that call the bridge directly.
v4: multi-brand device ID
SDK v4 drops `bydDeviceId` for a brand-prefixed `deviceId` plus a separate `brand` field. The wire form is `<brand>:<native_id>`, e.g. `byd:BYDMCKLE0PARD8801`.