docs: document the kaptan-native protocol

This commit is contained in:
Onur Özkan committed 2026-08-24 00:56:32 +03:00
1 parent 5f91465e66
commit 4bc98adbc6
10 files changed
+314 -307

No files matched your search

+48 -53
View File
@@ -27,73 +27,68 @@ The data channel owns rate selection, mailbox consumption, message encoding,
and bounded TX classification. Board code supplies physical routes, target and bounded TX classification. Board code supplies physical routes, target
schema, and factory values; it does not receive generic output authority. schema, and factory values; it does not receive generic output authority.
Configuration and runtime observability are separate, one-way planes: Configuration and runtime observability are separate planes:
```text ```text
Kaptan GCS custom MAVLink V2 messages FILE_CONTROL / FILE_DATA
│ (future: verified signing, source binding, replay state) │
▼ ▼
kaptan-config-mavlink ──► state-only ConfigProtocol kaptan-file-mavlink ──► virtual FileProvider
│ borrows
kaptan-flight-runtime
├── one ConfigManager ──► kaptan-config ──► flight schema/store
└── one bounded MAVLink log-sink queue
│ │
kaptan-diagnostics-mavlink payload adapter KCFG importer/exporter
│ │
platform-owned sole encoder / DMA TX link ConfigManager ──► private store
kaptan-log / kaptan-err-reportable
│
▼
kaptan-diagnostics-mavlink ──► platform-owned encoder / DMA TX
``` ```
The generated dialect is intentionally custom rather than a `PARAM_*` or QGC The file dialect is intentionally Kaptan-native rather than standard MAVLink
adapter. The core uses numeric field IDs and typed scalar values; descriptor FTP, `PARAM_*`, or a QGC adapter. Its two messages know only bounded paths,
text is metadata, not a wire identity. Discovery/value reads, exact-index bytes, positions, checksums, transfer ownership, and retries. The configuration
descriptor and enum-option requests, and structured results are custom-dialect provider maps `/config/user.kcfg` to a canonical self-describing artifact.
records. Runtime logs use one bounded text message with a conventional level KCFG carries generated metadata and a complete committed value set; the target
and flags. Remote writes use only schema-described typed scalar setters. The core streams it without retaining a whole-file buffer. Saving imports and validates
owns one RAM transaction and the distinction one complete replacement candidate. The core distinguishes only immutable
between boot-active, committed, and editable candidate snapshots. In the boot-active values from the latest committed values. A save cannot mutate
current P1 policy a commit is restart-only. It cannot mutate controller or controller or estimator state in flight.
estimator state in flight.
Target-owned stores may serialize one bounded complete CBOR snapshot. SITL Target-owned stores use private persistence formats. SITL atomically replaces a
supplies the host file adapter and atomically replaces that snapshot through a bounded CBOR snapshot. F722 alternates compact `KCSP` snapshots between two
same-directory temporary file. The serialization is not configuration state erase sectors, retaining only the old and replacement records across the commit
and is never exposed by the MAVLink protocol. boundary. Neither format is exposed as the public KCFG file.
Motor allocation/mixer topology is immutable compiled vehicle capability data, Motor allocation/mixer topology is immutable compiled vehicle capability data,
never a scalar field or CBOR candidate member. F722 now recovers operational never a scalar field or persistence member. F722 recovers operational settings
settings from a generic two-slot journal before constructing consumers. Its before constructing consumers. Its directly wired local UART accepts only
directly wired local UART accepts only exact-target, CRC-valid configuration exact-target, CRC-valid Kaptan file requests while physical outputs remain
commands while physical outputs remain inhibited. CRC is integrity-only; a inhibited. CRC is integrity-only; a shared or remote GCS still needs production
shared or remote GCS still needs production authentication, durable audit authentication, source binding, and persistent anti-replay state. Disarmed
logging, source binding, and persistent anti-replay state. Disarmed activation activation and flight-consumer reconstruction remain separate reviewed work.
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 The MAVLink dialect root directly includes one-message XML leaves under
option request. The first accepted transaction binds one source principal `publish/` and `transfer/`. The `exchange/` contract remains empty until a
through a bounded post-terminal replay lease; foreign traffic cannot evict a current consumer justifies a message. There are no category index XML files or
lost result before that lease expires. separate enum catalogs. The build watches the definitions tree and generates
one `generated::kaptan` module; leaves are never separately generated Rust
The MAVLink dialect root includes `kaptan/config.xml`, `kaptan/log.xml`, and modules. `kaptan-log` itself has
`kaptan/error.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, or filesystem dependency; its sole no MAVLink, platform, time, allocation, or filesystem dependency; its sole
configuration coupling is compile-time enum metadata for `LevelFilter`. configuration coupling is compile-time enum metadata for `LevelFilter`.
`kaptan-err-reportable` independently owns explicitly assigned reportable-error `kaptan-err-reportable` independently owns explicitly assigned reportable-error
identity and a bounded nonblocking ingress; it has identity and a bounded nonblocking ingress; it has no logging, MAVLink,
no logging, MAVLink, platform, filesystem, or clock dependency. Event creation platform, filesystem, or clock dependency. Event creation is independent from
is independent from sink filtering, allowing a future blackbox and MAVLink to sink filtering, allowing a future blackbox and MAVLink to select different
select different policies. The platform drains reportable errors before its bounded policies. The platform drains reportable errors before its bounded critical and
critical and best-effort log queues and removes a pending record only after TX best-effort log queues and removes a pending record only after TX admission.
admission. `KAPTAN_LOG` carries one conventional level plus one bounded text `LOG` carries one conventional level plus one bounded text field; `ERROR`
field; `KAPTAN_ERROR` carries an explicitly assigned 16-bit ID and the bounded carries an explicitly assigned 16-bit ID and the bounded `file:line` of the
`file:line` of the reporting point. Its reporting point. Its message kind already identifies a reportable firmware
message kind already identifies a reportable firmware error. Heartbeat and safety error. Kaptan-native heartbeat and sensor publications remain separate from
telemetry remain separate from diagnostics. diagnostics.
See [Configuration](configuration.md) for the deployment matrix and GCS transaction See [Configuration](configuration.md) for KCFG and the file-transfer contract,
contract, [Logs](logs.md) for the receiver contract, and [Errors](errors.md) [Logs](logs.md) for the receiver contract, and [Errors](errors.md)
for derived-error and failure-reporting scope. for derived-error and failure-reporting scope.
## Ownership rules ## Ownership rules
+2 -2
View File
@@ -5,7 +5,7 @@ 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) 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`. labels the external UART2 pads `T2` and `R2`.
Its board-local `UserConfig` factory selects endpoint `1/1`, raw-IMU and Its board-local `UserConfig` factory selects endpoint `1/1`, IMU and
heartbeat publication rates, and an enabled MAVLink diagnostic sink with an heartbeat publication rates, and an enabled MAVLink diagnostic sink with an
`Info` level filter. The web configurator may replace those `Info` level filter. The web configurator may replace those
target-advertised values in the journal. Range target-advertised values in the journal. Range
@@ -59,7 +59,7 @@ Then run:
```sh ```sh
cargo run --locked -p kaptan-configurator -- \ cargo run --locked -p kaptan-configurator -- \
--serial /dev/serial/by-id/<adapter> --baud 115200 --source-system 255 --serial /dev/serial/by-id/<adapter> --baud 115200
``` ```
Open `http://127.0.0.1:4861`, Save the discovered fields, and manually Open `http://127.0.0.1:4861`, Save the discovered fields, and manually
+2 -2
View File
@@ -5,7 +5,7 @@ MAVLink telemetry on USART1, and the MTF01 range/optical-flow route on UART4.
The checked-in manufacturer datasheet is The checked-in manufacturer datasheet is
`firmware/boards/kakute-f7-mini-v3/datasheet.pdf`. `firmware/boards/kakute-f7-mini-v3/datasheet.pdf`.
Its board-local `UserConfig` factory selects endpoint `1/1`, raw-IMU, range, Its board-local `UserConfig` factory selects endpoint `1/1`, IMU, range,
optical-flow and heartbeat publication rates, and an enabled MAVLink diagnostic optical-flow and heartbeat publication rates, and an enabled MAVLink diagnostic
sink with an `Info` level filter. The web configurator may replace those sink with an `Info` level filter. The web configurator may replace those
target-advertised values in the journal. Its `ImmutableConfig` keeps the target-advertised values in the journal. Its `ImmutableConfig` keeps the
@@ -64,7 +64,7 @@ Launch the target-described web UI:
```sh ```sh
cargo run --locked -p kaptan-configurator -- \ cargo run --locked -p kaptan-configurator -- \
--serial /dev/serial/by-id/<adapter> --baud 115200 --source-system 255 --serial /dev/serial/by-id/<adapter> --baud 115200
``` ```
Open `http://127.0.0.1:4861`, edit the discovered fields, press Save, and Open `http://127.0.0.1:4861`, edit the discovered fields, press Save, and
+141 -177
View File
@@ -1,18 +1,18 @@
# Configuration # Configuration
Kaptan has one generic configuration lifecycle in `kaptan-config`: descriptors, Kaptan has one generic configuration model in `kaptan-config`. Target schemas
typed writes, transactions, validation, scalar snapshots, and a declare typed fields and validation once; derive code generates the stable
power-loss-safe two-slot journal. Target models declare fields once; derive code field registry and schema fingerprint. Boards supply the sole erased-flash
generates the registry consumed by both the protocol and the browser. There is factory values. The browser contains no board, driver, sensor, schema, or
no board, driver, sensor, schema, or parameter catalog in the configurator. parameter catalog.
## Current deployment matrix ## Current deployment matrix
| Deployment | Configuration consumed today | Remote writes | | Deployment | Configuration consumed today | Remote writes |
| --- | --- | --- | | --- | --- | --- |
| `kaptan-sitl serve --config FILE.cbor` | One validated Quad-X configuration at process construction. | No live MAVLink endpoint is attached yet. | | `kaptan-sitl serve --config FILE.cbor` | One validated Quad-X configuration at process construction. | No live file endpoint is attached yet. |
| `kaptan-sitl serve` | The validated factory Quad-X configuration. | No persistent store; commits are rejected. | | `kaptan-sitl serve` | The validated factory Quad-X configuration. | No persistent store. |
| STM32F722 image | Board factory settings, or the newest valid flash-journal snapshot. | Enabled on the directly wired local UART while motor pins remain inhibited. | | STM32F722 image | Board factory settings, or the newest valid flash-journal snapshot. | `/config/user.kcfg` on the directly wired local UART while motor pins remain inhibited. |
## Web configurator ## Web configurator
@@ -21,208 +21,172 @@ USB-UART adapter, then run from `firmware`:
```sh ```sh
cargo run --locked -p kaptan-configurator -- \ cargo run --locked -p kaptan-configurator -- \
--serial /dev/serial/by-id/<adapter> --baud 115200 --source-system 255 --serial /dev/serial/by-id/<adapter> --baud 115200
``` ```
Open `http://127.0.0.1:4861`. Edit discovered fields and press `--source-system` is the configurator's exact MAVLink source identity. Every
Save. Save commits the complete candidate to the inactive flash slot and writes simultaneously connected GCS must use a different nonzero system ID so transfer
the commit marker last. The active runtime does not change. Once Save completes, ownership and retries cannot alias another client.
manually power-cycle the controller to recover the committed journal and
activate it. Open `http://127.0.0.1:4861`. On discovery, the configurator downloads the
complete `/config/user.kcfg` artifact, validates it, and renders the metadata
and values contained in that file. Save produces one complete edited KCFG
artifact and uploads it. The target validates the complete candidate before
atomically persisting it. The boot-active runtime does not change; manually
power-cycle the controller after a successful save when the UI reports that a
restart is required.
Saving erases one 128-KiB STM32F7 journal sector synchronously. Expect roughly Saving erases one 128-KiB STM32F7 journal sector synchronously. Expect roughly
one second without telemetry or sensor servicing while the target flash 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 controller is busy; the host gives the final close a separate five-second
vehicle unpowered except for setup power and do not disconnect it during 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 The artifact carries generated field IDs, native scalar kinds, display keys,
advertised descriptors, and reads/writes values only by stable `FieldId`. A new units, bounds, enum choices, factory values, committed values, schema identity,
board or field needs no host release. The board package remains the composition and the derived restart-required state. A new board or field therefore needs no
root for erased-flash defaults; the platform never switches on a board name. configurator release. `u8`, `u16`, and enum-backed values retain their native
Enum-backed fields advertise their valid value/label pairs through exact width rather than being widened at the configuration boundary.
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 The Incoming MAVLink table groups live, CRC-valid Kaptan packets by source and
source and message ID and shows the measured receive frequency and total count. message ID and shows receive frequency and total count. Selecting a message
Names come from the generated MAVLink dialect decoders; the browser contains no watches its latest decoded value. The separate Runtime logs view retains
message catalog. Selecting a message name shows the latest decoded field values; individual `LOG` and `ERROR` events in receive order, with local receive time,
the diagnostic projection renders the `KAPTAN_ERROR` ID and reporting level, and readable text.
`file:line`. 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 ## Board ownership
`KAPTAN_ERROR` events in receive order. Each row shows local receive time,
decoded level, 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. A visible `TRUNCATED` badge marks bounded `KAPTAN_LOG`
text. 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 Each F722 board declares one `UserConfig` root for the settings consumed by its
current boards advertise: compiled driver graph. The explicit `FACTORY_USER_CONFIG` literal is the sole
erased-flash value. There is no `Default` implementation and no platform-wide
union of board schemas.
- MAVLink system and component identity; Both current boards expose MAVLink identity, IMU and heartbeat publication
- raw-IMU and heartbeat publication rates; and rates, and the MAVLink log level filter. Kakute also exposes range and
- the MAVLink diagnostic sink's Off/Error/Warn/Info/Debug/Trace level filter. optical-flow rates because its immutable hardware manifest contains an MTF01.
Those fields are absent from GEPRC rather than present-but-disabled.
The Kakute image also advertises range and optical-flow publication rates The adjacent `IMMUTABLE_CONFIG` literal remains the sole source of facts that
because its immutable route includes an MTF01. The GEPRC image declares no cannot safely be edited as scalar configuration:
vertical sensor, so those fields are absent rather than present-but-disabled.
The UART baud is the immutable bootstrap link for this first configuration - output-inhibit and status-LED routes;
transport. Pass it explicitly to the host with `--baud`. - IMU model, bus, interrupt route, mount, and driver setup;
- MAVLink and optional vertical-sensor routes and bootstrap baud rates;
- service-timer frequency; and
- compiled image role.
Each board declares one `UserConfig` root for its compiled driver graph. That The F722 platform consumes stable semantic field IDs through the generic
root composes board-local `MavlinkConfig` and `TelemetryConfig` records; derive `Config::field_value` boundary. It does not know a board section, Rust field,
code generates recursive routing, validation, and the schema fingerprint. The or aggregate shape. Whole-candidate validation checks both generated local
board's explicit `FACTORY_USER_CONFIG` literal supplies erased-flash values. rules and relationships such as publication budget and immutable hardware
Board-native IDs and publication rates retain their narrow `u8` representation. capabilities.
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 ## KCFG artifact
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 KCFG is the public, canonical import/export representation. It is independent
typestate expression visibly selects the inhibited output pins, IMU route, of MAVLink and of the private storage format. A file contains:
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, - `KCFG` magic and one format version;
controller, safety supervisor, allocator envelope, or MAVLink diagnostic sink: - generated 16-byte schema identity and exact field count;
- one canonical self-describing record for every field; and
- a big-endian CRC-32C trailer covering the preceding artifact bytes.
- prearm dwell; The trailer uses network byte order deliberately. The generic file dialect
- IMU, local-aiding, and command freshness/timing; also calculates CRC-32C over every complete file byte; network-order storage
- position/velocity aiding variance; keeps that independent transfer checksum content-specific instead of producing
- outer-loop gains and acceleration limits; the fixed residue obtained by appending a reflected CRC in little-endian order.
- mass, collective-thrust, and tilt limits;
- rate PID gains and limits;
- actuator minimum, maximum, and slew limits; and
- the restart-only MAVLink diagnostic-sink level filter.
The fixed Quad-X mixer is intentionally not configurable. It is reviewed An imported file must match the target-generated schema, descriptor metadata,
capability data, not a user-editable array. factory values, field order, native scalar kinds, and exact length. Unknown,
duplicate, missing, malformed, noncanonical, out-of-range, and relationally
invalid values are rejected. The target stages only the typed candidate and a
small parser state; it never buffers the complete public file. Exports are also
generated by range directly from compiled metadata and current committed
values. One small export plan containing the exact length and checksums is
prepared when a read opens and reused for every requested range.
Each editable scalar declares its ID, display key, unit, type, and local Schema identity is required because a complete file can otherwise be validly
validation in one `#[config(...)]` attribute. Unannotated fields of a derived encoded while belonging to another firmware schema. It is generated from the
record are configuration children; no per-field section marker is needed. schema and is not a hand-maintained board ID.
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 hardware topology The artifact header's restart bit describes the committed values in an export.
It is not an instruction accepted from an upload: after persistence the target
derives restart state by comparing committed values with immutable boot-active
values.
The checked-in F722 `ImmutableConfig` literals retain the facts that cannot ## Generic file dialect
safely be changed by a scalar transaction:
- IMU model and its fixed body mount (`BodyAligned` or `Yaw180`); Kaptan uses two custom messages rather than standard MAVLink FTP or a family of
- logical MAVLink port and bootstrap baud rate; configuration-specific requests:
- vertical-sensor presence, electrical route, and bootstrap baud rate; and
- output-inhibit and status-LED routes; and
- service-timer frequency.
The const typestate expression becomes the platform's sole hardware manifest; ```text
there is no second route structure to compare against it. Incomplete or FILE_CONTROL
unsupported IMU, UART, vertical-sensor, status-LED, and output-inhibit FILE_DATA
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.
Endpoint identity, raw-IMU/heartbeat rates, and MAVLink diagnostic-sink policy `FILE_CONTROL` opens, closes, aborts, and acknowledges a transfer. It
are overlaid from the recovered operational snapshot before their consumers carries a client-selected nonzero `u16` transfer ID, exact target, position,
are constructed. complete file size, CRC-32C, operation/status, and at most 64 bytes of path or
Pins, sensor models, driver routes, DMA ownership, and calibration remain board result detail. A source does not reuse an ID during the ten-second replay lease.
support or dedicated calibration concerns, not UI-selectable strings. A new ID from the same source may supersede an open only before the first byte
has advanced; a progressed abandoned transfer remains pinned until lease expiry.
`FILE_DATA` uses the full MAVLink payload budget for a
246-byte chunk. The logical final-chunk length follows from the opened file
size, so no redundant length field is sent with every data packet. An upload
request carries status `UNSPECIFIED`; a read-data response carries status `OK`.
All failures remain typed `FILE_CONTROL` responses.
## Local persistence The service owns one bounded stop-and-wait transfer. Chunks are sequential;
immediate retries are replay-safe, conflicting retries are rejected, and stale
transfer IDs cannot mutate a later transfer. A receive fault or bounded lease
expiry aborts staged provider state. The generic layer knows only paths, bytes,
positions, checksums, ownership, and retry state. It does not parse KCFG or
contain configuration policy.
SITL and F722 share the same configuration lifecycle and validation rules, but The current provider exposes exactly `/config/user.kcfg`. A successful write
use target-appropriate private persistence formats. Neither format is exposed close has generic status `OK` and content beginning with the authoritative
by the MAVLink configuration protocol. seven-byte KCFG result prefix: restart flag, primary field, related field, and
validation-rule code. Its exact layout is `flags:u8 | primary_field:u16 LE |
related_field:u16 LE | validation_rule:u16 LE`; flag bit 0, when set, means a
restart is required, and every other flag bit is reserved zero. The field
sentinel is `0xffff`; rule zero means no rule.
KCFG `CONTENT_REJECTED` responses use the same prefix and may append bounded
UTF-8 detail. The configurator rejects a successful close with a missing,
malformed, or failure-bearing prefix. The file layer passes these bytes through
without interpreting them.
### SITL CBOR file The directly wired local UART is the initial transport. CRC validates framing
integrity, not peer authenticity. Disconnect other transmitters from the board
RX line. A shared or remote GCS still requires signing, source/key binding,
persistent anti-replay state, and provisioning before this inbound service can
be exposed safely.
SITL stores one raw, bounded CBOR configuration snapshot directly: ## Private persistence
- it is a map keyed by the discovered numeric `FieldId`s; SITL and F722 use target-appropriate private persistence. Neither private
- key `0` is `snapshot_format = 2` and reserved key `65535` is the 16-byte format is the public KCFG artifact.
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 for the compiled schema: every advertised field is present ### SITL CBOR
once. Unknown, duplicate, missing, or indefinite-map keys are rejected. Its
field count therefore follows the SITL composition instead of a global
parameter list.
Saving writes a same-directory temporary file, synchronizes its contents, and SITL stores one bounded CBOR snapshot keyed by stable `FieldId`. Saving writes
atomically renames it over the configured file. The file contains no generation a same-directory temporary file, synchronizes it, and atomically renames it
or journal envelope; host filesystem replacement is distinct from the F722 over the configured file. The exact map rejects unknown, duplicate, and missing
flash power-loss protocol. fields, wrong schema or format, trailing input, invalid scalar encodings, and
complete-model validation failures.
### F722 flash journal ### F722 flash journal
F722 serializes the board's `UserConfig` as a bounded fixed scalar snapshot: F722 stores a compact private `KCSP` scalar snapshot inside a two-slot journal.
a magic/version header and schema fingerprint followed by the exact field count Each entry stores its stable field ID, scalar kind, and value at that kind's
and one `(FieldId, value kind, raw scalar bits)` entry per field. It is not native width; an `u8` is one byte rather than a widened integer slot. Each
CBOR. Recovery rejects a wrong schema, field set, value kind, scalar encoding, journal record includes payload length, CRC-32C, commit marker, and a private
or complete-model validation result. monotonic sequence. Saving erases the inactive slot, programs and verifies the
complete replacement, and writes the commit marker last. The previous valid
slot remains recoverable across power loss until that point.
The snapshot is wrapped in a private journal record containing its payload Only two complete records can exist: the old committed value and its
length, CRC-32C, commit marker, and monotonically increasing sequence. Two replacement. Saves do not append history. The sequence is storage metadata used
independently erasable flash slots hold at most the old and replacement only to select the newer valid slot if both slots survive; it is never exported
snapshots. Saving erases the inactive slot, programs and verifies the complete through KCFG or shown in the UI.
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. There is no migration path because no configuration 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`, 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. issue typed setters for changed fields;
5. validate and commit; and
6. treat `COMMITTED_RESTART_REQUIRED` as persistence only, never live
activation.
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.
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.
+11 -10
View File
@@ -53,30 +53,31 @@ backpressure, retry, and untrusted-input results remain unannotated. In
particular, transient SPI/I²C `Busy` and unavailable range measurements stay particular, transient SPI/I²C `Busy` and unavailable range measurements stay
inside their service retry/idle paths. In contrast, every logging inside their service retry/idle paths. In contrast, every logging
macro—including `kaptan_log::error!`—produces only macro—including `kaptan_log::error!`—produces only
`KAPTAN_LOG`. Error reporting is not a replacement for safety state, `LOG`. Error reporting is not a replacement for safety state,
actuator inhibit, or typed configuration results. actuator inhibit, or typed configuration results.
`kaptan-err-reportable` owns reportable-error identity and its independent `kaptan-err-reportable` owns reportable-error identity and its independent
one-slot ingress. A pending record contains only the compact ID and a static one-slot ingress. A pending record contains only the compact ID and a static
reference to the reporting point's `file:line`. The MAVLink adapter copies that reference to the reporting point's `file:line`. The MAVLink adapter copies that
location into its fixed field when constructing `KAPTAN_ERROR`. Producers never location into its fixed field when constructing `ERROR`. Producers never
encode or transmit MAVLink themselves. Admission is bounded and lossy if a encode or transmit MAVLink themselves. Admission is bounded and lossy if a
different reportable error is already pending. different reportable error is already pending.
## What the receiver sees ## What the receiver sees
- Configuration writes receive authoritative structured failures through - A rejected KCFG upload receives an authoritative `FILE_CONTROL`
`KAPTAN_CONFIG_RESULT`: operation, lifecycle state, field IDs, validation status and bounded readable detail. Invalid user content is not a firmware
rule, and protocol error code. A reportable persistence failure also emits error. A durable-storage failure additionally emits one stable
the storage-only `CommitTransactionError::Store` ID once as `PersistConfigurationError` ID that distinguishes journal geometry, model,
`KAPTAN_ERROR`; replaying the cached command result does not repeat the error sequence, flash-controller, bounds, unlock, and verification
or the flash operation. failures. Replaying the cached close result does not repeat the report or
flash operation.
- Reported `ReportableError` values reach the F722 MAVLink worker as a - Reported `ReportableError` values reach the F722 MAVLink worker as a
`KAPTAN_ERROR`, subject to bounded transport admission. The payload is `ERROR`, subject to bounded transport admission. The payload is
`{ error_id, raised_at[96] }`. `raised_at` is a zero-padded UTF-8 `{ error_id, raised_at[96] }`. `raised_at` is a zero-padded UTF-8
`file:line` prefix for the reporting point. `file:line` prefix for the reporting point.
The message kind itself identifies an error; no level is duplicated in the The message kind itself identifies an error; no level is duplicated in the
payload. Dynamic human-readable context belongs in `KAPTAN_LOG`, while payload. Dynamic human-readable context belongs in `LOG`, while
structured protocol failures remain in their owning response message. structured protocol failures remain in their owning response message.
- Control, navigation, safety, and simulator error types are derive-ready but - Control, navigation, safety, and simulator error types are derive-ready but
their live owners are not yet attached to a MAVLink worker. their live owners are not yet attached to a MAVLink worker.
+24 -22
View File
@@ -10,10 +10,10 @@ kaptan_log::error!("sensor transfer failed");
kaptan_log::trace!("prediction step complete"); kaptan_log::trace!("prediction step complete");
``` ```
All five macros emit `KAPTAN_LOG`; their level never selects a different wire All five macros emit `LOG`; their level never selects a different wire
message. When a reportable firmware error escapes a reviewed message. When a reportable firmware error escapes a reviewed
`#[kaptan_err_reportable::propagate]` function, it makes one bounded admission attempt `#[kaptan_err_reportable::propagate]` function, it makes one bounded admission attempt
to the separate `KAPTAN_ERROR` ingress. to the separate `ERROR` ingress.
`kaptan-log` formats each event once into a fixed bounded buffer and never `kaptan-log` formats each event once into a fixed bounded buffer and never
sends MAVLink, allocates, blocks, changes flight state, or touches DMA. The sends MAVLink, allocates, blocks, changes flight state, or touches DMA. The
@@ -29,31 +29,33 @@ Panic and hard-fault paths still do not attempt live telemetry.
## Receiver contract ## Receiver contract
`definitions/kaptan/log.xml` and `definitions/kaptan/error.xml` separate `definitions/publish/log.xml` and `definitions/publish/error.xml` define the
ordinary logs and reportable errors: separate ordinary-log and reportable-error publications:
```text ```text
KAPTAN_LOG { level, flags, message_length, message[160] } LOG { level, flags, message_length, message[160] }
KAPTAN_ERROR { ERROR {
error_id, raised_at[96] error_id, raised_at[96]
} }
``` ```
A GCS decoder generated from the same `kaptan.xml` revision must validate A GCS decoder generated from the same `kaptan.xml` revision must validate
`message_length <= 160` for `KAPTAN_LOG`. `KAPTAN_ERROR.raised_at` is a `message_length <= 160` for `LOG`. `ERROR.raised_at` is a
zero-padded UTF-8 `file:line` prefix; receivers use bytes before the first zero, zero-padded UTF-8 `file:line` prefix; receivers use bytes before the first zero,
or the complete field when no zero is present. or the complete field when no zero is present.
`KAPTAN_LOG.level` accepts only Error (3), Warning (4), Info (6), and Debug `LOG.level` is the typed severity-ordered Kaptan wire enum Trace (1), Debug
(7); Trace is encoded as Debug (7) plus the `TRACE` flag. Other MAVLink (2), Info (3), Warn (4), or Error (5). Off (0) is a filter-only sentinel and
severity numbers are not Kaptan log levels. is never emitted as a `LOG` event. `LOG.flags` currently owns only the
The `KAPTAN_ERROR` message kind already means a reportable firmware error, so it has Truncated bit (1).
The `ERROR` message kind already means a reportable firmware error, so it has
no redundant level or flags field. no redundant level or flags field.
`error_id` is the stable nonzero catalog number explicitly assigned to the `error_id` is the stable nonzero catalog number explicitly assigned to the
Rust error variant. Renaming or reordering a variant does not change it. Rust error variant. Renaming or reordering a variant does not change it.
`raised_at` identifies the reporting point where the `Err` entered `raised_at` identifies the reporting point where the `Err` entered
diagnostics. Error details requiring diagnostics. Protocol failures remain in their owning response; for example, a
machine action stay in their owning protocol, notably `KAPTAN_CONFIG_RESULT`. rejected KCFG upload returns a `FILE_CONTROL` failure and readable
bounded detail.
Delivery is bounded and lossy under link pressure; heartbeat and safety Delivery is bounded and lossy under link pressure; heartbeat and safety
telemetry use separate paths. Logs have no publication rate setting and are telemetry use separate paths. Logs have no publication rate setting and are
not periodic streams. A retained event leaves its MAVLink sink only once the not periodic streams. A retained event leaves its MAVLink sink only once the
@@ -62,10 +64,10 @@ event.
## Filtering ## Filtering
The single restart-only `mavlink.logging.level_filter` field uses the familiar The single restart-only `mavlink.logging.level_filter` field uses Off (0),
Off, Error, Warn, Info, Debug, and Trace choices. `Info`, for example, accepts Trace (1), Debug (2), Info (3), Warn (4), and Error (5). `Info`, for example,
Error, Warn, and Info log events. `Off` suppresses `KAPTAN_LOG` on MAVLink accepts Info, Warn, and Error log events. `Off` suppresses `LOG` on MAVLink
without suppressing reportable `KAPTAN_ERROR` events or logs sent to another without suppressing reportable `ERROR` events or logs sent to another
sink. There is no separate enable flag and no MAVLink-specific set of extra sink. There is no separate enable flag and no MAVLink-specific set of extra
runtime log levels. F722 uses the validated filter recovered at boot, falling runtime log levels. F722 uses the validated filter recovered at boot, falling
back to its board factory when the journal is empty or invalid. back to its board factory when the journal is empty or invalid.
@@ -73,14 +75,14 @@ back to its board factory when the journal is empty or invalid.
Events retain the compact typed `Level` and sink configuration retains the Events retain the compact typed `Level` and sink configuration retains the
compact typed `LevelFilter`. Generic schema discovery exposes the filter's compact typed `LevelFilter`. Generic schema discovery exposes the filter's
named choices to the configurator, so the UI carries no hardcoded field list. named choices to the configurator, so the UI carries no hardcoded field list.
Only the MAVLink adapter converts the five event levels to MAVLink's numeric Only the MAVLink adapter converts the five event levels to the typed Kaptan
severity representation; Trace uses MAVLink Debug plus the Trace flag. wire enum.
The log ingress is transport-neutral and unfiltered. The tested The log ingress is transport-neutral and unfiltered. The tested
`SitlConfigRuntime` composition applies the configured MAVLink policy when `SitlConfigRuntime` composition applies the configured MAVLink policy when
fanning an event into that sink and proves `kaptan_log::info!` reaches a fanning an event into that sink and proves `kaptan_log::info!` reaches a
generated `KAPTAN_LOG` payload. generated `LOG` payload.
`kaptan-sitl serve` does not yet attach a config/diagnostic MAVLink link. F722 `kaptan-sitl serve` does not yet attach a file/diagnostic MAVLink link. F722
images accept only the unsigned configuration service on their directly wired images accept only the unsigned Kaptan file service on their directly wired
local UART; that direct-wire assumption does not extend to a shared or remote local UART; that direct-wire assumption does not extend to a shared or remote
MAVLink network. MAVLink network.
+55 -23
View File
@@ -1,33 +1,36 @@
# MAVLink and telemetry # MAVLink and telemetry
Kaptan emits telemetry using MAVLink 2 framing. Inbound processing is narrowly Kaptan uses MAVLink 2 framing exclusively with the generated Kaptan dialect.
scoped: It neither emits nor accepts messages from `common`, `minimal`, or another
dialect. Inbound processing is narrowly scoped:
- MAVLink 1 is discarded. - MAVLink 1 is discarded.
- CRC-invalid MAVLink 2 frames are discarded. - CRC-invalid MAVLink 2 frames are discarded.
- Signed frames are discarded because no signature verifier or persistent - Signed frames are discarded because no signature verifier or persistent
replay state exists. replay state exists.
- CRC-valid unsigned MAVLink 2 frames are accepted only for the exact-target - CRC-valid unsigned MAVLink 2 frames are retained only when their message ID
Kaptan configuration service on the directly wired local UART. Other inbound and extra CRC belong to the Kaptan dialect. Exact-target Kaptan file requests
messages do not become authority. are the only inbound records with a current firmware consumer.
`TODO(signing)`: any future shared or remote inbound authority requires verified Shared or remote inbound authority is deliberately unsupported. Enabling it
MAVLink 2 signing, configured source/target binding, persistent anti-replay requires verified MAVLink 2 signing, configured source/target binding,
state, and a provisioning/key-rotation policy as one reviewed feature. 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 The local UART is Kaptan's initial operational file transport, not an
authenticated remote-control channel. Any transmitter electrically connected authenticated remote-control channel. Any transmitter electrically connected
to that RX line can issue configuration commands, so connect the USB-UART to that RX line can issue file requests, so connect the USB-UART
adapter directly and disconnect other transmitters. The platform retains its adapter directly and disconnect other transmitters. The platform retains its
startup-configured low motor-output state throughout this workflow; no marker startup-configured low motor-output state throughout this workflow; no marker
type is treated as GPIO ownership or command authority. A networked GCS must type is treated as GPIO ownership or command authority. A networked GCS must
add verified signing, source binding, and durable replay protection before it add verified signing, source binding, and durable replay protection before it
can receive authority. can receive authority.
Configuration protocol `2.0` admits one source principal from `BEGIN` until The custom Kaptan file service admits one exact source principal for a bounded
its ten-second lease expires or the UART receive path faults. That lease also stop-and-wait transfer. A receive fault or lease expiry aborts its staged
covers the post-commit retry window, preventing another source from evicting a provider state. Completed control replies have a bounded replay path so a lost
lost result. It serializes the direct link but does not authenticate its peer. close response does not repeat a flash operation. This serialization does not
authenticate its peer.
Telemetry transmission uses bounded frame classes. A critical frame may Telemetry transmission uses bounded frame classes. A critical frame may
replace pending best-effort traffic, but never the DMA-owned active buffer. replace pending best-effort traffic, but never the DMA-owned active buffer.
@@ -37,22 +40,51 @@ later service turn.
The local web configurator exposes a bounded live view of CRC-valid inbound 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 frames. It groups packets by source and message ID and measures receive
frequency in one-second windows. Selecting a message reveals the latest decoded frequency in one-second windows. Selecting a message reveals the latest decoded
fields, including human-readable `KAPTAN_ERROR` text. This observation path fields, including the readable zero-padded `ERROR.raised_at` reporting
retains one latest value per bounded aggregate rather than packet history and location. This observation path retains one latest value per bounded aggregate
grants no additional command authority. rather than packet history and grants no additional command authority.
Diagnostic events also feed a separate bounded Runtime logs view at Diagnostic events also feed a separate bounded Runtime logs view at
`http://127.0.0.1:4861`. That view retains individual `KAPTAN_LOG` and `http://127.0.0.1:4861`. That view retains individual `LOG` and
`KAPTAN_ERROR` events in receive order with local receive time, level, and `ERROR` events in receive order with local receive time, level, and
readable text. Packet inspection remains a latest-value view; it is not used as readable text. Packet inspection remains a latest-value view; it is not used as
log history. log history.
The custom Kaptan definitions are aggregated by `definitions/kaptan.xml` from The custom Kaptan definitions are aggregated by `definitions/kaptan.xml` from
`kaptan/config.xml`, `kaptan/log.xml`, and `kaptan/error.xml`. one-message leaves grouped by interaction contract:
`KAPTAN_LOG` is an outbound human-readable four-field record. `KAPTAN_ERROR`
| Directory | Contract |
| --- | --- |
| `publish/` | Zero-reply records for any interested receiver. |
| `exchange/` | Exact-endpoint requests with one terminal response; currently empty. |
| `transfer/` | Exact-endpoint, bounded multi-message sessions. |
The root includes every message leaf directly; there are no redundant category
index files or separate enum catalogs. Placement is a wire-contract
classification, while bindgen still generates one flat `generated::kaptan`
module. Bare message names are scoped by that sole dialect and must not be
merged with another MAVLink dialect. `exchange/` gains its first XML only when
an exchange has a current consumer.
`LOG` is an outbound human-readable four-field record. `ERROR`
is the separate bounded channel for reportable firmware errors, carrying a is the separate bounded channel for reportable firmware errors, carrying a
stable explicit ID plus the `file:line` of its reporting point, and needs no stable explicit ID plus the `file:line` of its reporting point, and needs no
redundant level field. Configuration messages back the current target-described redundant level field. `FILE_CONTROL` and `FILE_DATA` implement
web configurator and the future signed GCS. See Kaptan's generic virtual-file transport without standard MAVLink FTP
compatibility. The current configurator reads and replaces
`/config/user.kcfg`. See
[Configuration](configuration.md), [Logs](logs.md), and [Errors](errors.md) for [Configuration](configuration.md), [Logs](logs.md), and [Errors](errors.md) for
the exact contracts. the exact contracts.
The current scheduled publications are deliberately native rather than
projections into a broader dialect:
| Publication | Current payload |
| --- | --- |
| `HEARTBEAT` (51027) | The single current initialized/output-inhibited state. |
| `IMU` (51028) | Monotonic microseconds, body acceleration in mg, angular rate in mrad/s, and temperature in cdegC. |
| `RANGE` (51029) | Monotonic microseconds and downward distance in mm. |
| `OPTICAL_FLOW` (51030) | Monotonic microseconds, integer ground-relative velocity in mm/s, distance in mm, and quality. |
These records preserve the firmware's bounded native values. They do not
carry unused sensor IDs, repeated board topology, float projections, narrowed
timestamps, or clamped measurements.
+21 -8
View File
@@ -5,14 +5,15 @@ This page collects stable limits and points to the detailed contracts.
| Surface | Current contract | | Surface | Current contract |
| --- | --- | | --- | --- |
| SITL file | One raw bounded CBOR configuration snapshot, replaced through a same-directory temporary file and atomic rename. | | 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. | | F722 persistence | One bounded native-width 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. | | Public config artifact | One canonical self-describing KCFG file containing generated metadata, factory values, and the complete committed candidate. |
| Config discovery | Correlated `KAPTAN_CONFIG_INFO_REQUEST` / `KAPTAN_CONFIG_INFO`, then exact-index descriptor/enum-option and field-value requests. | | File transfer | Custom `FILE_CONTROL` (51025) and `FILE_DATA` (51026), one owner-bound stop-and-wait transfer, 246-byte data chunks, CRC-32C. |
| Diagnostics | `KAPTAN_LOG` (51020) for macro-produced level/text; `KAPTAN_ERROR` (51021) for a bounded reportable `{ error_id, raised_at[96] }` firmware error. | | Diagnostics | `LOG` (51020) for macro-produced level/text; `ERROR` (51021) for a bounded reportable `{ error_id, raised_at[96] }` firmware error. |
| F722 configuration | Target-described fields and board factory defaults; the current local transport is a directly wired unsigned UART with motor outputs physically inhibited. | | Publications | Kaptan-native `HEARTBEAT` (51027), `IMU` (51028), `RANGE` (51029), and `OPTICAL_FLOW` (51030); exact integer units and monotonic `u64` microseconds. |
| F722 configuration | `/config/user.kcfg`; target-described fields and board factory values over a directly wired unsigned UART with motor outputs physically inhibited. |
Read [Configuration](configuration.md) for the field/consumer matrix and GCS Read [Configuration](configuration.md) for configuration ownership, artifact,
transaction sequence, [Logs](logs.md) for receiver decoding and filtering, and and file contracts, [Logs](logs.md) for receiver decoding and filtering, and
[Errors](errors.md) for reportable errors and panic scope. [Errors](errors.md) for reportable errors and panic scope.
## Verification ## Verification
@@ -28,8 +29,20 @@ cargo clippy --workspace --all-targets \
--exclude kaptan-geprc-f722-v2 --locked --offline -- -D warnings --exclude kaptan-geprc-f722-v2 --locked --offline -- -D warnings
cargo clippy -p kaptan-kakute-f7-mini-v3 -p kaptan-geprc-f722-v2 \ cargo clippy -p kaptan-kakute-f7-mini-v3 -p kaptan-geprc-f722-v2 \
--target thumbv7em-none-eabihf --locked --offline -- -D warnings --target thumbv7em-none-eabihf --locked --offline -- -D warnings
cargo build --release --locked --offline \
-p kaptan-kakute-f7-mini-v3 -p kaptan-geprc-f722-v2 \
--target thumbv7em-none-eabihf
python3 -m unittest scripts/test-analyze-fw.py
scripts/analyze-fw.py \
target/thumbv7em-none-eabihf/release/kaptan-kakute-f7-mini-v3 \
platforms/stm32f7/link/f722/memory.x
scripts/analyze-fw.py \
target/thumbv7em-none-eabihf/release/kaptan-geprc-f722-v2 \
platforms/stm32f7/link/f722/memory.x
``` ```
The target-specific lint verifies the local configuration receiver and journal The target-specific lint verifies the local configuration receiver and journal
path. The host suite separately asserts that configuration ingress cannot reach path. The host suite separately asserts that configuration ingress cannot reach
an actuator path. an actuator path. The ELF checks prove that the flash-busy operation is linked
into executable SRAM and reject compiler-outlined control flow or PC-relative
literal loads outside that section.
+5 -5
View File
@@ -16,12 +16,12 @@ There is no checked-in platform actuator route:
- no DShot or PWM peripheral implementation is linked; - no DShot or PWM peripheral implementation is linked;
- no TIM3/TIM5 motor task or DMA output path exists; - no TIM3/TIM5 motor task or DMA output path exists;
- MAVLink input is accepted only by the configuration service and has no - only CRC-valid Kaptan-dialect MAVLink input reaches the bounded virtual-file
actuator path; service, which has no actuator path;
- a future verified arm command is still acknowledged as unsupported. - no arm command is defined or admitted.
The protocol reference crate remains in the workspace only as a framing and The framing and file-transfer crates do not grant hardware access or actuator
arming-sequence reference. It does not grant hardware access or authority. authority.
Run the structural guard after any platform or board change: Run the structural guard after any platform or board change:
+5 -5
View File
@@ -36,8 +36,8 @@ Open <https://app.gazebosim.org/visualization>, connect to
## Quad-X profile ## Quad-X profile
SITL starts through the same central `ConfigManager` composition used by the SITL starts through the central `ConfigManager` lifecycle. Create and validate
host protocol tests. Create and validate a private host profile file with: a private host profile file with:
```sh ```sh
kaptan-sitl config write-default kaptan-sitl.cbor kaptan-sitl config write-default kaptan-sitl.cbor
@@ -49,9 +49,9 @@ The file selects the restart-only profile used to build the estimator,
controller, allocator envelope, safety supervisor, and MAVLink log-sink policy at controller, allocator envelope, safety supervisor, and MAVLink log-sink policy at
process start. It is local persistence and never appears on MAVLink. process start. It is local persistence and never appears on MAVLink.
`kaptan-sitl serve` retains `kaptan-sitl serve` retains
the central manager for that boot but does not yet open a MAVLink config/log the central manager for that boot but does not yet open a MAVLink file/log
endpoint; see [Configuration](configuration.md) for the exact transaction contract endpoint; see [Configuration](configuration.md) for the KCFG, file-transfer,
and safety boundary. and safety boundaries.
## Limits ## Limits