i99dash docs
Migration

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 FSM

React 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 callv5 equivalent
client.climate.getSnapshot()cabinTempCclient.car.read(['ac_cabin_temp'])
client.climate.getSnapshot()fanSpeedclient.car.read(['ac_fan'])
client.climate.getSnapshot()acPowerclient.car.read(['ac_power'])
client.climate.getSnapshot()targetTempCclient.car.read(['ac_target_temp'])
client.vehicleDiagnostics.getSnapshot() → tire psiclient.car.read(['tpms_pressure_lf', 'tpms_pressure_rf', 'tpms_pressure_lr', 'tpms_pressure_rr'])
client.vehicleDiagnostics.getSnapshot() → odometerclient.car.read(['stat_total_km'])
client.carStatus.getSnapshot()speedKmhclient.car.read(['speed_kmh'])
client.carStatus.getSnapshot()batteryPctclient.car.read(['battery_pct'])
client.media.getSnapshot() → playStatehost no longer ships a media bridge; consume Android MediaSession via _admin.exec if needed
client.system.getSnapshot() → ota statusnot yet in v2 catalog — file an issue if you depend on it
client.connectivity.getSnapshot() → networkTypenot yet in v2 catalog
client.location.getSnapshot() → lat/lonnot yet in v2 catalog (PII tier — coming in a follow-up)
client.navigation.getSnapshot() → destinationnot 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 hookv5 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 FSMuseCarConnection() — 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 by isCarBridge (single predicate covers the whole v2 surface).
  • Errors: CarStatusUnavailableError, CarStatusQuotaExceededError, ClimateUnavailableError, ConnectivityUnavailableError, LocationUnavailableError, MediaUnavailableError, NavigationUnavailableError, SystemUnavailableError, VehicleDiagnosticsUnavailableError, VehicleEnvironmentUnavailableError. Bridge-level failures now surface as BridgeTransportError / InvalidResponseError; per- call errors come back inside the car.* 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.

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.

On this page