docs: document board configuration workflow

This commit is contained in:
Onur Özkan committed 2026-08-22 18:41:07 +03:00
1 parent b7631c0bc7
commit 97acb2a2d7
13 files changed
+470 -173

No files matched your search

+43 -30
View File
@@ -10,19 +10,22 @@ board declaration
F722 platform / RTIC application ──► data channel ──► MAVLink telemetry
│
├── physical sensor drivers
└── motor-pin inhibit interlock
└── startup motor-pin inhibition
portable libraries
math ─► navigation ─► control ─► safety
```
The board contract selects one immutable `BoardProfile`. The checked-in
profile is `TelemetryOnly`; `ExternalControlOnly` is represented explicitly
but rejected at validation time because it has no reviewed runtime.
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.
The data channel owns rate selection, mailbox consumption, message encoding,
and bounded TX classification. Board code supplies physical routes and
telemetry configuration; it does not receive generic output authority.
and bounded TX classification. Board code supplies physical routes, target
schema, and factory values; it does not receive generic output authority.
Configuration and runtime observability are separate, one-way planes:
@@ -33,8 +36,8 @@ Kaptan GCS custom MAVLink V2 messages
kaptan-config-mavlink ──► state-only ConfigProtocol
│ borrows
kaptan-flight-runtime
├── one ConfigManager ──► kaptan-config ──► flight schema/CBOR
└── one kaptan-log hub
├── one ConfigManager ──► kaptan-config ──► flight schema/store
└── one bounded MAVLink log-sink queue
│
kaptan-log-mavlink payload adapter
│
@@ -43,39 +46,49 @@ kaptan-config-mavlink ──► state-only ConfigProtocol
The generated dialect is intentionally custom rather than a `PARAM_*` or QGC
adapter. The core uses numeric field IDs and typed scalar values; descriptor
text is metadata, not a wire identity. Discovery/value reads, bounded
descriptor pages, and structured results are custom-dialect records. Runtime
logs use one bounded text message with MAVLink severity and flags. CBOR import uses a fixed
4 KiB transaction buffer and
fixed 192-byte chunks; its decoded candidate still goes through the same whole
profile validation path. The core owns one RAM transaction and the distinction
text is metadata, not a wire identity. Discovery/value reads, exact-index
descriptor and enum-option requests, and structured results are custom-dialect
records. Runtime logs use one bounded text message with MAVLink severity and
flags. Remote writes use only schema-described typed scalar setters. The core
owns one RAM transaction and the distinction
between boot-active, committed, and editable candidate snapshots. In the
current P1 policy a commit is restart-only. It cannot mutate controller or
estimator state in flight.
One current CBOR profile format is a bounded complete snapshot. There is no
migration reader: any other format is rejected. SITL supplies the host file
adapter and persists a nonzero generation alongside its raw profile payload.
Motor allocation/mixer topology is immutable compiled
vehicle capability data, never a scalar field or CBOR candidate member. The
bare-metal receiver continues to reject inbound control authority. Target
persistence, authenticated ingress, durable audit logging, disarmed
activation, and atomic consumer reconstruction remain separate reviewed work
and cannot be implied by a valid CBOR file or CRC.
Target-owned stores may serialize one bounded complete CBOR snapshot. SITL
supplies the host file adapter and persists a private nonzero storage sequence
alongside that snapshot. The serialization and sequence are not configuration
state and are never exposed by the MAVLink protocol.
Motor allocation/mixer topology is immutable compiled vehicle capability data,
never a scalar field or CBOR candidate member. F722 now recovers operational
settings from a generic two-slot journal before constructing consumers. Its
directly wired local UART accepts only exact-target, CRC-valid configuration
commands while physical outputs remain inhibited. CRC is integrity-only; a
shared or remote GCS still needs production authentication, durable audit
logging, source binding, and persistent anti-replay state. Disarmed activation
and flight-consumer reconstruction remain separate reviewed work and cannot be
implied by a valid snapshot or CRC.
Configuration wire layout `2.0` uses one exact response per descriptor or enum
option request. The first accepted transaction binds one source principal
through a bounded post-terminal replay lease; foreign traffic cannot evict a
lost result before that lease expires.
The MAVLink dialect root includes `kaptan/config.xml`, `kaptan/log.xml`,
`kaptan/error.xml`, and `kaptan/shutdown.xml`.
The build watches every fragment and generates one `message::kaptan` module;
fragments are never separately generated Rust modules. `kaptan-log` itself has
no MAVLink, platform, time, allocation, filesystem, or configuration
dependency. It assigns bounded Critical and BestEffort queues for text logs.
The platform drains a record only after TX admission. `KAPTAN_LOG` carries
severity plus one bounded text field; `KAPTAN_ERROR` carries flags plus bounded
path and additional-information fields. Local saturation accounting remains
inside the hub, facade, and producer mailboxes; heartbeat and safety telemetry
remain separate from diagnostics.
dependency. Event creation is independent from sink filtering, allowing a
future blackbox and MAVLink to select different policies. Each sink owns
bounded Critical and BestEffort queues. The platform drains a MAVLink record
only after TX admission. `KAPTAN_LOG` carries
severity plus one bounded text field; `KAPTAN_ERROR` carries exact error
severity and flags plus bounded path and additional-information fields. Local saturation accounting remains
inside the sink queue, facade, and producer mailboxes; heartbeat and safety
telemetry remain separate from diagnostics.
See [Configuration](configuration.md) for the deployment matrix and GCS upload
See [Configuration](configuration.md) for the deployment matrix and GCS transaction
contract, [Logs](logs.md) for the receiver contract, and [Errors](errors.md)
for derived-error and failure-reporting scope.
+61 -10
View File
@@ -1,21 +1,72 @@
# GEPRC GEP-F722-HD V2
The GEPRC GEP-F722-HD V2 board declaration uses the ICM-42688-P on SPI3 and
MAVLink telemetry on USART2. It declares no vertical sensor stream.
MAVLink telemetry on 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`.
Its immutable board policy selects the ICM-42688-P yaw-180° mount, logical
MAVLink `Serial2`, endpoint `1/1`, 115200 baud, the documented telemetry
schedule, and an `INFO` optional-log threshold. The platform verifies these
values against the checked SPI3/USART2 typestate route before it enables
peripherals. They are compiled image policy, not ground-station parameters.
Its board-local `UserConfig` factory selects endpoint `1/1`, raw-IMU and
heartbeat publication rates, and an enabled MAVLink diagnostic sink with an
`INFO` maximum severity. 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.
Build and package from the firmware workspace:
## Build and package
From the firmware workspace:
```sh
cd firmware
cargo build --release -p kaptan-geprc-f722-v2 --target thumbv7em-none-eabihf
cargo build --release --locked -p kaptan-geprc-f722-v2 \
--target thumbv7em-none-eabihf
./scripts/create-dfu-image.sh target/thumbv7em-none-eabihf/release/kaptan-geprc-f722-v2
```
The resulting image is telemetry-only and has the shared physical motor-pin
inhibit boundary.
The packaging script validates the ELF against the shared F722 linker layout
and prints the flash and RAM report before producing the raw binary.
## Flash over USB DFU
Remove the propellers and disconnect the LiPo before connecting USB. This image
is linked at the beginning of internal flash and the command below assumes the
STM32 system-memory ROM DFU loader, not a flash-resident bootloader with an
application offset. Enter the board's documented ROM DFU mode, disconnect any
other STM32 boards in DFU mode, and verify that the intended device is the
listed `0483:df11` target before flashing:
```sh
dfu-util --list
dfu-util --device 0483:df11 --alt 0 \
--dfuse-address 0x08000000:leave \
--download target/thumbv7em-none-eabihf/release/kaptan-geprc-f722-v2.bin
```
## Telemetry wiring
Connect a 3.3 V USB-UART adapter with crossed data lines and common ground:
| Adapter | Flight controller |
| --- | --- |
| `RX` | `T2` |
| `TX` | `R2` |
| `GND` | `GND` |
Then run:
```sh
cargo run --locked -p kaptan-configurator -- \
--serial /dev/serial/by-id/<adapter> --baud 115200
```
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 this direct wiring; disconnect every other transmitter from `R2` while
configuring.
+40 -11
View File
@@ -5,11 +5,15 @@ MAVLink telemetry on USART1, and the MTF01 range/optical-flow route on UART4.
The checked-in manufacturer datasheet is
`firmware/boards/kakute-f7-mini-v3/datasheet.pdf`.
Its immutable board policy selects the MPU-6000 body-aligned mount, logical
MAVLink `Serial1`, endpoint `1/1`, 115200 baud, the documented telemetry
schedule, and an `INFO` optional-log threshold. The platform verifies these
values against the checked SPI1/USART1/MTF01 typestate route before it enables
peripherals. They are compiled image policy, not ground-station parameters.
Its board-local `UserConfig` factory selects endpoint `1/1`, raw-IMU, range,
optical-flow and heartbeat publication rates, and an enabled MAVLink diagnostic
sink with an `INFO` maximum severity. 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.
## Build and package
@@ -17,22 +21,33 @@ From the firmware workspace:
```sh
cd firmware
cargo build --release -p kaptan-kakute-f7-mini-v3 --target thumbv7em-none-eabihf
cargo build --release --locked -p kaptan-kakute-f7-mini-v3 \
--target thumbv7em-none-eabihf
./scripts/create-dfu-image.sh target/thumbv7em-none-eabihf/release/kaptan-kakute-f7-mini-v3
```
The packaging script derives the matching binary and linker layout, then
prints the flash and RAM report.
The packaging script uses the shared F722 linker layout and prints the flash
and RAM report before producing the raw binary.
## Flash over USB DFU
Remove the propellers and disconnect the LiPo before connecting USB. This image
is linked at the beginning of internal flash and the command below assumes the
STM32 system-memory ROM DFU loader, not a flash-resident bootloader with an
application offset.
1. Disconnect USB.
2. Hold the board's `Boot` button.
3. Connect USB, then release `Boot` once the STM32 DFU device enumerates.
4. Flash the generated image:
4. Disconnect any other STM32 boards in DFU mode and verify that the intended
device is the listed `0483:df11` target.
5. Flash the generated image:
```sh
dfu-util --alt 0 --dfuse-address 0x08000000:leave --download target/thumbv7em-none-eabihf/release/kaptan-kakute-f7-mini-v3.bin
dfu-util --list
dfu-util --device 0483:df11 --alt 0 \
--dfuse-address 0x08000000:leave \
--download target/thumbv7em-none-eabihf/release/kaptan-kakute-f7-mini-v3.bin
```
## Telemetry wiring
@@ -45,4 +60,18 @@ Use a 3.3 V USB-to-UART adapter with crossed data lines and common ground:
| `TX` | `R1` |
| `GND` | `G` |
This wiring is telemetry only. It does not enable motor control.
Launch the target-described web UI:
```sh
cargo run --locked -p kaptan-configurator -- \
--serial /dev/serial/by-id/<adapter> --baud 115200
```
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 this direct wiring; disconnect every other
transmitter from `R1` while configuring.
+181 -58
View File
@@ -1,21 +1,102 @@
# Configuration
Kaptan has one generic configuration lifecycle crate, `kaptan-config`. It
owns descriptors, typed writes, transactions, validation, generations, and
stores. It does not grow a crate per subsystem. Flight-specific values live in
the single `kaptan-flight-config` candidate; board electrical topology remains
in the immutable board declaration.
Kaptan has one generic configuration lifecycle in `kaptan-config`: descriptors,
typed writes, transactions, validation, scalar snapshots, and a
power-loss-safe two-slot journal. Target models declare fields once; derive code
generates the registry consumed by both the protocol and the browser. There is
no board, driver, sensor, schema, or parameter catalog in the configurator.
## Current deployment matrix
| Deployment | Configuration consumed today | Remote writes |
| --- | --- | --- |
| `kaptan-sitl serve --config FILE.cbor` | One validated Quad-X profile at process construction. | No live MAVLink endpoint is attached yet. |
| `kaptan-sitl serve` | The validated factory Quad-X profile. | No persistent store; commits are rejected. |
| STM32F722 telemetry image | Immutable compiled `BoardConfig` plus checked typestate routes. | Disabled: all inbound MAVLink remains discarded. |
| `kaptan-sitl serve --config FILE.cbor` | One validated Quad-X configuration at process construction. | No live MAVLink endpoint is attached yet. |
| `kaptan-sitl serve` | The validated factory Quad-X configuration. | No persistent store; commits are rejected. |
| STM32F722 image | Board factory settings, or the newest valid flash-journal snapshot. | Enabled on the directly wired local UART while motor pins remain inhibited. |
The current SITL profile has only settings consumed by its estimator,
controller, safety supervisor, allocator envelope, or log hub:
## Web configurator
Build and flash the board image, connect its MAVLink UART directly to a 3.3 V
USB-UART adapter, then run from `firmware`:
```sh
cargo run --locked -p kaptan-configurator -- \
--serial /dev/serial/by-id/<adapter> --baud 115200
```
Open `http://127.0.0.1:4861`. Edit discovered fields and press
Save. Save commits the complete candidate to the inactive flash slot and writes
the commit marker last. The active runtime does not change. Once Save completes,
manually power-cycle the controller to recover the committed journal and
activate it.
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 uses a separate five-second Save timeout. Keep the
vehicle unpowered except for setup power and do not disconnect it during Save.
The tool discovers the target's protocol version and schema identity, renders
advertised descriptors, and reads/writes values only by stable `FieldId`. A new
board or field needs no host release. The board package remains the composition
root for erased-flash defaults; the platform never switches on a board name.
Enum-backed fields advertise their valid value/label pairs through exact
descriptor and option-index requests, so the browser renders a select control
without knowing the field key or compiling its choices.
The Incoming MAVLink table at the bottom groups live, CRC-valid packets by
source and message ID and shows the measured receive frequency and total count.
Names come from the generated MAVLink dialect decoders; the browser contains no
message catalog. Selecting a message name shows the latest decoded field values;
the diagnostic projection renders `KAPTAN_ERROR` path and additional information
as text. The configurator keeps only one latest value per bounded aggregate,
rather than packet history, and clears it for every serial-link session.
The separate Runtime logs section retains the most recent 512 `KAPTAN_LOG` and
`KAPTAN_ERROR` events in receive order. Each row shows local receive time,
decoded severity, and readable diagnostic text. Repeated events remain separate;
older host-side entries are discarded at the fixed display bound and the UI
reports that count. This event history is independent of the packet table's
single latest-value projection and resets when the serial link changes.
Each F722 board owns the exact settings consumed by its compiled image. Both
current boards advertise:
- MAVLink system and component identity;
- raw-IMU and heartbeat publication rates; and
- whether the MAVLink diagnostic sink is enabled and its maximum severity.
The Kakute image also advertises range and optical-flow publication rates
because its immutable route includes an MTF01. The GEPRC image declares no
vertical sensor, so those fields are absent rather than present-but-disabled.
The UART baud is the immutable bootstrap link for this first configuration
transport. Pass it explicitly to the host with `--baud`.
Each board declares one `UserConfig` root for its compiled driver graph. That
root composes board-local `MavlinkConfig` and `TelemetryConfig` records; derive
code generates recursive routing, validation, and the schema fingerprint. The
board's explicit `FACTORY_USER_CONFIG` literal supplies erased-flash values.
Board-native IDs and publication rates retain their narrow `u8` representation.
Compact enums advertise names and values through explicit indexed descriptor
metadata rather than a hardcoded browser-side configuration catalog.
There is no platform-wide union, reusable concrete board schema, or host-side
switch.
The F722 platform consumes only its stable semantic field IDs through the
generic `Config::field_value` boundary; it never names a board section, Rust
field, or aggregate shape. Range and optical-flow rates are required only when
the immutable hardware manifest exposes those capabilities. Driver- and
board-specific fields remain in the same discovered tree without platform
forwarding code.
The adjacent `IMMUTABLE_CONFIG` literal is the complete physical manifest. Its
typestate expression visibly selects the inhibited output pins, IMU route,
mount and driver setup, MAVLink route and baud, optional vertical-sensor route
and baud, status LED, and telemetry-only role. Those choices are not hidden in
board-specific getters or a `build()` function.
The current SITL configuration has only settings consumed by its estimator,
controller, safety supervisor, allocator envelope, or MAVLink diagnostic sink:
- prearm dwell;
- IMU, local-aiding, and command freshness/timing;
@@ -24,79 +105,121 @@ controller, safety supervisor, allocator envelope, or log hub:
- mass, collective-thrust, and tilt limits;
- rate PID gains and limits;
- actuator minimum, maximum, and slew limits; and
- the restart-only maximum optional log severity.
- the restart-only MAVLink diagnostic-sink enable and maximum severity.
The fixed Quad-X mixer is intentionally not configurable. It is reviewed
capability data, not an operator-editable array.
capability data, not a user-editable array.
Each editable field declares its ID, display key, unit, default, scalar type,
and local validation in one `#[config(...)]` attribute. The derive generates
the descriptor registry and local validation from that declaration; the only
handwritten checks are whole-profile physical relationships such as thrust
margin, timing ordering, and actuator limits.
Each editable scalar declares its ID, display key, unit, type, and local
validation in one `#[config(...)]` attribute. Unannotated fields of a derived
record are configuration children; no per-field section marker is needed.
Factory values belong to the composition root, not the field attributes or a
`Default` implementation. The only handwritten checks are whole-candidate
physical relationships such as thrust margin, timing ordering, actuator
limits, and compatibility with immutable target capabilities.
## Immutable F722 board policy
## Immutable hardware topology
The checked-in F722 boards consume these compiled `BoardConfig` settings before
peripheral initialization:
The checked-in F722 `ImmutableConfig` literals retain the facts that cannot
safely be changed by a scalar transaction:
- IMU model and its fixed body mount (`BodyAligned` or `Yaw180`);
- logical MAVLink port, endpoint IDs, and baud rate;
- vertical-sensor capability and telemetry schedule; and
- maximum optional MAVLink log severity.
- logical MAVLink port and bootstrap baud rate;
- vertical-sensor presence, electrical route, and bootstrap baud rate; and
- output-inhibit and status-LED routes; and
- service-timer frequency.
The platform compares that metadata with the selected `F722BoardRoutes` before
claiming pins, UARTs, DMA, or IRQs. A board declaration therefore cannot claim
an MPU-6000 while selecting the ICM route, select the wrong serial port or
mount, or request a baud rate that cannot carry the fixed link budget. The
physical port, sensor driver, pins, DMA, timers, and calibration are not file
configuration and are not GCS-editable.
The const typestate expression becomes the platform's sole hardware manifest;
there is no second route structure to compare against it. Incomplete or
unsupported IMU, UART, vertical-sensor, status-LED, and output-inhibit
combinations cannot call `telemetry_only()`. Runtime validation still rejects a
service frequency the selected timer cannot provide or a baud rate that cannot
carry the fixed link budget. The physical port, sensor driver, pins, DMA,
timers, and calibration are not file configuration and are not GCS-editable.
F722’s log threshold is immutable board policy. It is deliberately separate
from the SITL profile’s `observability.maximum_log_level`: the F722 image does
not construct a runtime `ConfigManager` or profile store.
Endpoint identity, raw-IMU/heartbeat rates, and MAVLink diagnostic-sink policy
are overlaid from the recovered operational snapshot before their consumers
are constructed.
Pins, sensor models, driver routes, DMA ownership, and calibration remain board
support or dedicated calibration concerns, not UI-selectable strings.
## One CBOR profile format
## Local persistence
There is one unreleased raw profile blob format:
SITL and F722 share the same configuration lifecycle and validation rules, but
use target-appropriate private persistence formats. Neither format is exposed
by the MAVLink configuration protocol.
- it is a CBOR map keyed by the discovered numeric `FieldId`s;
- key `0` is `profile_format = 1` and reserved key `65535` is the 16-byte
`schema_id = "KAPTANPROFILE-01"`;
### SITL CBOR file
SITL stores one raw, bounded CBOR configuration snapshot directly:
- it is a map keyed by the discovered numeric `FieldId`s;
- key `0` is `snapshot_format = 2` and reserved key `65535` is the 16-byte
fingerprint generated from the complete schema;
- the complete blob is at most 4 KiB; and
- malformed, trailing, wrong-format, non-finite, out-of-range, cross-field,
and capability-invalid values are rejected.
The map is exact: it has those 26 keys once each. Unknown, duplicate, missing,
or indefinite-map keys are rejected. That keeps the unreleased file contract
unambiguous instead of silently accepting undeclared extensions.
The map is exact for the compiled schema: every advertised field is present
once. Unknown, duplicate, missing, or indefinite-map keys are rejected. Its
field count therefore follows the SITL composition instead of a global
parameter list.
There is no migration path because no version has shipped. The raw blob is the
future GCS import/export representation. The SITL file is a
private host envelope `[generation, raw-profile-blob]`; it is not an upload
blob and must not be sent to `IMPORT_*` directly.
Saving writes a same-directory temporary file, synchronizes its contents, and
atomically renames it over the configured file. The file contains no generation
or journal envelope; host filesystem replacement is distinct from the F722
flash power-loss protocol.
## Future GCS upload contract
### F722 flash journal
F722 serializes the board's `UserConfig` as a bounded fixed scalar snapshot:
a magic/version header and schema fingerprint followed by the exact field count
and one `(FieldId, value kind, raw scalar bits)` entry per field. It is not
CBOR. Recovery rejects a wrong schema, field set, value kind, scalar encoding,
or complete-model validation result.
The snapshot is wrapped in a private journal record containing its payload
length, CRC-32C, commit marker, and monotonically increasing sequence. Two
independently erasable flash slots hold at most the old and replacement
snapshots. Saving erases the inactive slot, programs and verifies the complete
record, then writes its commit marker last; the previous valid slot remains
recoverable until then. The private sequence only selects the newer record when
both slots are valid. It does not append configuration history.
There is no migration path because no version has shipped.
## GCS protocol contract
The custom dialect is prepared for a first-party GCS without borrowing
`PARAM_*` semantics. A client will:
1. send `KAPTAN_CONFIG_INFO_REQUEST` and require the exact protocol, profile
format, and schema ID in the correlated `KAPTAN_CONFIG_INFO` reply;
2. request descriptors and values as needed;
1. send `KAPTAN_CONFIG_INFO_REQUEST`, require the exact breaking protocol
version `2.0`,
and retain the target-advertised schema ID as the exact identity of that
configuration session;
2. request descriptors, enum options, and values by exact index or field ID;
3. open a transaction with `KAPTAN_CONFIG_BEGIN`;
4. either issue typed setters or begin a raw-CBOR upload with the advertised
schema ID, then send aligned idempotent chunks in any order;
4. issue typed setters for changed fields;
5. validate and commit; and
6. treat `COMMITTED_RESTART_REQUIRED` as persistence only, never live
activation.
An accepted import begin returns a server-issued transfer ID. Every chunk and
finish request echoes it, preventing delayed data from an earlier transfer
from entering the current one. The service validates CRC-32C and the entire
candidate before it replaces the RAM transaction.
The directly wired local UART is the initial operational configuration
transport. The board composition admits CRC-valid unsigned configuration
commands on that link; CRC supplies integrity, not authentication. This does
not authorize a shared or remotely reachable MAVLink network. Use a direct
adapter connection and disconnect every other transmitter from the board's RX
line because the firmware cannot prove electrical exclusivity. Motor outputs
remain physically inhibited in both current F722 images.
This is protocol groundwork, not a claim of active aircraft configuration:
F722 needs authenticated ingress, source binding and replay protection,
journalled target storage, a disarmed/inhibited activation permit, and atomic
consumer rebuild/reset before it may accept any configuration command.
The first accepted `BEGIN` binds the one protocol instance to that MAVLink
source. The binding and its sole most-recent result remain for a ten-second
lease, including after `COMMIT` or `ABORT`, so retrying a lost terminal reply
cannot be displaced by another source. A UART receive fault or lease expiry
clears the binding; another source may then begin. This is bounded direct-link
serialization, not authentication.
A remotely reachable GCS transport still requires MAVLink signing, source/key
binding, persistent anti-replay state, and provisioning/rotation. Those are
future additions to the same target-described configuration protocol, not a
different board or parameter catalog.
+6 -3
View File
@@ -7,7 +7,7 @@ cd firmware
cargo test --workspace --offline
cargo clippy --workspace --all-targets --offline --exclude kaptan-geprc-f722-v2 --exclude kaptan-kakute-f7-mini-v3 -- -D warnings
cargo clippy -p platform-stm32f7 --all-targets --offline -- -D warnings
cargo fmt --check
cargo fmt --all --check
git diff --check
```
@@ -15,8 +15,11 @@ git diff --check
```sh
cd firmware
cargo check -p kaptan-geprc-f722-v2 --release --target thumbv7em-none-eabihf --offline
cargo check -p kaptan-kakute-f7-mini-v3 --release --target thumbv7em-none-eabihf --offline
cargo clippy -p kaptan-geprc-f722-v2 -p kaptan-kakute-f7-mini-v3 \
--target thumbv7em-none-eabihf --offline -- -D warnings
cargo build --release --locked \
-p kaptan-geprc-f722-v2 -p kaptan-kakute-f7-mini-v3 \
--target thumbv7em-none-eabihf --offline
```
## Strict core coverage
+7 -6
View File
@@ -28,12 +28,13 @@ for safety state, actuator inhibit, or typed configuration results.
rule, and protocol error code.
- Runtime driver and link faults reach the F722 MAVLink worker as a
`KAPTAN_ERROR` path, subject to bounded transport admission. The error
payload is `{ flags, path_length, additional_information_length, path[96],
additional_information[64] }`; each length identifies the valid UTF-8
prefix and the optional information field may be empty. A `KaptanError`
derive supplies the type/variant path and normally leaves additional
information empty. `log::error!` uses the Rust log target as the path and
the formatted message as additional information.
payload is `{ level, flags, path_length, additional_information_length,
path[96], additional_information[64] }`; `level` preserves the originating
MAVLink-compatible severity from Emergency through Error, each length
identifies the valid UTF-8 prefix, and the optional information field may be
empty. A `KaptanError` derive supplies the type/variant path and normally
leaves additional information empty. `log::error!` uses the Rust log target
as the path and the formatted message as additional information.
- A rejected configuration operation may enqueue a companion `KAPTAN_ERROR`
whose path is the high-level `ProtocolError` identity. It is intentionally
not correlated and carries no field/rule detail; the matching
+9 -2
View File
@@ -4,6 +4,12 @@
Use the firmware toolchain, Rust 1.90. The firmware workspace declares the
embedded F7 targets and `llvm-tools-preview` in `firmware/rust-toolchain.toml`.
The repository development shell also supplies `dfu-util` and the checked
`mdbook` version:
```sh
nix develop
```
For host development, run:
@@ -20,10 +26,11 @@ target-specific commands in [Development and verification](development.md).
| Path | Responsibility |
| --- | --- |
| `firmware/boards/` | Immutable board declarations and linker layouts. |
| `firmware/platforms/stm32f7/` | Reviewed F722 RTIC platform and peripheral ownership. |
| `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/libs/` | Typed math, estimation, control, safety, protocol, and driver crates. |
| `firmware/tools/sitl/` | Host-only closed-loop Gazebo simulation. |
| `firmware/tools/configurator/` | Board-agnostic loopback web UI and serial bridge. |
| `docs/` | This Markdown documentation site. |
Do not add board-local READMEs. Add a page here instead, then link it from
+6 -6
View File
@@ -15,8 +15,8 @@ Markdown.
- Firmware source and its Rust workspace begin in `firmware/`.
- Board work belongs under [Boards](boards/index.md).
- The currently supported closed-loop simulation is [Host SITL](sitl.md).
- [Configuration](configuration.md) distinguishes the current SITL profile
from immutable F722 board policy.
- [Configuration](configuration.md) explains target-described user settings
and immutable F722 hardware policy.
- [Logs](logs.md) and [Errors](errors.md) describe the custom MAVLink
observability surface and its current safety scope.
- Before submitting changes, follow [Development and verification](development.md).
@@ -29,7 +29,7 @@ only defines the firmware workspace boundary. Keep pages in Markdown, add each
page to `SUMMARY.md`, and place commands beside the assumptions and safety
boundary they depend on.
The current configuration contract is intentionally deployment-specific. A
SITL Quad-X profile is a bounded restart-only CBOR snapshot; F722 hardware
selection remains immutable board policy. Neither is permission for an
operator to substitute runtime values or bypass an immutable safety decision.
The current configuration contract is target-described. A SITL Quad-X profile
and each F722 board expose only values consumed by that compiled target;
physical F722 hardware selection remains immutable board policy. No writable
snapshot can bypass an immutable safety decision.
+44 -17
View File
@@ -9,9 +9,13 @@ log::warn!("range aid stale");
log::error!("sensor transfer failed"); // emitted as KAPTAN_ERROR
```
`kaptan-log` formats once into a fixed bounded buffer, queues locally, and
never sends MAVLink, allocates, blocks, changes flight state, or touches DMA.
The sole MAVLink worker drains admitted records. F722 deliberately does not
`kaptan-log` formats each event once into a fixed bounded buffer and never
sends MAVLink, allocates, blocks, changes flight state, or touches DMA. The
composition fans the event into each enabled sink; the sole MAVLink worker
drains its bounded sink queue. Ordinary text, error paths, and error details
use distinct 160-, 96-, and 64-byte storage types, so a queued record retains
exactly the common diagnostic-envelope capacity and needs no adapter-side
clipping buffer. F722 deliberately does not
link this facade in ISR, DMA, control, actuator, panic, hard-fault, or shutdown
contexts; those paths use static fault mailboxes that the worker turns into
messages later.
@@ -25,7 +29,7 @@ orderly-shutdown observations:
```text
KAPTAN_LOG { level, flags, message_length, message[160] }
KAPTAN_ERROR {
flags, path_length, additional_information_length,
level, flags, path_length, additional_information_length,
path[96], additional_information[64]
}
KAPTAN_SHUTDOWN { reason }
@@ -38,31 +42,54 @@ fixed arrays into UTF-8 prefixes. Generated MAVLink framing alone does not
enforce these semantic length fields. `KAPTAN_LOG` displays its text at the
MAVLink severity in `level`; `TRACE` is encoded as `DEBUG` plus the `TRACE`
flag.
`KAPTAN_ERROR` is error-only and accepts only `TRUNCATED` and `COALESCED`; a
`TRACE` bit is never emitted there. Unknown future flag bits must be ignored.
`KAPTAN_ERROR.level` preserves the originating MAVLink-compatible severity from
Emergency through Error. The error channel accepts only `TRUNCATED` and
`COALESCED`; a `TRACE` bit is never emitted there. Unknown future flag bits
must be ignored. `COALESCED` means that one or more later observations from
the same source mailbox were folded while the retained first error was pending;
it does not claim that their paths or details were identical.
The derived error path is intentionally not a numeric error catalog; renaming
its Rust module/type/variant changes that identity. Error details requiring
machine action stay in their owning protocol, notably `KAPTAN_CONFIG_RESULT`.
`KAPTAN_SHUTDOWN` has only its typed reason and is valid only after an orderly
output-inhibited completion. Delivery is bounded and lossy under link pressure;
heartbeat and safety telemetry use separate paths.
heartbeat and safety telemetry use separate paths. Logs have no publication
rate setting and are not periodic streams. A retained event leaves the
MAVLink sink queue once the lower transport admits it; temporary queue pressure
only defers that same event.
## Filtering
The SITL profile’s restart-only `observability.maximum_log_level` accepts
MAVLink severities Error (3) through Debug (7). For example, selecting Warning
permits warning records while suppressing info and debug records. Error-level
records always use `KAPTAN_ERROR` and remain non-filterable. F722 uses its
immutable board log threshold instead, because it has no active runtime
profile.
The restart-only `mavlink.logging.enabled` field controls the MAVLink
diagnostic sink independently from future sinks such as a blackbox.
`mavlink.logging.maximum_severity` accepts the complete MAVLink severity range,
Emergency (0) through Debug (7). For example, selecting Warning permits warning
and more severe events while suppressing notice, info, and debug. Disabling the
sink suppresses both `KAPTAN_LOG` and `KAPTAN_ERROR` on MAVLink without
suppressing events from another sink. F722 uses the validated policy recovered
at boot, falling back to its board factory when the journal is empty or
invalid.
Severity remains a typed `Severity` value inside the board and runtime. Generic
schema discovery exposes its named choices one at a time to the configurator,
so the UI does not carry a field-key-specific severity list; numeric conversion
exists only at the generic configuration transport and snapshot boundary. The MAVLink `level`
field intentionally remains `uint8_t`: this generator represents XML enums as
four-byte Rust values, so attaching an XML enum would widen the generated
payload type. The adapter performs the checked one-byte conversion at that
unavoidable wire boundary instead.
Cargo `log/max_level_*` features compile calls away before the facade sees
them; they are not a supported filtering mechanism. The build policy keeps
that decision out of workspace dependencies.
The tested `SitlConfigRuntime` composition proves `log!` reaches a generated
The SITL facade ingress is transport-neutral and unfiltered. The tested
`SitlConfigRuntime` composition applies the configured MAVLink policy when
fanning an event into that sink and proves `log!` reaches a generated
`KAPTAN_LOG` payload, and rejected configuration replies enqueue a generated
`KAPTAN_ERROR` companion without replacing their authoritative result. `kaptan-sitl
serve` does not yet attach a config/diagnostic MAVLink link, and F722 emits only
outbound telemetry/diagnostic frames while rejecting all inbound authority.
`KAPTAN_ERROR` companion without replacing their authoritative result.
`kaptan-sitl serve` does not yet attach a config/diagnostic MAVLink link. F722
images accept only the unsigned configuration service on their directly wired
local UART; that direct-wire assumption does not extend to a shared or remote
MAVLink network.
+41 -12
View File
@@ -1,30 +1,59 @@
# MAVLink and telemetry
Kaptan emits telemetry using MAVLink 2 framing. The production receiver does
not promote inbound MAVLink frames to authority records:
Kaptan emits telemetry using MAVLink 2 framing. Inbound processing is narrowly
scoped:
- MAVLink 1 is discarded.
- CRC-invalid MAVLink 2 frames are discarded.
- Signed frames are discarded because no signature verifier or persistent
replay state exists.
- Unsigned MAVLink 2 frames are also discarded; a V2 CRC detects corruption,
not authentication.
- CRC-valid unsigned MAVLink 2 frames are accepted only for the exact-target
Kaptan configuration service on the directly wired local UART. Other inbound
messages do not become authority.
`TODO(signing)`: any future inbound authority requires verified MAVLink 2
signing, configured source/target binding, persistent anti-replay state, and a
provisioning/key-rotation policy as one reviewed feature.
`TODO(signing)`: any future shared or remote inbound authority requires verified
MAVLink 2 signing, configured source/target binding, persistent anti-replay
state, and a provisioning/key-rotation policy as one reviewed feature.
The local UART is Kaptan's initial operational configuration transport, not an
authenticated remote-control channel. Any transmitter electrically connected
to that RX line can issue configuration commands, so connect the USB-UART
adapter directly and disconnect other transmitters. 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.
Configuration protocol `2.0` admits one source principal from `BEGIN` until
its ten-second lease expires or the UART receive path faults. That lease also
covers the post-commit retry window, preventing another source from evicting a
lost result. It serializes the direct link but does not authenticate its peer.
Telemetry transmission uses bounded frame classes. A critical frame may
replace pending best-effort traffic, but never the DMA-owned active buffer.
Completion retries preserve ordering and retain a transiently busy frame for a
later service turn.
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
frequency in one-second windows. Selecting a message reveals the latest decoded
fields, including human-readable `KAPTAN_ERROR` text. This observation path
retains one latest value per bounded aggregate rather than packet history and
grants no additional command authority.
Diagnostic events also feed a separate bounded Runtime logs view at
`http://127.0.0.1:4861`. That view retains individual `KAPTAN_LOG` and
`KAPTAN_ERROR` events in receive order with local receive time, severity, and
readable text. Packet inspection remains a latest-value view; it is not used as
log history.
The custom Kaptan definitions are aggregated by `definitions/kaptan.xml` from
`kaptan/config.xml`, `kaptan/log.xml`, `kaptan/error.xml`, and
`kaptan/shutdown.xml`.
`KAPTAN_LOG` is an outbound human-readable four-field record. `KAPTAN_ERROR`
is its separate bounded error channel, while `KAPTAN_SHUTDOWN` represents only
a completed orderly output-inhibited shutdown. Configuration messages are GCS
groundwork only: the current F722 receiver rejects every inbound frame before
configuration dispatch. See [Configuration](configuration.md), [Logs](logs.md),
and [Errors](errors.md) for the exact contracts.
is its separate bounded error channel and preserves the originating error
severity, while `KAPTAN_SHUTDOWN` represents only
a completed orderly output-inhibited shutdown. Configuration messages back the
current target-described web configurator and the future signed GCS. See
[Configuration](configuration.md), [Logs](logs.md), and [Errors](errors.md) for
the exact contracts.
+15 -11
View File
@@ -4,15 +4,15 @@ This page collects stable limits and points to the detailed contracts.
| Surface | Current contract |
| --- | --- |
| Flight profile | One 4 KiB-or-smaller raw CBOR blob, `profile_format = 1`, schema ID `KAPTANPROFILE-01`. |
| SITL file | Private `[generation, raw-profile-blob]` envelope; not a MAVLink upload blob. |
| Config transaction | One owner-bound RAM transaction; typed writes or 192-byte idempotent CBOR chunks; commit is restart-required. |
| Config discovery | Correlated `KAPTAN_CONFIG_INFO_REQUEST` / `KAPTAN_CONFIG_INFO`, then descriptor/value requests. |
| Diagnostics | `KAPTAN_LOG` (51020) for ordinary severity/text; `KAPTAN_ERROR` (51021) for a bounded `{ path[96], additional_information[64] }` error; and `KAPTAN_SHUTDOWN` (51022) for completed orderly output-inhibited shutdowns. |
| F722 configuration | Immutable compiled board policy only; inbound MAVLink configuration remains disabled. |
| SITL file | One raw bounded CBOR configuration snapshot, replaced through a same-directory temporary file and atomic rename. |
| F722 persistence | One bounded fixed scalar snapshot inside each slot of a power-loss-safe two-slot flash journal. |
| Config transaction | One owner-bound RAM transaction with typed scalar writes; commit is restart-required. |
| Config discovery | Correlated `KAPTAN_CONFIG_INFO_REQUEST` / `KAPTAN_CONFIG_INFO`, then exact-index descriptor/enum-option and field-value requests. |
| Diagnostics | `KAPTAN_LOG` (51020) for ordinary severity/text; `KAPTAN_ERROR` (51021) for a bounded `{ level, path[96], additional_information[64] }` error; and `KAPTAN_SHUTDOWN` (51022) for completed orderly output-inhibited shutdowns. |
| F722 configuration | Target-described fields and board factory defaults; the current local transport is a directly wired unsigned UART with motor outputs physically inhibited. |
Read [Configuration](configuration.md) for the field/consumer matrix and GCS
upload sequence, [Logs](logs.md) for receiver decoding and filtering, and
transaction sequence, [Logs](logs.md) for receiver decoding and filtering, and
[Errors](errors.md) for error propagation and panic scope.
## Verification
@@ -23,9 +23,13 @@ Before review, run:
cd firmware
cargo fmt --all --check
cargo test --workspace --locked --offline
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo clippy --workspace --all-targets \
--exclude kaptan-kakute-f7-mini-v3 \
--exclude kaptan-geprc-f722-v2 --locked --offline -- -D warnings
cargo clippy -p kaptan-kakute-f7-mini-v3 -p kaptan-geprc-f722-v2 \
--target thumbv7em-none-eabihf --locked --offline -- -D warnings
```
The embedded no-std binaries require their target-specific build setup; the
host workspace test suite continues to assert that the telemetry F722 image
has no inbound authority or actuator path.
The target-specific lint verifies the local configuration receiver and journal
path. The host suite separately asserts that configuration ingress cannot reach
an actuator path.
+13 -4
View File
@@ -1,14 +1,23 @@
# Safety boundary
Every checked-in F722 image inhibits PB0, PB1, PB4, and PB5 before normal
board initialization. The interlock configures each pin as a low, push-pull
GPIO output; it runs before clocks, board routing, UART setup, or RTIC tasks.
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.
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
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;
- MAVLink input has no production authority path;
- MAVLink input is accepted only by the configuration service and has no
actuator path;
- a future verified arm command is still acknowledged as unsupported.
The protocol reference crate remains in the workspace only as a framing and
+4 -3
View File
@@ -46,10 +46,11 @@ kaptan-sitl serve --config kaptan-sitl.cbor
```
The file selects the restart-only profile used to build the estimator,
controller, allocator envelope, safety supervisor, and optional log filter at
process start. It is not the raw GCS upload blob. `kaptan-sitl serve` retains
controller, allocator envelope, safety supervisor, and MAVLink log-sink policy at
process start. It is local persistence and never appears on MAVLink.
`kaptan-sitl serve` retains
the central manager for that boot but does not yet open a MAVLink config/log
endpoint; see [Configuration](configuration.md) for the exact upload groundwork
endpoint; see [Configuration](configuration.md) for the exact transaction contract
and safety boundary.
## Limits