i99dash docs
BYD API

Live vs. known

Why the BYD framework reports ~21k catalog entries but your car only exposes ~9k live features — the dedup, the device-type coverage, and the write-only commands that account for the gap.

TL;DR. The framework catalog reports ~21k entries because nested-class constants are stored twice (bare + prefixed), doubling ~10–11k unique constants. The registry then probes each name across seven device-types, drops sentinels, and de-duplicates first-wins. On a Leopard 8 the live count settles around 9k. The gap is expected; it's where write-only commands, unprobed subsystems, and unimplemented features fall.

This is the single most common question — "BYD says 21k, my car shows 9k, what's broken?" Nothing's broken. The numbers measure different things.

What the two numbers mean

NumberWhat it countsSource
~21,000Keys in BydAutoFeatureIdsCatalog.byName.Reflection of BYDAutoFeatureIds + every nested class, with each nested constant stored under both its prefixed and bare name.
~9,000Live registry entries on this car.AutoCarRegistry.entries.size — names that returned a non-sentinel value under at least one of seven WARM_DT device-types.

The 21k is "BYD's universe of names" — the framework's vocabulary. The 9k is "names that are actually bound on this hardware right now."

The three reductions

catalog (bare + prefixed)         ~21,000
↳ dedup to unique constants       ~10,500   (-50%)
↳ × WARM_DT subsystem coverage     ~9,500   (-10% from unprobed dts)
↳ − write-only / command keys      ~9,000   (-5% sentinel under read)

1. Bare + prefixed storage doubles the catalog

BydAutoFeatureIdsCatalog stores nested-class constants twice:

  • Ac.AC_POWER_STATE0x60000001 (prefixed)
  • AC_POWER_STATE0x60000001 (bare, for back-compat)

That's intentional — see Catalog — and absorbed by the registry's first-wins dedup on read. So out of the 21k catalog keys, only ~10.5k correspond to distinct (framework int) values.

2. WARM_DT covers seven device-types, not all of them

BYD's framework partitions features by device-type. The registry today registers push devices for seven dts (1000, 1001, 1023, 1038, 1040, 1041, 1045). Constants scoped to other dts — ADAS (1002), mirrors (1003), energy (1018) — are invisible to the probe. They exist in the catalog but read as sentinel under the WARM_DT walk.

This drops another ~10 % off the ceiling.

3. Write-only command keys read as sentinel

A meaningful fraction of the catalog encodes commands, not state:

  • Door.DOOR_LOCK_COMMAND_AREA_LEFT_FRONT — issuing a write here locks the door. Reading it returns -10011 because there's no underlying CAN signal carrying state-of-command. The locked state lives elsewhere under Bodywork.*.
  • Ac.AC_POWER_MODE_SET — issuing a write here changes mode. Reading returns -10011. The current mode is exposed under Ac.AC_POWER_STATE instead.

The registry's sentinel filter correctly drops these — they're not "live readable values," they're write addresses. They remain available as write targets through fast_actions in the textproto registry; the SDK invokes them via api.invoke('door.lock').

This drops the last few percent.

Trim variation

TrimLive countNotes
Han L4~7,500Smaller subsystem footprint; no premium audio, fewer driver-assist sensors.
Tang L4~8,200Mid-size SUV; tire pressure cluster + sunroof add a few hundred.
Leopard 8 Flagship~9,500Off-road sensors, full lighting suite, dual-screen cluster.
Yangwang U8~10,200Only when ADAS dt (1002) is in WARM_DT — currently off by default.

Trim variation comes from which subsystems exist on the hardware, not from the SDK choosing to expose less. The same probe code runs on every trim; the framework simply returns more sentinels on trims with fewer subsystems.

Diagnostic recipe

final api = ref.watch(carRegistryClientProvider);

final live  = await api.liveFeatures();        // Map<String, int>
final known = await api.allCatalogNames();     // Iterable<String>

final notLive = known.toSet()..removeAll(live.keys);
// Names in catalog but not live on this car.

notLive will contain ~12k names on a Leopard 8 — that's the expected gap. Don't try to "fix" it.

When the gap is a bug

Two cases where the gap deserves investigation:

SymptomWhat it suggests
Live count is far below the trim's typical range (e.g. Leopard 8 at 4k)BydPushDevice failed to construct for several WARM_DTs. Check inAppPush.stats().devicesRegistered.
Live count drops between boots on the same trimA ROM update changed the framework. Cache signature mismatch should have triggered a re-probe; verify in logs (AutoCarRegistry: cache invalidated).

Anything within ~±500 of the trim norm is normal trim variation.

What you should NOT do

  • Don't read from the framework directly to bypass the registry. You'd hit the same sentinels. The framework doesn't have more data; it just exposes it through the same API the registry uses.
  • Don't disable the sentinel filter to "see more entries." Sentinels are not data. Letting them through pollutes the registry with phantom values that never update.
  • Don't expect future DiLink versions to close the gap. Each release moves the live count by ~±200, not by thousands. The bare+prefixed dedup and the write-only commands are structural — they aren't going away.

On this page