feat(firmware): validate configuration and migrate sensor-driven flight APIs

Validate configuration through its owning schemas and keep board
declarations separate from shared F722 resource ownership. Bound USB
transitions, isolate transport failures and retain restart-only activation.

Migrate navigation and flight consumers with the sensor and schema
contracts. Require explicit installation data and synchronous output
authority; board motor outputs remain inhibited.

Validation: assembled-prefix host tests, strict Clippy, platform/USB tests
and both ARM release builds. Final-source coverage and scoped Miri/Kani
evidence are retained separately from hardware qualification.
This commit is contained in:
Onur Özkan committed 2026-11-07 18:12:11 +03:00
1 parent fb1b8af2d1
commit f9a61a086f
211 files changed
+45657 -12740

No files matched your search

+153 -55
View File
@@ -12,22 +12,72 @@ F722 platform / RTIC application ──► data channel ──► MAVLink teleme
├── physical sensor drivers
└── startup motor-pin inhibition
portable libraries
math ─► navigation ─► control ─► safety
Portable flight libraries (not connected to physical actuators)
FlightEndpoint ──► Navigation / control / phases / safety
│
└── caller-owned synchronous output sink
```
Each F722 board contract pairs a board-owned `UserConfig` schema and explicit
`FACTORY_USER_CONFIG` with a board-owned `ImmutableConfig`. The immutable root
is one literal typestate manifest and the sole source of output inhibition,
physical routes, driver setup, sensor mount, bootstrap line rates, status LED,
image role, and service frequency. The user root contains only settings
consumed by that compiled driver graph.
Each F722 board combines `F722UserConfig` with an immutable hardware manifest.
The manifest selects the IMU route, driver settings, mount, serial inventory,
status LED, optional I²C sensor, motor pins and service frequency. User settings
select UART roles, baud and MAVLink settings. Motor pins are inhibited at startup.
The data channel owns rate selection, mailbox consumption, message encoding,
and bounded TX classification. Board code supplies physical routes, target
schema, and factory values; it does not receive generic output authority.
`F722ImmutableConfig<R>` retains the IMU route type through initialization and
interrupt acknowledgement. The sealed `Mpu6000Spi1` and `Icm42688Spi3` routes
select IMU wiring and backend independently of serial inventory and LED wiring.
Configuration and runtime observability are separate planes:
Each board declares one `PortCatalog<N>::DESCRIPTORS` array. Both
`Ports<Catalog, N>` and `SerialInventory::new(&Catalog::DESCRIPTORS)` use it for
stable UART/field identities, supported roles, baud allowlists and resource
claims. Validation produces a boot-only `PortPlan<N>`. The same const `N` sizes
I/O and protocol storage: five UARTs on each current board. Absent UARTs
have no slots. A declared UART set to Disabled retains its slot but initializes
no peripheral or pins. USB is fixed normal MAVLink outside that inventory,
separate from ROM DFU.
Each MAVLink `Link` has its own scheduler, encoder and receive framing, with a
separate transport queue. A shared collector snapshots sensor mailboxes for all
links. See [MAVLink](mavlink.md) for scheduling and transport behavior.
`kaptan-flight-runtime::FlightEndpoint` runs Quad-X control and
`kaptan-navigation::Navigation` without allocation. Callers supply
observations, independently captured receipt/processing time, clock ticks and a
synchronous output sink. Transport and wire encoding stay outside the core.
The board images do not yet construct a flight endpoint.
See [Safety boundary](safety-boundary.md#shared-runtime-commit-contract) for
epochs, request sequencing and the output commit contract.
`FlightEndpoint::supervise` checks actual time, cached health and phase/stream
deadlines without advancing navigation, integrating control or consuming a
control release. It can inhibit and publish outcomes, but cannot renew enabled
output. Callers must service the endpoint with actual time even when input
stops. The core owns control release and deadline decisions.
Within `FlightEndpoint`, fusion, current prediction and initialization completion
run only when the 100-Hz control service is due. Early `tick` calls supervise
cached evidence without advancing navigation. Ingress admission and its
diagnostics remain immediate, but receiving or queuing fresh observations alone
does not renew capabilities. The actual control gap is checked before a due
navigation update can refresh evidence.
`Navigation` initializes from a stationary window of IMU, fixed-reference local
position/velocity, magnetic, pressure and range observations. It derives attitude
and a fixed launch origin from sensors, with explicit calibration/reference
identities. Its shared 16-state error covariance covers position, velocity,
attitude error, gyro bias, accelerometer bias and barometric datum. Bounded
delayed fusion and current prediction preserve observation timing and capability
evidence.
The navigation `sensor-snapshots` feature enables driver snapshot conversion.
The same flight estimator is available on host and embedded targets.
Navigation owns the 25-m/s² free-flight acceleration bound checked by
complete-profile validation. See
[Configuration](configuration.md#flight-configuration) for required installation
data and the supported vehicle model.
Configuration and diagnostics use separate paths:
```text
FILE_CONTROL / FILE_DATA
@@ -42,50 +92,97 @@ kaptan-file-mavlink ──► virtual FileProvider
kaptan-log / kaptan-err-reportable
│
▼
kaptan-diagnostics-mavlink ──► platform-owned encoder / DMA TX
kaptan-diagnostics-mavlink ──► per-link encoder / bounded TX
```
The file dialect is intentionally Kaptan-native rather than standard MAVLink
FTP, `PARAM_*`, or a QGC adapter. Its two messages know only bounded paths,
bytes, positions, checksums, transfer ownership, and retries. The configuration
provider maps `/config/user.kcfg` to a canonical self-describing artifact.
KCFG carries generated metadata and a complete committed value set; the target
streams it without retaining a whole-file buffer. Saving imports and validates
one complete replacement candidate. The core distinguishes only immutable
boot-active values from the latest committed values. A save cannot mutate
controller or estimator state in flight.
The Kaptan-native file dialect owns bounded transfers and retries across links.
Its provider maps saved, active and factory-reset endpoints to streaming KCFG v3
and the generic configuration lifecycle.
`ConfigManager` owns boot-active values, known committed values or unknown durable
state, and the factory candidate. F722 supplies its UART-disabled setup fallback
through `recover_with`. Readable invalid settings remain inspectable for USB
repair. Saving or factory reset never changes active consumers until reboot.
Target-owned stores use private persistence formats. F722 alternates compact
`KCSP` snapshots between two erase sectors, retaining only the old and
replacement records across the commit boundary. That format is not exposed as the public KCFG file.
Motor allocation/mixer topology is immutable compiled vehicle capability data,
never a scalar field or persistence member. F722 recovers operational settings
before constructing consumers. Its local USB CDC-ACM and directly wired UART
accept only exact-target, CRC-valid Kaptan file requests while physical outputs
remain inhibited. CRC is integrity-only; a shared or remote GCS still needs
production authentication, source binding, and persistent anti-replay state.
Disarmed activation and flight-consumer reconstruction remain separate reviewed
work.
`KCSP` snapshots between two erase sectors, retaining only the old and replacement
records across the commit boundary. That format is not exposed as KCFG. Motor
allocation/mixer topology remains compiled vehicle capability data.
The MAVLink dialect root directly includes one-message XML leaves under
`publish/` and `transfer/`. The `exchange/` contract remains empty until a
current consumer justifies a message. There are no category index XML files or
separate enum catalogs. The build watches the definitions tree and generates
one `generated::kaptan` module; leaves are never separately generated Rust
modules. `kaptan-log` itself has
no MAVLink, platform, time, allocation, or filesystem dependency; its sole
configuration coupling is compile-time enum metadata for `LevelFilter`.
`kaptan-err-reportable` independently owns explicitly assigned reportable-error
identity and a bounded nonblocking ingress; it has no logging, MAVLink,
platform, filesystem, or clock dependency. Event creation is independent from
sink filtering, allowing a future blackbox and MAVLink to select different
policies. The platform drains reportable errors before its bounded critical and
best-effort log queues and removes a pending record only after TX admission.
`LOG` carries one conventional level plus one bounded text field; `ERROR`
carries an explicitly assigned 16-bit ID and the bounded `file:line` of the
reporting point. Its message kind already identifies a reportable firmware
error. Kaptan-native heartbeat and sensor publications remain separate from
diagnostics.
## I/O ownership
Sensor handoff uses owned `LatestMailbox<T>` storage split into exactly one
non-cloneable producer and one non-cloneable consumer. Publishing replaces an
unread value, and taking removes it at most once. Each operation copies or takes
an `Option<T>` inside the platform critical section. No callback, allocation,
retry or wait occurs inside that section. Storage stays borrowed while endpoints
are in use, and moving, dropping or forgetting an endpoint holds no lock. The
backend must exclude every participating context, using Cortex-M single-core exclusion on
F722 and the `critical-section/std` backend for host threads.
UART TX and RX are interrupt-driven, without UART DMA. Each I/O owner retains
the peripheral, TX queue and a 512-byte RX buffer with a typed `Timestamp` per
byte captured at IRQ receipt. The worker takes at most 16 bytes per UART per
turn under a short lock. Protocol parsing, scheduling and persistence run
outside UART locks. Overflow or a hardware RX fault discards the damaged backlog
and resets the decoder before later bytes. These bounds do not establish a
measured latency guarantee.
`kaptan-tx::TransmitQueue` owns bounded admission and one logically active frame.
Pending admission cannot replace that active frame. The queue itself is movable
and does not promise DMA address stability. UART byte writes and USB endpoint
writes borrow or copy its bytes synchronously. MAVLink owns encoding, while
platform I/O owners handle transmission.
USB uses the repository-patched Synopsys driver. Runtime register waits allow
higher-priority interrupts and stop after 5 ms or 1,080,000 polls per wait. These
are fault limits, not measured execution times. A fault holds USB in peripheral
reset until restart and discards its queued data. Brief critical sections protect
USB ownership and endpoint buffers. Boot initialization still runs under RTIC's
startup interrupt mask.
SPI1 and SPI3 DMA each use a once-claimed static pair of 32-byte TX/RX buffers.
`start_transfer` copies in, so hardware retains no caller slice. Completion
copies out only after checking stream disable, SPI quiescence, transfer counts
and faults. Failure to establish shutdown or drain RX quarantines the storage
and prevents reuse. Moving, dropping or forgetting the owner never releases
that claim. The F722 implementation relies on DMA-accessible SRAM, disabled
D-cache and device barriers. Physical SPI shutdown, timing and fault behavior
still require hardware validation.
## Monotonic time
TIM2 is the canonical F722 clock. The configured 108-MHz timer input and PSC=107
produce a free-running 32-bit counter at 1 MHz with ARR=`u32::MAX`. All readers
and the CC1 service IRQ extend one shared `u64` microsecond epoch inside the same
critical section. A consumer may remain silent across wraps if other readers
or IRQ service keep extending that epoch. Combined observations must be strictly
less than 2^32 microseconds apart, about 71.58 minutes, including interrupt
blackouts and debugger halts. NMI and fault handlers must not access this clock.
CC1 schedules the current 100-Hz service at epoch-aligned 10-ms deadlines.
Delayed service skips missed periods in constant work and emits at most one
notification per IRQ. Rearming uses two bounded attempts before pending a
recovery invocation. Callbacks and notifications run after clock exclusion ends.
TIM2 is not reset for each service tick, and no update IRQ or timer DMA is used.
Its IRQ is enabled only at the successful end of initialization.
Ordinary WFI retains the timer clock. Deep sleep, STOP/Standby, clock-tree changes,
counter reset/regression and debugger halts spanning a full wrap are unsupported
and require platform reset. Uptime must leave one service period before `u64`
microsecond overflow. DWT is used only for the CPU-running SRAM flash-poll timeout
at 216 MHz, not sensor timestamps or service deadlines. These contracts do not
establish measured oscillator accuracy, IRQ latency, jitter or worst-case timing.
## Diagnostics
`kaptan-log` has no MAVLink, platform, time, allocation or filesystem dependency.
Its configuration coupling is compile-time enum metadata for `LevelFilter`.
`kaptan-err-reportable` independently owns explicit error identities and a bounded
nonblocking ingress, without logging, transport or clock dependencies.
Event creation is independent from sink filtering. The worker drains reportable
errors before logs and offers each diagnostic to all MAVLink links. Heartbeat
and sensor publications use separate scheduling.
See [Configuration](configuration.md) for KCFG and the file-transfer contract,
[Logs](logs.md) for the receiver contract, and [Errors](errors.md)
@@ -96,7 +193,8 @@ for derived-error and failure-reporting scope.
- A peripheral owner is local to the platform runtime.
- Portable crates use typed units and validated values rather than raw wire or
timer representations.
- A safety permit is epoch-bound and must be revalidated at the only nonzero
output boundary.
- A failed observation or numerical check does not partially commit estimator,
controller, allocator, or safety state.
- `SafetySupervisor::evaluate_and_commit` checks current evidence at the
synchronous enabled-output boundary. No transferable permit escapes it.
- Rejected numerical updates do not partially commit computational candidates.
Fault latches, inhibition and ingress replay watermarks follow their explicit
failure contracts rather than rolling back authority or replay history.
+14 -23
View File
@@ -1,19 +1,13 @@
# GEPRC GEP-F722-HD V2
The GEPRC GEP-F722-HD V2 board declaration uses the ICM-42688-P on SPI3 and
MAVLink telemetry on USB CDC-ACM and USART2. It declares no vertical sensor stream. The
manufacturer's [GEP-F722-HD V2 manual](https://geprc.com/wp-content/uploads/2025/05/GEP-F722-HD-v2%E9%A3%9E%E6%8E%A7_%E4%BA%A7%E5%93%81%E8%AF%B4%E6%98%8E%E4%B9%A6-.pdf)
labels the external UART2 pads `T2` and `R2`.
The GEPRC GEP-F722-HD V2 uses the ICM-42688-P on SPI3.
The manufacturer's [GEP-F722-HD V2 manual](https://geprc.com/wp-content/uploads/2025/05/GEP-F722-HD-v2%E9%A3%9E%E6%8E%A7_%E4%BA%A7%E5%93%81%E8%AF%B4%E6%98%8E%E4%B9%A6-.pdf)
labels UART1 as `T1`/`R1` and UART2 as `T2`/`R2`.
Its board-local `UserConfig` factory selects endpoint `1/1`, IMU and
heartbeat publication rates, and an enabled MAVLink diagnostic sink with an
`Info` level filter. The web configurator may replace those
target-advertised values in the journal. Range
and optical-flow fields are absent because this image has no vertical-sensor
route. Its `ImmutableConfig` keeps the ICM-42688-P route, yaw-180° mount,
USART2 pins and baud, active-high PA13 status LED, and PB0/PB1/PB4/PB5 output
inhibition outside the writable schema. These selections appear together in
the board's immutable typestate literal.
The board declares its yaw-180° IMU mount, UART catalog, active-high PA13 LED
and no I²C range sensor.
Before fallible initialization, the platform holds the four documented motor
pins PB0/PB1/PB4/PB5 low.
## Build and package
@@ -53,9 +47,10 @@ make firmware/tools/configurator
Open `http://127.0.0.1:4861` and connect `/dev/serial/by-id/<kaptan-device>` or
the discovered `/dev/ttyACM*` path from the Connection section.
## UART fallback
## UART connection
Connect a 3.3 V USB-UART adapter with crossed data lines and common ground:
First assign UART2 to `KaptanMavlink` over USB, save and restart. Then connect
a 3.3 V USB-UART adapter with crossed data lines and common ground:
| Adapter | Flight controller |
| --- | --- |
@@ -65,11 +60,7 @@ Connect a 3.3 V USB-UART adapter with crossed data lines and common ground:
Enter `/dev/serial/by-id/<adapter>` in the same configurator's Connection section.
Open `http://127.0.0.1:4861`, Save the discovered fields, and manually
power-cycle the controller after Save completes. The flash journal is outside
the application image, so the exact range-limited DFU command above preserves
it. A mass erase or full-chip erase removes committed settings and restores
board factory defaults at the next boot. The image remains telemetry-only and
applies startup motor-pin inhibition. The unsigned local configuration path
assumes one directly connected client; disconnect every other transmitter from
`R2` while configuring over UART.
Use UART2's boot-active baud. If its role was changed or recovery disabled it,
connect over normal USB. Follow [save/reset/reload](../configuration.md#web-configurator)
before expecting new settings to apply. Disconnect other transmitters from `R2`
while configuring. Physical motor control is not yet implemented.
+13 -3
View File
@@ -3,10 +3,20 @@
Board pages describe supported hardware topology, build products, and safe
verification. They do not declare a board flight-ready.
| Board | MCU family | Primary IMU | MAVLink links |
| Board | IMU route | Serial inventory | Factory UART roles |
| --- | --- | --- | --- |
| [Kakute F7 Mini V3](kakute-f7-mini-v3.md) | STM32F722 | MPU-6000 | USB CDC-ACM, USART1 |
| [GEPRC GEP-F722-HD V2](geprc-f722-v2.md) | STM32F722 | ICM-42688-P | USB CDC-ACM, USART2 |
| [Kakute F7 Mini V3](kakute-f7-mini-v3.md) | `Mpu6000Spi1` | UART1, UART2, UART3, UART4, UART6 | All disabled |
| [GEPRC GEP-F722-HD V2](geprc-f722-v2.md) | `Icm42688Spi3` | UART1, UART2, UART3, UART4, UART6 | All disabled |
USB CDC-ACM always uses Kaptan MAVLink and has no configurable role. ROM DFU is a separate flashing mode. UART roles and baud are
restart-only user settings. Both factories use 115200 baud, endpoint `1/1`,
20-Hz IMU, 2-Hz heartbeat, 20-Hz range/flow settings and the `Info` log filter.
Range/flow publications require an enabled sensor source.
See [Configuration](../configuration.md) for role choices, supported baud values,
save/reset/reload and invalid-storage USB repair. The range-limited DFU workflows
on these pages preserve the journal outside the application image. Mass erase
removes saved settings and selects factory values at the next boot.
All board images share the motor-pin inhibit boundary described in
[Safety boundary](../safety-boundary.md).
+13 -22
View File
@@ -1,20 +1,14 @@
# Kakute F7 Mini V3
The Holybro Kakute F7 Mini V3 board declaration uses the MPU-6000 on SPI1,
MAVLink telemetry on USB CDC-ACM and USART1, and the MTF01 range/optical-flow
route on UART4.
The Holybro Kakute F7 Mini V3 uses the MPU-6000 on SPI1.
The checked-in manufacturer datasheet is
`firmware/boards/kakute-f7-mini-v3/datasheet.pdf`.
Its board-local `UserConfig` factory selects endpoint `1/1`, IMU, range,
optical-flow and heartbeat publication rates, and an enabled MAVLink diagnostic
sink with an `Info` level filter. The web configurator may replace those
target-advertised values in the journal. Its `ImmutableConfig` keeps the
MPU-6000 route, body-aligned
mount and concrete driver setup, USART1 pins and baud, MTF01 route and baud,
active-low PD2 status LED, and PB0/PB1/PB4/PB5 output inhibition outside the
writable schema. These selections appear together in the board's immutable
typestate literal.
The board maps MPU axes `(X, Y, Z)` to `(-Y, X, Z)`, matching the manufacturer's
`KAKUTEF7MINIV3` target, and supplies the driver settings. It declares the UART
catalog, active-low PD2 LED and no I²C range sensor.
Before fallible initialization, the platform holds all six documented
motor pins PB0/PB1/PB4/PB5/PB6/PB7 low.
## Build and package
@@ -59,9 +53,10 @@ make firmware/tools/configurator
Open `http://127.0.0.1:4861` and connect `/dev/serial/by-id/<kaptan-device>` or
the discovered `/dev/ttyACM*` path from the Connection section.
## UART fallback
## UART connection
Use a 3.3 V USB-to-UART adapter with crossed data lines and common ground:
First assign UART1 to `KaptanMavlink` over USB, save and restart. Then connect
a 3.3 V USB-UART adapter with crossed data lines and common ground:
| Adapter | Flight controller |
| --- | --- |
@@ -71,11 +66,7 @@ Use a 3.3 V USB-to-UART adapter with crossed data lines and common ground:
Enter `/dev/serial/by-id/<adapter>` in the same configurator's Connection section.
Open `http://127.0.0.1:4861`, edit the discovered fields, press Save, and
manually power-cycle the controller after Save completes. The flash journal is
outside the application image, so the exact range-limited DFU command above
preserves it. A mass erase or full-chip erase removes committed settings and
restores board factory defaults at the next boot. The image remains
telemetry-only and does not enable motor control. The unsigned local
configuration path assumes one directly connected client; disconnect every
other transmitter from `R1` while configuring over UART.
Use the UART's boot-active baud. If its role was changed or recovery disabled
UARTs, connect over normal USB. Follow [save/reset/reload](../configuration.md#web-configurator)
before expecting new settings to apply. Disconnect other transmitters from the
selected RX pad while configuring. Physical motor control is not yet implemented.
+221 -106
View File
@@ -1,16 +1,56 @@
# Configuration
Kaptan has one generic configuration model in `kaptan-config`. Target schemas
declare typed fields and validation once; derive code generates the stable
field registry and schema fingerprint. Boards supply the sole erased-flash
factory values. The browser contains no board, driver, sensor, schema, or
parameter catalog.
`kaptan-config` owns typed fields, validation and the committed-versus-active
lifecycle. The F722 platform supplies the shared user schema and factory values.
Boards declare their UART catalog and immutable hardware context. The
configurator renders target metadata without a board or parameter catalog.
## Current deployment matrix
## Flight configuration
| Deployment | Configuration consumed today | Remote writes |
`kaptan-flight-config::CandidateProfile` owns the flight policy, gains, thrust
model, navigation noise densities and timing constraints. It is consumed by the
portable runtime and its tests. The board firmware uses its own board schema
and **KCFG v3** service.
Field IDs **6, 7 and 25** remain reserved.
The other field IDs retain their meanings and numbering.
Construct the candidate's raw sections and supply a `NavigationInstallation`
through `VehicleCapabilities::quad_x`. It declares sources, calibration and
reference identities, range mounting and quantization, contact geometry,
magnetic field and local pressure/temperature. Complete validation produces
the immutable `VehicleProfile`. The current runtime supports Quad-X allocation
and four equal contact spheres on a body-frame rectangle.
The current controller descriptors include fields 41..44:
| ID | Key | Meaning |
| --- | --- | --- |
| STM32F722 image | Board factory settings, or the newest valid flash-journal snapshot. | `/config/user.kcfg` over local USB CDC-ACM or the directly wired UART while motor pins remain inhibited. |
| 41 | `control.attitude_roll_pitch_gain_per_s` | Roll/pitch attitude gain in 1/s. |
| 42 | `control.attitude_yaw_gain_per_s` | Yaw attitude gain in 1/s. |
| 43 | `control.yaw_rate_pid.proportional` | Yaw rate proportional gain. |
| 44 | `control.yaw_rate_pid.integral` | Yaw rate integral gain. |
Roll/pitch rate gains use the rate-PID fields. Derivative and contribution limits
are shared.
Use the generated `kaptan-flight-config::SCHEMA_ID` from the source/build being
used. Schema identity describes the field contract; it does not qualify a
physical vehicle or identify calibrated values.
The test fixture has a 10-ms control period, 20-ms control deadline, 250-ms
maximum command-admission TTL, 300-ms offboard stream timeout and one-second
fallback hold. Its configured output lifetime ceiling is 50 ms, further limited
by the 20-ms control deadline and live evidence. The navigation time-error
ceiling is 2000 us. These are library contracts, not measured F722 timing
guarantees or a qualified aircraft profile.
Navigation owns `MAXIMUM_FREE_ACCELERATION_MPS2 = 25`. Complete-profile
validation checks maximum collective thrust `T_max <= m * (25 - g)` using `f64`
arithmetic, and requires thrust above weight. The bound applies to free flight,
not contact impulses. Mixer and installation remain compiled capabilities.
The 1.2-kg/18-N fixture and its installation values exist only in test support.
They do not calibrate physical sensors or identify a propulsion system.
## Web configurator
@@ -27,155 +67,230 @@ as `/dev/serial/by-id/<kaptan-device>` or `/dev/ttyACM0`. For UART, connect a
3.3 V USB-UART adapter to the board-specific RX/TX pads and enter its device
path instead.
The web tool defaults to MAVLink source system `255` and baud `115200`.
`--source-system` and `--baud` remain optional command-line overrides. Every
simultaneously connected GCS must use a different nonzero system ID so transfer
ownership and retries cannot alias another client.
The tool defaults to MAVLink source system `255` and baud `115200`.
`--source-system` and `--baud` are command-line overrides. Match the adapter baud
to the UART's boot-active value. For example, from `firmware/`, launch
`cargo run -p kaptan-configurator --locked --offline -- --baud 230400`.
Peers sharing a link need distinct nonzero source identities, but those
identities do not authenticate them.
Open `http://127.0.0.1:4861`. On discovery, the configurator downloads the
complete `/config/user.kcfg` artifact, validates it, and renders the metadata
and values contained in that file. Save produces one complete edited KCFG
artifact and uploads it. The target validates the complete candidate before
atomically persisting it. The boot-active runtime does not change; manually
power-cycle the controller after a successful save when the UI reports that a
restart is required.
1. Connect and wait for discovery to read both saved and boot-active artifacts.
Each field shows **Boot-active**, **Saved** and **Draft** values.
2. Edit the draft and press **Save**. The target checks the downloaded baseline,
validates the complete replacement and persists it. After success, the tool
reloads both artifacts to establish the next save's baseline.
3. To replace saved settings with the target's factory values, press
**Reset saved configuration**. This is a persistence operation, not a reboot.
It also works when saved state is unknown, provided active metadata was read.
4. Save and factory reset leave active roles, baud, identity, rates and logging
unchanged. When restart is required, manually reset or power-cycle the board,
then reconnect and check the reloaded values. A UART may now use another baud
or no longer carry MAVLink. Normal USB remains the configuration route.
Saving erases one 128-KiB STM32F7 journal sector synchronously. Expect roughly
one second without telemetry or sensor servicing while the target flash
controller is busy; the host gives the final close a separate five-second
timeout. Keep the vehicle unpowered except for setup power and do not disconnect
it during Save.
A stale baseline returns `REQUEST_CONFLICT` without writing flash. The tool
discards that draft. Disconnect and reconnect to reload before editing again.
If saved state is unknown, ordinary saves are blocked. Reconnect to inspect it,
then use factory reset or reboot recovery as described below.
The artifact carries generated field IDs, native scalar kinds, display keys,
units, bounds, enum choices, factory values, committed values, schema identity,
and the derived restart-required state. A new board or field therefore needs no
configurator release. `u8`, `u16`, and enum-backed values retain their native
width rather than being widened at the configuration boundary.
A changed save or explicit factory reset synchronously erases a 128-KiB journal
sector. Flash activity can interrupt telemetry and sensor servicing. The host
allows five seconds for the final close, which is not a measured erase-time
guarantee. Keep setup power connected until the operation completes. An unchanged
ordinary replacement skips flash writes.
The Incoming MAVLink table groups live, CRC-valid Kaptan packets by source and
message ID and shows receive frequency and total count. Selecting a message
watches its latest decoded value. The separate Runtime logs view retains
individual `LOG` and `ERROR` events in receive order, with local receive time,
level, and readable text.
See [MAVLink](mavlink.md) and [Logs](logs.md) for the packet and runtime-log views.
## Board ownership
Each F722 board declares one `UserConfig` root for the settings consumed by its
compiled driver graph. The explicit `FACTORY_USER_CONFIG` literal is the sole
erased-flash value. There is no `Default` implementation and no platform-wide
union of board schemas.
Each F722 board selects `F722UserConfig<Catalog, N>`. The shared factory values
leave every configurable UART disabled. USB always carries Kaptan MAVLink and
has no configurable role. Board catalogs determine which UART fields exist.
Both current boards expose MAVLink identity, IMU and heartbeat publication
rates, and the MAVLink log level filter. Kakute also exposes range and
optical-flow rates because its immutable hardware manifest contains an MTF01.
Those fields are absent from GEPRC rather than present-but-disabled.
Both current boards expose MAVLink identity, publication rates, log filtering,
and `ports` fields from `kaptan-ports`. Range and optical-flow rates are present
on both boards because either can assign an inventoried UART to MTF01. Their
streams are enabled only when the selected sensor service supplies them.
The adjacent `IMMUTABLE_CONFIG` literal remains the sole source of facts that
cannot safely be edited as scalar configuration:
The adjacent `IMMUTABLE_CONFIG` manifest holds:
- output-inhibit and status-LED routes;
- IMU model, bus, interrupt route, mount, and driver setup;
- MAVLink UART and optional vertical-sensor routes and bootstrap baud rates;
- the fixed F722 USB CDC-ACM transport;
- service-timer frequency; and
- compiled image role.
- the IMU route, buses and interrupt wiring
- sensor mount and driver setup
- the available serial inventory, status LED and optional I²C range route
- service-timer frequency.
The F722 platform consumes stable semantic field IDs through the generic
`Config::field_value` boundary. It does not know a board section, Rust field,
or aggregate shape. Whole-candidate validation checks both generated local
rules and relationships such as publication budget and immutable hardware
capabilities.
Motor pins are inhibited at startup. The board images have no physical actuator
driver.
UART roles and baud are writable settings applied only at boot. Each inventoried
UART supports `Disabled = 0`, `KaptanMavlink = 1` (`Role::Mavlink`), and
`Mtf01 = 2`. Disabled ports do not initialize their peripheral or pins.
One board-owned `PortCatalog<N>::DESCRIPTORS` array supplies both
`Ports<Catalog, N>` metadata and `SerialInventory`. The same const `N` sizes the
boot plan, I/O slots and protocol slots. An absent UART has no slot or field.
A declared but disabled UART retains its bounded slot storage.
| Keys | Role / baud field IDs | MCU TX / RX | Board inventory |
| --- | --- | --- | --- |
| `ports.uart1.role`, `ports.uart1.baud` | 201 / 202 | PA9 / PA10 | Both |
| `ports.uart2.role`, `ports.uart2.baud` | 203 / 204 | PA2 / PA3 | Both |
| `ports.uart3.role`, `ports.uart3.baud` | 205 / 206 | PB10 / PB11 | Both |
| `ports.uart4.role`, `ports.uart4.baud` | 207 / 208 | PA0 / PA1 | Both |
| `ports.uart6.role`, `ports.uart6.baud` | 211 / 212 | PC6 / PC7 | Both |
Roles are native `u8` values and baud is `u32`. The F722 allowlist is exactly
**115200, 230400, 460800**. Intermediate values are rejected even though the
displayed bounds span that range. The current MTF01 driver requires **115200**.
At most one UART may use MTF01, and it cannot coexist with the immutable I²C
range source. These restrictions come from current platform capabilities.
Whole-candidate validation checks resource conflicts and the enabled publication
schedule against the 100-Hz service budget. Each MAVLink UART must also carry
the scheduled framing-inclusive bytes at 8N1. This is an admission check, not a
delivery guarantee for unscheduled file or diagnostic traffic. USB has no role
or baud field. See [Boards](boards/index.md) for factory assignments.
## Recovery and activation
`ConfigManager::recover_with` owns recovery state. F722 supplies an explicit
setup policy that validates factory settings with all UART roles disabled when
stored settings cannot be activated. Normal USB MAVLink is initialized
independently of the UART plan. It is not ROM DFU, and its availability still
depends on successful clocks, USB hardware and application initialization.
- Empty storage selects the board factory configuration.
- Readable but invalid committed settings remain available for inspection and
repair through USB. The boot-active export shows the UART-disabled fallback.
- Unreadable storage or a storage error leaves committed state **unknown**.
Active values and factory metadata remain readable. Ordinary writes are
blocked until reboot recovery establishes a baseline or factory reset succeeds.
Fallback selection never rewrites storage. A persistence error also makes the
committed view unknown because the attempted write may already be durable.
Reboot may therefore activate that attempted value. Explicit factory reset
always writes the validated factory, even if values appear unchanged. A failed
reset leaves durability unknown. Neither operation changes the immutable
boot-active view or its boot diagnostic.
## Configuration endpoints
| Path | Access | Meaning |
| --- | --- | --- |
| `/config/user.kcfg` | Read/write | Read the known committed values. Write a complete replacement guarded by its expected base. |
| `/config/active.kcfg` | Read-only | Inspect the actual boot selection, including recovery fallback. |
| `/config/reset.kcfg` | Write-only | Accept only exact factory requested values. Ignore the expected base and force persistence. |
Reset still requires a complete schema-matching KCFG artifact. The configurator
builds it from active metadata. There is no file command to reboot or activate
settings live.
## KCFG artifact
KCFG is the public, canonical import/export representation. It is independent
of MAVLink and of the private storage format. A file contains:
- `KCFG` magic and one format version;
- generated 16-byte schema identity and exact field count;
- one canonical self-describing record for every field; and
- `KCFG` magic, format version **3** and a restart flag
- generated 16-byte schema identity and exact field count
- one record per field with ID, native kind, requested/current value, factory
value and expected-base value at native width, plus field flags, bounds,
display key, unit and enum choices
- a big-endian CRC-32C trailer covering the preceding artifact bytes.
The trailer uses network byte order deliberately. The generic file dialect
also calculates CRC-32C over every complete file byte; network-order storage
keeps that independent transfer checksum content-specific instead of producing
the fixed residue obtained by appending a reflected CRC in little-endian order.
Exports put the selected view's value in both current and expected-base slots.
Edits change only requested values. On ordinary save, the manager compares every
expected-base scalar's native kind and bits with current committed values before
whole-candidate validation, no-op detection or persistence. This is a full-value
compare-and-swap (CAS). Native-kind-and-bit equality also governs no-op and
restart comparisons. Positive and negative floating-point zero differ, while
identical NaN payload bits compare equal when the field's rules allow NaN.
It is not a CRC, generation counter or history check. A change away from and
back to the same values still matches.
Field flag bits 0 and 1 indicate minimum and maximum bounds. Bit 2 is `finite`
and bit 3 is `nonzero`. Other bits are reserved zero. These constraints are
independent: `finite` rejects NaN and infinities, while `nonzero` rejects numeric
zero, including both floating-point signed zeros. `nonzero` alone does not imply
finite values. Any declared floating-point range rejects NaN as unordered even
without `finite`. Infinities are subject to the declared bounds and `finite`
rule. These flags are descriptor metadata and contribute to schema identity.
The configurator exchanges floating-point values with the browser as strings.
Finite decimal strings preserve signed zero. Nonfinite values use exact IEEE
bit strings, `bits:0x` followed by 8 hexadecimal digits for `f32` or 16 for `f64`,
preserving NaN sign and payload. This representation does not bypass validation.
CRC-32C detects accidental corruption. The big-endian trailer avoids the fixed
residue produced by a little-endian reflected CRC under the outer transfer CRC,
but neither checksum proves unique content or authenticates a peer.
An imported file must match the target-generated schema, descriptor metadata,
factory values, field order, native scalar kinds, and exact length. Unknown,
duplicate, missing, malformed, noncanonical, out-of-range, and relationally
invalid values are rejected. The target stages only the typed candidate and a
small parser state; it never buffers the complete public file. Exports are also
generated by range directly from compiled metadata and current committed
values. One small export plan containing the exact length and checksums is
prepared when a read opens and reused for every requested range.
invalid requested values are rejected. The target stages typed requested and
expected-base candidates with a small parser state, not a whole-file buffer.
Exports are generated by range from metadata and the selected view, with one
length/checksum plan prepared at open.
Schema identity is required because a complete file can otherwise be validly
encoded while belonging to another firmware schema. It is generated from the
schema and is not a hand-maintained board ID.
The board-agnostic `decode_kcfg` API instead borrows a complete input artifact
and requires caller-owned 8192-byte field-ID scratch. Hosted callers can allocate
that bitmap directly on the heap. Decoding visits fields once, with work
O(input bytes + 8192), and the returned view borrows only the input, so scratch
is immediately reusable. This dynamic decoder is distinct from the bounded
typed streaming importer used by firmware.
The artifact header's restart bit describes the committed values in an export.
It is not an instruction accepted from an upload: after persistence the target
derives restart state by comparing committed values with immutable boot-active
values.
Schema identity detects schema mismatch. It is not a device identity,
authentication token or proof of all hardware compatibility. Complete-candidate
validation against the immutable context remains authoritative.
The restart bit reports manager state, including on the active export. It is not
an upload instruction. The target derives it from committed versus boot-active
values, or sets it when durable state is unknown.
## Generic file dialect
Kaptan uses two custom messages rather than standard MAVLink FTP or a family of
configuration-specific requests:
```text
FILE_CONTROL
FILE_DATA
```
Kaptan uses custom `FILE_CONTROL` and `FILE_DATA` messages rather than standard
MAVLink FTP or configuration-specific messages.
`FILE_CONTROL` opens, closes, aborts, and acknowledges a transfer. It
carries a client-selected nonzero `u16` transfer ID, exact target, position,
complete file size, CRC-32C, operation/status, and at most 64 bytes of path or
result detail. A source does not reuse an ID during the ten-second replay lease.
A new ID from the same source may supersede an open only before the first byte
has advanced; a progressed abandoned transfer remains pinned until lease expiry.
result detail. A peer does not reuse an ID during the ten-second replay lease.
A new ID from the same source and ingress may supersede an open only before the
first byte advances. A progressed abandoned transfer stays pinned until expiry.
`FILE_DATA` uses the full MAVLink payload budget for a
246-byte chunk. The logical final-chunk length follows from the opened file
size, so no redundant length field is sent with every data packet. An upload
request carries status `UNSPECIFIED`; a read-data response carries status `OK`.
All failures remain typed `FILE_CONTROL` responses.
The service owns one bounded stop-and-wait transfer. Chunks are sequential;
immediate retries are replay-safe, conflicting retries are rejected, and stale
transfer IDs cannot mutate a later transfer. A receive fault or bounded lease
expiry aborts staged provider state. The generic layer knows only paths, bytes,
positions, checksums, ownership, and retry state. It does not parse KCFG or
contain configuration policy.
One stop-and-wait transfer is shared across all links. Ownership includes the
claimed source system/component and the platform-supplied ingress identity.
Chunks are sequential. Immediate retries compare exact retained bytes, not just
checksums. Conflicting retries are rejected. Accepted owner activity renews the
ten-second monotonic lease, while rejected traffic does not. Expiry aborts staged
provider state. A receive discontinuity resets framing without itself releasing
the file lease. Responses use the requesting link.
The current provider exposes exactly `/config/user.kcfg`. A successful write
close has generic status `OK` and content beginning with the authoritative
seven-byte KCFG result prefix: restart flag, primary field, related field, and
validation-rule code. Its exact layout is `flags:u8 | primary_field:u16 LE |
related_field:u16 LE | validation_rule:u16 LE`; flag bit 0, when set, means a
For either writable KCFG endpoint, a successful close has generic status `OK`
and an authoritative seven-byte KCFG result prefix:
`flags:u8 | primary_field:u16 LE |
related_field:u16 LE | validation_rule:u16 LE`. Flag bit 0, when set, means a
restart is required, and every other flag bit is reserved zero. The field
sentinel is `0xffff`; rule zero means no rule.
sentinel is `0xffff`. Rule zero means no rule.
KCFG `CONTENT_REJECTED` responses use the same prefix and may append bounded
UTF-8 detail. The configurator rejects a successful close with a missing,
malformed, or failure-bearing prefix. The file layer passes these bytes through
without interpreting them.
Local USB CDC-ACM and the directly wired UART are the F722 transports. CRC
validates framing integrity, not peer authenticity. Use one directly connected
client and disconnect other transmitters from the board RX line while using the
UART. A shared or remote GCS still requires signing, source/key binding,
persistent anti-replay state, and provisioning before this inbound service can
be exposed safely.
See [MAVLink](mavlink.md) for the local-link trust boundary.
## Private persistence
F722 uses private persistence separate from the public KCFG artifact.
The F722 private storage format is independent of the public KCFG artifact.
### F722 flash journal
F722 stores a compact private `KCSP` scalar snapshot inside a two-slot journal.
Each entry stores its stable field ID, scalar kind, and value at that kind's
native width; an `u8` is one byte rather than a widened integer slot. Each
F722 stores a `KCSP` scalar snapshot inside a two-slot journal.
Each entry stores its stable field ID, scalar kind and value at native width. Each
journal record includes payload length, CRC-32C, commit marker, and a private
monotonic sequence. Saving erases the inactive slot, programs and verifies the
complete replacement, and writes the commit marker last. The previous valid
+13 -2
View File
@@ -78,14 +78,25 @@ different reportable error is already pending.
`PersistConfigurationError` ID that distinguishes journal geometry, model,
sequence, flash-controller, bounds, unlock, and verification
failures. Replaying the cached close result does not repeat the report or
flash operation.
flash operation. Any persistence error makes committed state unknown and
blocks ordinary saves until reboot recovery or a successful explicit factory
reset. Boot-active values remain unchanged.
- A stale KCFG expected base returns `REQUEST_CONFLICT` before persistence.
It is a reload-required editing conflict, not a reportable firmware error.
Invalid boot configuration uses the UART-disabled USB setup path and retains
readable saved values for repair. See [Configuration](configuration.md).
- SPI completion errors never copy partial data into the caller's receive
buffer. If bounded shutdown cannot establish quiescence, the SPI route
quarantines its static DMA storage and refuses further transfers. This is
distinct from transient `Busy`. Physical SPI shutdown and fault behavior
remain hardware-validation work.
- Reported `ReportableError` values reach the F722 MAVLink worker as a
`ERROR`, subject to bounded transport admission. The payload is
`{ error_id, raised_at[96] }`. `raised_at` is a zero-padded UTF-8
`file:line` prefix for the reporting point.
Dynamic human-readable context belongs in `LOG`, while
structured protocol failures remain in their owning response message.
- Control, navigation, and safety error types are derive-ready but
- Control, navigation and safety error types are derive-ready but
their live owners are not yet attached to a MAVLink worker.
F722 uses `panic-halt`, without live panic reporting or retained-record replay.
+2 -2
View File
@@ -30,8 +30,8 @@ target-specific commands in [Development and verification](development.md).
| Path | Responsibility |
| --- | --- |
| `firmware/boards/` | Board-owned user schemas, factory values, immutable hardware declarations, and image composition roots. |
| `firmware/platforms/stm32f7/` | Reviewed F722 RTIC platform, peripheral ownership, and shared linker layout. |
| `firmware/boards/` | Hardware declarations, UART catalogs and image roots. |
| `firmware/platforms/stm32f7/` | F722 RTIC platform, user schema, factory values, peripheral ownership and linker layout. |
| `firmware/libs/` | Typed math, estimation, control, safety, protocol, and driver crates. |
| `firmware/tools/configurator/` | Board-agnostic loopback web UI and serial bridge. |
| `docs/` | This Markdown documentation site. |
+3 -3
View File
@@ -4,9 +4,9 @@ Kaptan is an embedded Rust flight-controller project. This manual is the
single source of project documentation: board support, safety boundaries,
development workflow, and design references live here as Markdown.
> **Status:** the checked-in firmware is telemetry-only. It does not expose
> physical motor authority, and this site does not make a physical-flight
> release claim.
> **Status:** The board images do not yet run the flight runtime or implement
> motor drivers. Motor pins are inhibited at startup. Physical flight has not
> been qualified.
## Index
+11 -9
View File
@@ -60,11 +60,12 @@ Rust error variant. Renaming or reordering a variant does not change it.
diagnostics. Protocol failures remain in their owning response; for example, a
rejected KCFG upload returns a `FILE_CONTROL` failure and readable
bounded detail.
Delivery is bounded and lossy under link pressure; heartbeat and safety
Delivery is bounded and lossy under link pressure. Heartbeat and sensor
telemetry use separate paths. Logs have no publication rate setting and are
not periodic streams. A retained event leaves its MAVLink sink only once the
lower transport admits it; temporary queue pressure only defers that same
event.
not periodic streams. The worker offers each retained event to USB and every
MAVLink UART, then removes it once at least one transport admits it. Admission
does not prove receipt or delivery to every link. If none admits it, the event
remains pending. Opening USB does not take diagnostics away from UART.
## Filtering
@@ -73,8 +74,11 @@ Trace (1), Debug (2), Info (3), Warn (4), and Error (5). `Info`, for example,
accepts Info, Warn, and Error log events. `Off` suppresses `LOG` on MAVLink
without suppressing reportable `ERROR` events or logs sent to another
sink. There is no separate enable flag and no MAVLink-specific set of extra
runtime log levels. F722 uses the validated filter recovered at boot, falling
back to its board factory when the journal is empty or invalid.
runtime log levels. F722 uses the validated boot-active filter. Empty storage
uses factory values. Invalid storage selects the setup fallback, which also
uses the factory filter but disables UART roles. Saved changes and factory
reset do not alter the active filter until reboot. See
[configuration recovery](configuration.md#recovery-and-activation).
Events retain the compact typed `Level` and sink configuration retains the
compact typed `LevelFilter`. Schema metadata supplies the filter choices to the
@@ -82,6 +86,4 @@ configurator.
Only the MAVLink adapter converts the five event levels to the typed Kaptan
wire enum.
The log ingress is transport-neutral and unfiltered. F722 images accept the
unsigned Kaptan file service on local USB CDC-ACM and their directly wired UART; that physical-link assumption does not extend to a shared
or remote MAVLink network.
The log ingress is transport-neutral and unfiltered.
+55 -36
View File
@@ -9,48 +9,67 @@ dialect. Inbound processing is narrowly scoped:
- Signed frames are discarded because no signature verifier or persistent
replay state exists.
- CRC-valid unsigned MAVLink 2 frames are retained only when their message ID
and extra CRC belong to the Kaptan dialect. Exact-target Kaptan file requests
are the only inbound records with a current firmware consumer.
and extra CRC belong to the Kaptan dialect. The board firmware consumes
exact-target Kaptan file requests.
Shared or remote inbound authority is deliberately unsupported. Enabling it
requires verified MAVLink 2 signing, configured source/target binding,
persistent anti-replay state, and a provisioning/key-rotation policy as one
reviewed feature.
Each F722 image exposes MAVLink on its board-selected UART and as a USB CDC-ACM
serial device. Linux normally names the application USB link `/dev/ttyACM*`;
this is distinct from the STM32 ROM DFU device used while the Boot pin is
asserted. UART and USB have independent receive framers. A host that opens the
CDC port and asserts DTR becomes the publication and diagnostic link; UART is
used while USB is closed. A file response is always returned on the link that
carried its request, so connecting USB cannot redirect a UART transaction.
Each F722 board image initializes USB CDC-ACM as a MAVLink link independently
of UART roles. Linux normally names it `/dev/ttyACM*`. It is separate from the
STM32 ROM DFU device used for flashing. Every UART assigned `KaptanMavlink` at
boot has its own link. USB needs an enumerated device and asserted DTR to carry
traffic. Opening or closing it does not redirect UART publications.
Each link has independent scheduling, encoding, framing and bounded TX state.
Current links share the same boot-active endpoint identity and publication
rates, with one shared sensor snapshot per service turn. There are no per-link
rate settings. A stalled link does not consume another link's publication state,
though all links share the protocol worker and flash-operation stalls. File
responses always return on the requesting link.
The current descriptor uses the pid.codes shared development identity
`1209:0001` plus the STM32 factory device ID as its serial number. Obtain and
substitute a project-specific PID before distributing hardware as a product.
These local links are operational file transports, not authenticated
The normal telemetry links are operational file transports, not authenticated
remote-control channels. Any transmitter physically connected to either link
can issue file requests, so use a direct USB cable or USB-UART adapter and
disconnect other transmitters while configuring. The platform retains its
startup-configured low motor-output state throughout this workflow; no marker
type is treated as GPIO ownership or command authority. A networked GCS must
add verified signing, source binding, and durable replay protection before it
can receive authority.
type is treated as GPIO ownership or command authority.
The custom Kaptan file service admits one exact source principal for a bounded
stop-and-wait transfer. A receive fault or lease expiry aborts its staged
provider state. Completed control replies have a bounded replay path so a lost
close response does not repeat a flash operation. This serialization does not
authenticate its peer.
One file service serializes transfers across USB and all MAVLink UARTs. Its
owner is the claimed source system/component plus ingress identity, so the same
wire identity on another port cannot take over a transfer. Completed replies
have an exact bounded replay path so a lost close response does not repeat a
flash operation. Neither source identity, schema identity nor CRC authenticates
a peer. [Configuration](configuration.md#generic-file-dialect) defines the lease
and retry contract.
Telemetry transmission uses bounded frame classes. A critical frame may
replace pending best-effort traffic, but never a UART-DMA-owned or active USB
frame. UART completion retries preserve ordering, while USB advances a retained
frame across bounded CDC endpoint writes. DTR close, suspend, and
deconfiguration pause USB transmission and return publications to UART. Only a
bus reset discards USB transmission state. Closing DTR also discards partial
input. File-transfer and response-replay state expire under the protocol lease,
not on transport disconnect.
Telemetry transmission uses bounded frame classes. A critical frame may replace
pending best-effort traffic, but never an active frame. UART TX is IRQ-owned:
each interrupt writes at most one byte and retires at most one completed frame.
USB advances a retained frame through bounded CDC endpoint writes. DTR close,
suspend and deconfiguration pause USB TX. Only a bus reset discards its TX state.
Closing DTR discards partial input. UART receive faults reset framing. File and
replay state remain governed by the protocol lease rather than disconnects.
USB receive backpressure masks only `GINTMSK.RXFLVLM` while application packet
bytes remain unconsumed. The final byte cursor and gate update share the USB
resource lock. The OTG FS NVIC vector stays enabled, as do IN/TX completion,
reset and suspend interrupt sources. This does not make control reception
independent: EP0 shares the RX FIFO and cannot bypass a blocked bulk packet at
its head. Once buffered bytes are consumed, RXFLVLM is restored. An incomplete
MAVLink frame alone does not keep RX masked.
UART role and baud changes take effect after reboot. See
[Configuration](configuration.md) for allowed values and USB repair, and
[Architecture](architecture.md#io-ownership) for RX buffering and ownership.
Normal USB configuration recovery does not guarantee availability after an
arbitrary clock, USB hardware or initialization failure.
The local web configurator exposes a bounded live view of CRC-valid inbound
frames. It groups packets by source and message ID and measures receive
@@ -71,35 +90,35 @@ one-message leaves grouped by interaction contract:
| Directory | Contract |
| --- | --- |
| `publish/` | Zero-reply records for any interested receiver. |
| `exchange/` | Exact-endpoint requests with one terminal response; currently empty. |
| `transfer/` | Exact-endpoint, bounded multi-message sessions. |
The root includes every message leaf directly; there are no redundant category
index files or separate enum catalogs. Placement is a wire-contract
classification, while bindgen still generates one flat `generated::kaptan`
module. Bare message names are scoped by that sole dialect and must not be
merged with another MAVLink dialect. `exchange/` gains its first XML only when
an exchange has a current consumer.
merged with another MAVLink dialect.
`LOG` is an outbound human-readable four-field record. `ERROR`
is the separate bounded channel for reportable firmware errors, carrying a
stable explicit ID plus the `file:line` of its reporting point, and needs no
redundant level field. `FILE_CONTROL` and `FILE_DATA` implement
Kaptan's generic virtual-file transport without standard MAVLink FTP
compatibility. The current configurator reads and replaces
`/config/user.kcfg`. See
compatibility. The configurator reads saved and boot-active KCFG values and can
save replacements or reset saved settings to factory values. See
[Configuration](configuration.md), [Logs](logs.md), and [Errors](errors.md) for
the exact contracts.
The current scheduled publications are deliberately native rather than
The board firmware's scheduled publications are native rather than
projections into a broader dialect:
| Publication | Current payload |
| --- | --- |
| `HEARTBEAT` (51027) | The single current initialized/output-inhibited state. |
| `IMU` (51028) | Monotonic microseconds, body acceleration in mg, angular rate in mrad/s, and temperature in cdegC. |
| `IMU` (51028) | `u64` monotonic microseconds, `i16[3]` body acceleration in mg, `i32[3]` angular rate in mrad/s, and `i16` temperature in cdegC. |
| `RANGE` (51029) | Monotonic microseconds and downward distance in mm. |
| `OPTICAL_FLOW` (51030) | Monotonic microseconds, integer ground-relative velocity in mm/s, distance in mm, and quality. |
These records preserve the firmware's bounded native values. They do not
carry unused sensor IDs, repeated board topology, float projections, narrowed
timestamps, or clamped measurements.
The IMU payload is 28 bytes with MAVLink extra CRC **22**. Wire field ordering
places time at byte 0, angular rate at byte 8, acceleration at byte 20 and
temperature at byte 26. The signed 32-bit gyro units preserve the full decoded
MPU6000 ±2000°/s range through mounting, mailbox handoff and encoding.
+74 -20
View File
@@ -1,34 +1,88 @@
# Safety boundary
Every checked-in F722 image drives PB0, PB1, PB4, and PB5 low before normal
board initialization. This startup output inhibition configures each pin as a
low, push-pull GPIO output; it runs before clock-tree setup, board routing,
UART setup, or RTIC tasks. There is no marker or token standing in for the
register writes.
F722 startup drives PB0, PB1, PB4 and PB5 low, plus PB6 and PB7 on Kakute.
It configures them as push-pull GPIO outputs before clock-tree setup, board
routing, UART setup or RTIC tasks.
This is a firmware startup property, not a hardware latch. Reset temporarily
returns GPIO configuration to the MCU reset state, and the code cannot prove
external routing, pull resistors, ESC polarity, or ESC power behavior. Board
support must document those electrical assumptions; continuous inhibition
external routing, pull resistors, ESC polarity or ESC power behavior. Board
support must document those electrical assumptions. Continuous inhibition
through reset requires board hardware that provides it.
There is no checked-in platform actuator route:
- no DShot or PWM peripheral implementation is linked;
- no TIM3/TIM5 motor task or DMA output path exists;
- only CRC-valid Kaptan-dialect MAVLink input reaches the bounded virtual-file
service, which has no actuator path;
- no arm command is defined or admitted.
The framing and file-transfer crates do not grant hardware access or actuator
authority.
The board images have no DShot, PWM or other actuator driver. The portable
flight runtime is not yet connected to physical motors. File requests grant
no actuator access.
Run the structural guard after any platform or board change:
```sh
make firmware -- test -p platform-stm32f7 \
cd firmware
cargo test -p platform-stm32f7 \
--test platform_safety_boundary --locked --offline
```
This boundary is deliberately stronger than an application-level disarm flag:
it removes the physical actuator implementation from the platform image.
## Shared runtime commit contract
`FlightEndpoint` uses `SafetySupervisor::evaluate_and_commit` to
evaluate current evidence and invoke an enabled-output callback at most once
while exclusively borrowing that supervisor. The callback synchronously retains
a core-owned `OutputRecord` in caller-owned storage. This establishes local
publication only. The receiving consumer must check the record's binding,
sequence and absolute validity at application time. A queued record or successful
USB write is not a reusable permission or proof of remote application.
Runtime ingress carries a producer session and runtime generation, with separate
command-owner, arm, offboard and origin bindings. Commands and setpoints have
independent strictly increasing sequences. Retransmission retains its original
identity and bytes. Replays and wrong bindings cannot renew authority, and checked
authority sequences cannot wrap. A local `begin_epoch` is allowed only while
disarmed after successful inhibited publication. It resets navigation and request
watermarks while preserving the producer's output sequence and clock history.
These typed runtime identities do not authenticate a peer.
Observation admission receives independently captured processing time in that
epoch. The measurement-time upper bound must not follow receipt time, receipt
must not follow processing time, and conservative measurement age must satisfy
the stream's freshness bound.
Receipt alone does not establish accepted or current evidence. Explicit clock
ticks advance navigation and supervision even when input stops. The current
control period is 10 ms on an absolute due grid with a 20-ms control deadline.
`supervise` checks time, cached health and deadlines without integrating control
or renewing enabled output. A deadline miss is a fault, not permission to replay
an old allocation indefinitely.
Returned enabled-output errors latch a fault and trigger an inhibition attempt.
The sink must remain callable for that attempt. At terminal landing, the endpoint
clears arm authority and attempts inhibition before publishing terminal command
outcomes. Successful stop completion requires accepted inhibition; a rejected
inhibition leaves the command failed. Callers must reconcile committed outcomes after
both successful and failed core calls. This ordering does not prove physical
touchdown or motor stop.
Output callbacks must not panic. Cleanup guarantees apply to
returned errors, not unwinding. A host that catches an unwind must discard the
affected endpoint and output owner rather than resume them or use an epoch reset
as recovery. Platform panic containment owns the physical
response. Abort or halt alone does not establish zero output.
## Core failure policy
`TakeoffHoverLand` starts only from `ArmedIdle` on a fresh command. Settled
takeoff starts a 30-second hold, followed by landing under the existing deadline
and landing reserve. It completes after accepted output inhibition. A superseding
command cancels the mission. The board integration prerequisites above still apply.
With required navigation and output health present, offboard stream expiry
after 300 ms latches fallback, holds for the configured duration (at most one
second) from expiry, then lands.
Late packets do not resume the expired stream. Loss of required IMU/range or
other navigation capability, numerical failure, control-deadline failure or
output-publication failure can terminate and inhibit instead. Hold-then-land is
not promised without its required evidence.
The core's configured output lifetime is at most 50 ms, further shortened by
capability evidence and the current 20-ms control deadline. A future physical
composition must enforce output validity at its actuator boundary and establish
its own hardware timing and failure behavior. The 1.2-kg/18-N test fixture is
uncalibrated. Component verification does not qualify a vehicle.