mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 20:43:46 +08:00
182 lines
11 KiB
Markdown
182 lines
11 KiB
Markdown
# StarPilot runtime contracts
|
||
|
||
Feature-specific behavior belongs with its owner. The [UI guide](ui/README.md)
|
||
describes presentation and input boundaries; the contracts below cover shared
|
||
audio settings and retained-state migration.
|
||
|
||
## Alert levels and sound packs
|
||
|
||
`audio/alert_volume.py` defines seven audible-alert families. Saved integer
|
||
`101`, or an absent value, means Auto. Manual levels range from 0–100%, except
|
||
Soft and Immediate Warnings, which have a 25% minimum. The immediate warning,
|
||
including a selfdrive timeout warning, still ramps to full volume. `soundd`
|
||
validates saved levels and refreshes them outside its audio callback. Opening
|
||
Settings does not create default values or reset other alert families.
|
||
|
||
`audio/sound_pack.py` loads the selected installed pack and falls back to the
|
||
stock clip when a replacement is missing or invalid. It accepts bounded,
|
||
uncompressed mono 48 kHz, 16-bit WAV files; decoded samples are immutable.
|
||
`audio/downloads.py` installs catalogued packs with size/hash validation,
|
||
bounded extraction and parked-state checks. Sound selection is independent of
|
||
onroad colors and layouts. Code tests do not establish audible quality on the
|
||
device.
|
||
|
||
## Settings preservation
|
||
|
||
`state_migration.py` separates exact recovery from importing preferences:
|
||
|
||
- `snapshot_settings(namespace, storage)` archives every raw value before cleanup,
|
||
including unknown keys, empty files, binary caches and credentials. It uses the
|
||
native Params lock, private permissions, content hashes and durable writes.
|
||
- `restore_snapshot(snapshot, namespace)` verifies the entire archive before
|
||
restoring exact bytes into an unused namespace. It refuses to overwrite an
|
||
existing namespace. This is an offline recovery operation, not an upgrade.
|
||
- `validate_bundle(bundle)` checks the versioned neutral settings format.
|
||
`decode_bundle(encoded)` also rejects duplicate JSON members before validation.
|
||
`stage_settings(bundle, storage, namespace)` saves it privately outside Params without
|
||
activating any value. Pending settings have explicit types and raw-byte hashes;
|
||
omitted settings have reasons. Archive and source hashes identify provenance,
|
||
but a syntactically valid bundle is not evidence of feature compatibility.
|
||
- `prepare_manager_start(params, storage)` runs before boot logging, clearing
|
||
settings, defaults or registration. Initial startup only accepts an empty
|
||
namespace or reviewed Boolean preferences. Other state is archived and startup
|
||
stops for migration. Subsequent startup rejects unknown keys, malformed
|
||
reviewed preferences and incompatible cache envelopes. Its profile marker
|
||
identifies the namespace and schema epoch; each cache must pass its own
|
||
schema and producer checks, even in a previously initialized namespace.
|
||
|
||
Event cache writers use version 2 envelopes. Their fingerprint covers the Event
|
||
wrapper, common fields and the selected service's complete schema, so unrelated
|
||
union members can change without invalidating saved calibration or learned values.
|
||
Physical wrapper changes or changes to a stored service still require migration.
|
||
Version 1 envelopes remain readable only when their full schema matches the
|
||
current runtime; reads do not rewrite them. CarParams envelopes remain version 1.
|
||
|
||
Before activating an update, `prepare_manager_start(params, storage, dry_run=True)`
|
||
checks the retained namespace with the candidate's compiled schemas and registry.
|
||
It does not qualify a profile, create recovery files or alter saved values. A
|
||
successful check describes that snapshot of state; normal startup still performs
|
||
the admission checks before launching processes.
|
||
|
||
The initial Boolean allowlist is `AlwaysOnLateral`, `IsMetric`, `RecordAudio`,
|
||
`RecordFront`, `RecordFrontLock`, `ShowDebugInfo`, `GsmMetered` and
|
||
`OpenpilotEnabledToggle`. Saved values must be canonical `0` or `1` bytes.
|
||
`AlwaysOnLateral` defaults to true for every fresh namespace, independently of
|
||
vehicle identification; an existing true or false value is preserved. This
|
||
preference default does not bypass vehicle, native or steering safety checks.
|
||
|
||
The neutral bundle has `format: "starpilot-settings"`, integer `version: 1`,
|
||
`source`, `preferences`, `pending` and `omitted`. Source metadata includes the full
|
||
source revision, registry SHA-256 and raw snapshot SHA-256. Optional cache fallback
|
||
requires a separate `cache_snapshot_sha256`. A raw snapshot hash is the SHA-256
|
||
of sorted `{key, sha256, size}` entries encoded as JSON with sorted object keys,
|
||
ASCII escaping and separators `(',', ':')`.
|
||
|
||
Never interpret archived vehicle parameters or logs using a different schema just
|
||
because field numbers match. Binary caches, credentials, control-policy settings
|
||
and pending feature settings each require their own migration policy. Staging
|
||
alone never marks a profile ready. Stop services before export, restore or any
|
||
future activation; older runtimes may write a separate cache outside the native
|
||
Params lock. Keep private archives outside Params and source repositories.
|
||
|
||
## Schema-aware caches
|
||
|
||
`schema_cache.py` stores a versioned envelope in each managed cache value. Payload,
|
||
producer contract, key, service, transitive compiled-schema fingerprint and content
|
||
hash are written atomically through the existing native Params queue. Live
|
||
`CarParams`, messaging and Panda wire formats remain unchanged.
|
||
|
||
`put_cache` requires a typed message from the current compiled producer schema.
|
||
It preserves the caller's blocking/asynchronous write choice. `get_cache` only
|
||
returns verified payload bytes; incompatible data stays untouched and is a cache
|
||
miss. Startup archives and blocks incompatible existing state before processes
|
||
can replace it. Fingerprints are prepared before realtime processing loops.
|
||
|
||
Managed keys are `CarParamsCache`, `CarParamsPersistent`, `CarParamsPrevRoute`,
|
||
`CalibrationParams`, `LiveParametersV2`, `LiveTorqueParameters` and `LiveDelay`.
|
||
The retired `LocationFilterInitialState` has no conversion contract and blocks
|
||
startup if present. A numeric schema ID or successful decode is insufficient
|
||
provenance: enum meanings and fields can change while IDs remain constant.
|
||
|
||
An envelope protects against accidental schema reuse; it is not a signature or
|
||
an automatic semantic conversion. Producers must use current observations or
|
||
verified cache data. Parsing unqualified historical bytes with today's schema
|
||
and then wrapping them does not make those bytes compatible.
|
||
|
||
Historical-log cache seeding requires a source-schema conversion contract.
|
||
Process replay rejects unqualified warm-start state before writing Params.
|
||
Interactive replay only seeds raw live parameters in an explicit `replay-`
|
||
namespace and does not seed persistent caches. Historical process-replay results
|
||
remain unqualified until their source schemas and conversion fixtures are supplied.
|
||
|
||
Enveloped caches are incompatible with runtimes expecting bare serialized bytes.
|
||
A downgrade requires an offline recovery or explicit conversion; switching Git
|
||
branches alone is not a supported rollback. No deployment is qualified here.
|
||
|
||
Run native persistence checks after building the Params library:
|
||
|
||
```sh
|
||
python -m unittest openpilot.starpilot.tests.test_state_migration -v
|
||
```
|
||
|
||
## Host actuator boundary
|
||
|
||
`tests/test_control_host_boundary.py` exercises the actual upstream `Controls`
|
||
constructor, longitudinal controller and native `carControl` publication in
|
||
disposable Params and IPC namespaces. Synthetic messages pass through the real
|
||
SubMaster input API. The two configurations deliberately supply a Model Y
|
||
candidate with system or stock longitudinal ownership; they do not identify a
|
||
physical vehicle or EPS firmware.
|
||
|
||
The checks warm the acceleration controller, then supply longitudinal override,
|
||
lateral fault, disable, a continuing negative plan and explicit re-enable inputs.
|
||
Inactive host longitudinal control must publish zero acceleration even with a
|
||
nonzero plan or earlier positive output. The lateral-fault case checks the host
|
||
activation flag while longitudinal control continues. Cancellation and override
|
||
publication are checked independently. Native libraries and source hashes are
|
||
recorded by the explicit probe.
|
||
|
||
This is a host input/output contract. It does not prove button or AOL policy,
|
||
message freshness handling, emitted CAN, Panda permission, stock vehicle response
|
||
or rearm behavior. Those need separate integrated evidence. A controller test
|
||
which directly injects stale nonzero acceleration cannot assume this host
|
||
neutralization step already ran.
|
||
|
||
## Modern Tesla integration
|
||
|
||
`tests/test_tesla_contract.py` checks known EPS firmware classification, controller
|
||
command fields and counts, counter progression, override behavior and C safety
|
||
decisions. Each test process compiles and loads its own temporary DEBUG safety
|
||
library. Firmware identification and direct controller inputs are separate checks.
|
||
|
||
`tests/test_tesla_host_integration.py` joins actual parsed CAN state, the upstream
|
||
Controls object, native `carControl` publication, the unchanged received command,
|
||
the vehicle interface/controller and C safety hooks. Two known Model Y EPS profiles
|
||
each exercise 120 consecutive logical frames. Override and disable each start with
|
||
a positive host acceleration output and must neutralize it through the emitted CAN
|
||
fields. Packet counts, counter continuity across transitions and native publication
|
||
identity are asserted. Logical CAN time is separate from measured publication time.
|
||
|
||
Host enable, override and plan messages are supplied test inputs. Stock cruise CAN
|
||
and native safety permission remain present during host disable; raw cancellation
|
||
commands do not simulate the vehicle responding. These tests do not establish AOL,
|
||
longitudinal-only policy, automatic rearm, physical hardware identity, release
|
||
firmware behavior or qualification of a car. Direct controller calls still require
|
||
the host to neutralize inactive acceleration. Sustained stock-ACC cancellation,
|
||
full process coordination and physical response require separate evidence.
|
||
|
||
## CAN subscription contract
|
||
|
||
`tests/test_can_subscription_contract.py` exercises the actual upstream Python
|
||
parser and packer with synthetic messages. A subscription frequency of `NaN`
|
||
excludes that message from aggregate alive checks. Zero instead requires its
|
||
presence and learns its frequency from received packets. Ported subscriptions
|
||
must express the intended behavior explicitly for each message and vehicle.
|
||
|
||
Optional signals keep zero timestamps until first received and retain their last
|
||
timestamps after reception stops. Aggregate `can_valid` therefore does not establish
|
||
their freshness. Consumers must independently check freshness and applicability
|
||
before using them. Continuing optional traffic also must not hide loss of a
|
||
required message. These checks do not qualify message integrity algorithms,
|
||
native safety, physical CAN or any vehicle.
|