From 97acb2a2d78e108b7db88b2807d50df80d4c41b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Sat, 22 Aug 2026 18:41:07 +0300 Subject: [PATCH] docs: document board configuration workflow --- docs/src/architecture.md | 73 ++++---- docs/src/boards/geprc-f722-v2.md | 71 ++++++-- docs/src/boards/kakute-f7-mini-v3.md | 51 ++++-- docs/src/configuration.md | 239 ++++++++++++++++++++------- docs/src/development.md | 9 +- docs/src/errors.md | 13 +- docs/src/getting-started.md | 11 +- docs/src/index.md | 12 +- docs/src/logs.md | 61 +++++-- docs/src/mavlink.md | 53 ++++-- docs/src/reference.md | 26 +-- docs/src/safety-boundary.md | 17 +- docs/src/sitl.md | 7 +- 13 files changed, 470 insertions(+), 173 deletions(-) diff --git a/docs/src/architecture.md b/docs/src/architecture.md index ebfb7a1..bfdb8c0 100644 --- a/docs/src/architecture.md +++ b/docs/src/architecture.md @@ -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. diff --git a/docs/src/boards/geprc-f722-v2.md b/docs/src/boards/geprc-f722-v2.md index 4206621..f672cea 100644 --- a/docs/src/boards/geprc-f722-v2.md +++ b/docs/src/boards/geprc-f722-v2.md @@ -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/ --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. diff --git a/docs/src/boards/kakute-f7-mini-v3.md b/docs/src/boards/kakute-f7-mini-v3.md index d8074e8..8c99ca1 100644 --- a/docs/src/boards/kakute-f7-mini-v3.md +++ b/docs/src/boards/kakute-f7-mini-v3.md @@ -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/ --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. diff --git a/docs/src/configuration.md b/docs/src/configuration.md index a8d7ff1..df25067 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -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/ --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. diff --git a/docs/src/development.md b/docs/src/development.md index bbfce54..76b35f7 100644 --- a/docs/src/development.md +++ b/docs/src/development.md @@ -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 diff --git a/docs/src/errors.md b/docs/src/errors.md index b89f72d..feaf193 100644 --- a/docs/src/errors.md +++ b/docs/src/errors.md @@ -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 diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index c94babe..58a6a67 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -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 diff --git a/docs/src/index.md b/docs/src/index.md index f5813fd..4e28879 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -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. diff --git a/docs/src/logs.md b/docs/src/logs.md index 95ff01a..ab5878f 100644 --- a/docs/src/logs.md +++ b/docs/src/logs.md @@ -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. diff --git a/docs/src/mavlink.md b/docs/src/mavlink.md index 4023913..e8e89ef 100644 --- a/docs/src/mavlink.md +++ b/docs/src/mavlink.md @@ -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. diff --git a/docs/src/reference.md b/docs/src/reference.md index 8626173..6453ddd 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -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. diff --git a/docs/src/safety-boundary.md b/docs/src/safety-boundary.md index 884f530..89ed2b5 100644 --- a/docs/src/safety-boundary.md +++ b/docs/src/safety-boundary.md @@ -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 diff --git a/docs/src/sitl.md b/docs/src/sitl.md index df7c085..6ed9291 100644 --- a/docs/src/sitl.md +++ b/docs/src/sitl.md @@ -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