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
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
Kaptan GCS custom MAVLink V2 messages
│ (future: verified signing, source binding, replay state)
FILE_CONTROL / FILE_DATA
│
▼
kaptan-config-mavlink ──► state-only ConfigProtocol
│ borrows
kaptan-flight-runtime
├── one ConfigManager ──► kaptan-config ──► flight schema/store
└── one bounded MAVLink log-sink queue
kaptan-file-mavlink ──► virtual FileProvider
│
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
adapter. The core uses numeric field IDs and typed scalar values; descriptor
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 a conventional level
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.
The file dialect is intentionally Kaptan-native rather than standard MAVLink
FTP, `PARAM_*`, or a QGC adapter. Its two messages know only bounded paths,
bytes, positions, checksums, transfer ownership, and retries. The configuration
provider maps `/config/user.kcfg` to a canonical self-describing artifact.
KCFG carries generated metadata and a complete committed value set; the target
streams it without retaining a whole-file buffer. Saving imports and validates
one complete replacement candidate. The core distinguishes only immutable
boot-active values from the latest committed values. A save cannot mutate
controller or estimator state in flight.
Target-owned stores may serialize one bounded complete CBOR snapshot. SITL
supplies the host file adapter and atomically replaces that snapshot through a
same-directory temporary file. The serialization is not configuration state
and is never exposed by the MAVLink protocol.
Target-owned stores use private persistence formats. SITL atomically replaces a
bounded CBOR snapshot. F722 alternates compact `KCSP` snapshots between two
erase sectors, retaining only the old and replacement records across the commit
boundary. Neither format is exposed as the public KCFG file.
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.
never a scalar field or persistence member. F722 recovers operational settings
before constructing consumers. Its directly wired local UART accepts only
exact-target, CRC-valid Kaptan file requests while physical outputs remain
inhibited. CRC is integrity-only; a shared or remote GCS still needs production
authentication, source binding, and persistent anti-replay state. Disarmed
activation and flight-consumer reconstruction remain separate reviewed work.
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`, and
`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
The MAVLink dialect root directly includes one-message XML leaves under
`publish/` and `transfer/`. The `exchange/` contract remains empty until a
current consumer justifies a message. There are no category index XML files or
separate enum catalogs. The build watches the definitions tree and generates
one `generated::kaptan` module; leaves are never separately generated Rust
modules. `kaptan-log` itself has
no MAVLink, platform, time, allocation, or filesystem dependency; its sole
configuration coupling is compile-time enum metadata for `LevelFilter`.
`kaptan-err-reportable` independently owns explicitly assigned reportable-error
identity and a bounded nonblocking ingress; it has
no logging, MAVLink, platform, filesystem, or clock dependency. Event creation
is independent from sink filtering, allowing a future blackbox and MAVLink to
select different policies. The platform drains reportable errors before its bounded
critical and best-effort log queues and removes a pending record only after TX
admission. `KAPTAN_LOG` carries one conventional level plus one bounded text
field; `KAPTAN_ERROR` carries an explicitly assigned 16-bit ID and the bounded
`file:line` of the reporting point. Its
message kind already identifies a reportable firmware error. Heartbeat and safety
telemetry remain separate from diagnostics.
identity and a bounded nonblocking ingress; it has no logging, MAVLink,
platform, filesystem, or clock dependency. Event creation is independent from
sink filtering, allowing a future blackbox and MAVLink to select different
policies. The platform drains reportable errors before its bounded critical and
best-effort log queues and removes a pending record only after TX admission.
`LOG` carries one conventional level plus one bounded text field; `ERROR`
carries an explicitly assigned 16-bit ID and the bounded `file:line` of the
reporting point. Its message kind already identifies a reportable firmware
error. Kaptan-native heartbeat and sensor publications remain separate from
diagnostics.
See [Configuration](configuration.md) for the deployment matrix and GCS transaction
contract, [Logs](logs.md) for the receiver contract, and [Errors](errors.md)
See [Configuration](configuration.md) for KCFG and the file-transfer contract,
[Logs](logs.md) for the receiver contract, and [Errors](errors.md)
for derived-error and failure-reporting scope.
## 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)
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
`Info` level filter. The web configurator may replace those
target-advertised values in the journal. Range
@@ -59,7 +59,7 @@ Then run:
```sh
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
+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
`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
sink with an `Info` level filter. The web configurator may replace those
target-advertised values in the journal. Its `ImmutableConfig` keeps the
@@ -64,7 +64,7 @@ Launch the target-described web UI:
```sh
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
+141 -177
View File
@@ -1,18 +1,18 @@
# Configuration
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.
Kaptan has one generic configuration model in `kaptan-config`. Target schemas
declare typed fields and validation once; derive code generates the stable
field registry and schema fingerprint. Boards supply the sole erased-flash
factory values. The browser contains no board, driver, sensor, schema, or
parameter catalog.
## Current deployment matrix
| 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` | 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. |
| `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. |
| 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
@@ -21,208 +21,172 @@ USB-UART adapter, then run from `firmware`:
```sh
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
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.
`--source-system` is the configurator's exact MAVLink source identity. Every
simultaneously connected GCS must use a different nonzero system ID so transfer
ownership and retries cannot alias another client.
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
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.
controller is busy; the host gives the final close a separate five-second
timeout. Keep the vehicle unpowered except for setup power and do not disconnect
it during Save.
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 artifact carries generated field IDs, native scalar kinds, display keys,
units, bounds, enum choices, factory values, committed values, schema identity,
and the derived restart-required state. A new board or field therefore needs no
configurator release. `u8`, `u16`, and enum-backed values retain their native
width rather than being widened at the configuration boundary.
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 the `KAPTAN_ERROR` ID and reporting
`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 Incoming MAVLink table groups live, CRC-valid Kaptan packets by source and
message ID and shows receive frequency and total count. Selecting a message
watches its latest decoded value. The separate Runtime logs view retains
individual `LOG` and `ERROR` events in receive order, with local receive time,
level, and readable text.
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 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.
## Board ownership
Each F722 board owns the exact settings consumed by its compiled image. Both
current boards advertise:
Each F722 board declares one `UserConfig` root for the settings consumed by its
compiled driver graph. The explicit `FACTORY_USER_CONFIG` literal is the sole
erased-flash value. There is no `Default` implementation and no platform-wide
union of board schemas.
- MAVLink system and component identity;
- raw-IMU and heartbeat publication rates; and
- the MAVLink diagnostic sink's Off/Error/Warn/Info/Debug/Trace level filter.
Both current boards expose MAVLink identity, IMU and heartbeat publication
rates, and the MAVLink log level filter. Kakute also exposes range and
optical-flow rates because its immutable hardware manifest contains an MTF01.
Those fields are absent from GEPRC rather than present-but-disabled.
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 adjacent `IMMUTABLE_CONFIG` literal remains the sole source of facts that
cannot safely be edited as scalar configuration:
The UART baud is the immutable bootstrap link for this first configuration
transport. Pass it explicitly to the host with `--baud`.
- output-inhibit and status-LED routes;
- 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
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 stable semantic field IDs through the generic
`Config::field_value` boundary. It does not know a board section, Rust field,
or aggregate shape. Whole-candidate validation checks both generated local
rules and relationships such as publication budget and immutable hardware
capabilities.
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.
## KCFG artifact
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.
KCFG is the public, canonical import/export representation. It is independent
of MAVLink and of the private storage format. A file contains:
The current SITL configuration has only settings consumed by its estimator,
controller, safety supervisor, allocator envelope, or MAVLink diagnostic sink:
- `KCFG` magic and one format version;
- 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;
- IMU, local-aiding, and command freshness/timing;
- position/velocity aiding variance;
- outer-loop gains and acceleration limits;
- 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 trailer uses network byte order deliberately. The generic file dialect
also calculates CRC-32C over every complete file byte; network-order storage
keeps that independent transfer checksum content-specific instead of producing
the fixed residue obtained by appending a reflected CRC in little-endian order.
The fixed Quad-X mixer is intentionally not configurable. It is reviewed
capability data, not a user-editable array.
An imported file must match the target-generated schema, descriptor metadata,
factory values, field order, native scalar kinds, and exact length. Unknown,
duplicate, missing, malformed, noncanonical, out-of-range, and relationally
invalid values are rejected. The target stages only the typed candidate and a
small parser state; it never buffers the complete public file. Exports are also
generated by range directly from compiled metadata and current committed
values. One small export plan containing the exact length and checksums is
prepared when a read opens and reused for every requested range.
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.
Schema identity is required because a complete file can otherwise be validly
encoded while belonging to another firmware schema. It is generated from the
schema and is not a hand-maintained board ID.
## 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
safely be changed by a scalar transaction:
## Generic file dialect
- IMU model and its fixed body mount (`BodyAligned` or `Yaw180`);
- 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.
Kaptan uses two custom messages rather than standard MAVLink FTP or a family of
configuration-specific requests:
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.
```text
FILE_CONTROL
FILE_DATA
```
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.
`FILE_CONTROL` opens, closes, aborts, and acknowledges a transfer. It
carries a client-selected nonzero `u16` transfer ID, exact target, position,
complete file size, CRC-32C, operation/status, and at most 64 bytes of path or
result detail. A source does not reuse an ID during the ten-second replay lease.
A new ID from the same source may supersede an open only before the first byte
has advanced; a progressed abandoned transfer remains pinned until lease expiry.
`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
use target-appropriate private persistence formats. Neither format is exposed
by the MAVLink configuration protocol.
The current provider exposes exactly `/config/user.kcfg`. A successful write
close has generic status `OK` and content beginning with the authoritative
seven-byte KCFG result prefix: restart flag, primary field, related field, and
validation-rule code. Its exact layout is `flags:u8 | primary_field:u16 LE |
related_field:u16 LE | validation_rule:u16 LE`; flag bit 0, when set, means a
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;
- 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.
SITL and F722 use target-appropriate private persistence. Neither private
format is the public KCFG artifact.
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.
### SITL CBOR
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.
SITL stores one bounded CBOR snapshot keyed by stable `FieldId`. Saving writes
a same-directory temporary file, synchronizes it, and atomically renames it
over the configured file. The exact map rejects unknown, duplicate, and missing
fields, wrong schema or format, trailing input, invalid scalar encodings, and
complete-model validation failures.
### 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.
F722 stores a compact private `KCSP` scalar snapshot inside a two-slot journal.
Each entry stores its stable field ID, scalar kind, and value at that kind's
native width; an `u8` is one byte rather than a widened integer slot. Each
journal record includes payload length, CRC-32C, commit marker, and a private
monotonic sequence. Saving erases the inactive slot, programs and verifies the
complete replacement, and writes the commit marker last. The previous valid
slot remains recoverable across power loss until that point.
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.
Only two complete records can exist: the old committed value and its
replacement. Saves do not append history. The sequence is storage metadata used
only to select the newer valid slot if both slots survive; it is never exported
through KCFG or shown in the UI.
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`, 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.
There is no migration path because no configuration version has shipped.
+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
inside their service retry/idle paths. In contrast, every logging
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.
`kaptan-err-reportable` owns reportable-error identity and its independent
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
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
different reportable error is already pending.
## What the receiver sees
- Configuration writes receive authoritative structured failures through
`KAPTAN_CONFIG_RESULT`: operation, lifecycle state, field IDs, validation
rule, and protocol error code. A reportable persistence failure also emits
the storage-only `CommitTransactionError::Store` ID once as
`KAPTAN_ERROR`; replaying the cached command result does not repeat the error
or the flash operation.
- A rejected KCFG upload receives an authoritative `FILE_CONTROL`
status and bounded readable detail. Invalid user content is not a firmware
error. A durable-storage failure additionally emits one stable
`PersistConfigurationError` ID that distinguishes journal geometry, model,
sequence, flash-controller, bounds, unlock, and verification
failures. Replaying the cached close result does not repeat the report or
flash operation.
- 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
`file:line` prefix for the reporting point.
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.
- Control, navigation, safety, and simulator error types are derive-ready but
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");
```
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
`#[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
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
`definitions/kaptan/log.xml` and `definitions/kaptan/error.xml` separate
ordinary logs and reportable errors:
`definitions/publish/log.xml` and `definitions/publish/error.xml` define the
separate ordinary-log and reportable-error publications:
```text
KAPTAN_LOG { level, flags, message_length, message[160] }
KAPTAN_ERROR {
LOG { level, flags, message_length, message[160] }
ERROR {
error_id, raised_at[96]
}
```
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,
or the complete field when no zero is present.
`KAPTAN_LOG.level` accepts only Error (3), Warning (4), Info (6), and Debug
(7); Trace is encoded as Debug (7) plus the `TRACE` flag. Other MAVLink
severity numbers are not Kaptan log levels.
The `KAPTAN_ERROR` message kind already means a reportable firmware error, so it has
`LOG.level` is the typed severity-ordered Kaptan wire enum Trace (1), Debug
(2), Info (3), Warn (4), or Error (5). Off (0) is a filter-only sentinel and
is never emitted as a `LOG` event. `LOG.flags` currently owns only the
Truncated bit (1).
The `ERROR` message kind already means a reportable firmware error, so it has
no redundant level or flags field.
`error_id` is the stable nonzero catalog number explicitly assigned to the
Rust error variant. Renaming or reordering a variant does not change it.
`raised_at` identifies the reporting point where the `Err` entered
diagnostics. Error details requiring
machine action stay in their owning protocol, notably `KAPTAN_CONFIG_RESULT`.
diagnostics. Protocol failures remain in their owning response; for example, a
rejected KCFG upload returns a `FILE_CONTROL` failure and readable
bounded detail.
Delivery is bounded and lossy under link pressure; heartbeat and safety
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
@@ -62,10 +64,10 @@ event.
## Filtering
The single restart-only `mavlink.logging.level_filter` field uses the familiar
Off, Error, Warn, Info, Debug, and Trace choices. `Info`, for example, accepts
Error, Warn, and Info log events. `Off` suppresses `KAPTAN_LOG` on MAVLink
without suppressing reportable `KAPTAN_ERROR` events or logs sent to another
The single restart-only `mavlink.logging.level_filter` field uses Off (0),
Trace (1), Debug (2), Info (3), Warn (4), and Error (5). `Info`, for example,
accepts Info, Warn, and Error log events. `Off` suppresses `LOG` on MAVLink
without suppressing reportable `ERROR` events or logs sent to another
sink. There is no separate enable flag and no MAVLink-specific set of extra
runtime log levels. F722 uses the validated filter recovered at boot, falling
back to its board factory when the journal is empty or invalid.
@@ -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
compact typed `LevelFilter`. Generic schema discovery exposes the filter's
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
severity representation; Trace uses MAVLink Debug plus the Trace flag.
Only the MAVLink adapter converts the five event levels to the typed Kaptan
wire enum.
The log ingress is transport-neutral and unfiltered. The tested
`SitlConfigRuntime` composition applies the configured MAVLink policy when
fanning an event into that sink and proves `kaptan_log::info!` reaches a
generated `KAPTAN_LOG` payload.
`kaptan-sitl serve` does not yet attach a config/diagnostic MAVLink link. F722
images accept only the unsigned configuration service on their directly wired
generated `LOG` payload.
`kaptan-sitl serve` does not yet attach a file/diagnostic MAVLink link. F722
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
MAVLink network.
+55 -23
View File
@@ -1,33 +1,36 @@
# MAVLink and telemetry
Kaptan emits telemetry using MAVLink 2 framing. Inbound processing is narrowly
scoped:
Kaptan uses MAVLink 2 framing exclusively with the generated Kaptan dialect.
It neither emits nor accepts messages from `common`, `minimal`, or another
dialect. 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.
- 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.
- CRC-valid unsigned MAVLink 2 frames are retained only when their message ID
and extra CRC belong to the Kaptan dialect. Exact-target Kaptan file requests
are the only inbound records with a current firmware consumer.
`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.
Shared or remote inbound authority is deliberately unsupported. Enabling it
requires verified MAVLink 2 signing, configured source/target binding,
persistent anti-replay state, and a provisioning/key-rotation policy as one
reviewed feature.
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
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
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.
The custom Kaptan file service admits one exact source principal for a bounded
stop-and-wait transfer. A receive fault or lease expiry aborts its staged
provider state. Completed control replies have a bounded replay path so a lost
close response does not repeat a flash operation. This serialization does not
authenticate its peer.
Telemetry transmission uses bounded frame classes. A critical frame may
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
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.
fields, including the readable zero-padded `ERROR.raised_at` reporting
location. 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, level, and
`http://127.0.0.1:4861`. That view retains individual `LOG` 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
log history.
The custom Kaptan definitions are aggregated by `definitions/kaptan.xml` from
`kaptan/config.xml`, `kaptan/log.xml`, and `kaptan/error.xml`.
`KAPTAN_LOG` is an outbound human-readable four-field record. `KAPTAN_ERROR`
one-message leaves grouped by interaction contract:
| Directory | Contract |
| --- | --- |
| `publish/` | Zero-reply records for any interested receiver. |
| `exchange/` | Exact-endpoint requests with one terminal response; currently empty. |
| `transfer/` | Exact-endpoint, bounded multi-message sessions. |
The root includes every message leaf directly; there are no redundant category
index files or separate enum catalogs. Placement is a wire-contract
classification, while bindgen still generates one flat `generated::kaptan`
module. Bare message names are scoped by that sole dialect and must not be
merged with another MAVLink dialect. `exchange/` gains its first XML only when
an exchange has a current consumer.
`LOG` is an outbound human-readable four-field record. `ERROR`
is the separate bounded channel for reportable firmware errors, carrying a
stable explicit ID plus the `file:line` of its reporting point, and needs no
redundant level field. Configuration messages back the current target-described
web configurator and the future signed GCS. See
redundant level field. `FILE_CONTROL` and `FILE_DATA` implement
Kaptan's generic virtual-file transport without standard MAVLink FTP
compatibility. The current configurator reads and replaces
`/config/user.kcfg`. See
[Configuration](configuration.md), [Logs](logs.md), and [Errors](errors.md) for
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 |
| --- | --- |
| 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 macro-produced level/text; `KAPTAN_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. |
| F722 persistence | One bounded native-width scalar snapshot inside each slot of a power-loss-safe two-slot flash journal. |
| Public config artifact | One canonical self-describing KCFG file containing generated metadata, factory values, and the complete committed candidate. |
| File transfer | Custom `FILE_CONTROL` (51025) and `FILE_DATA` (51026), one owner-bound stop-and-wait transfer, 246-byte data chunks, CRC-32C. |
| Diagnostics | `LOG` (51020) for macro-produced level/text; `ERROR` (51021) for a bounded reportable `{ error_id, raised_at[96] }` firmware error. |
| 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
transaction sequence, [Logs](logs.md) for receiver decoding and filtering, and
Read [Configuration](configuration.md) for configuration ownership, artifact,
and file contracts, [Logs](logs.md) for receiver decoding and filtering, and
[Errors](errors.md) for reportable errors and panic scope.
## Verification
@@ -28,8 +29,20 @@ cargo clippy --workspace --all-targets \
--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
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
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 TIM3/TIM5 motor task or DMA output path exists;
- MAVLink input is accepted only by the configuration service and has no
actuator path;
- a future verified arm command is still acknowledged as unsupported.
- only CRC-valid Kaptan-dialect MAVLink input reaches the bounded virtual-file
service, which has no actuator path;
- no arm command is defined or admitted.
The protocol reference crate remains in the workspace only as a framing and
arming-sequence reference. It does not grant hardware access or authority.
The framing and file-transfer crates do not grant hardware access or actuator
authority.
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
SITL starts through the same central `ConfigManager` composition used by the
host protocol tests. Create and validate a private host profile file with:
SITL starts through the central `ConfigManager` lifecycle. Create and validate
a private host profile file with:
```sh
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
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 transaction contract
and safety boundary.
the central manager for that boot but does not yet open a MAVLink file/log
endpoint; see [Configuration](configuration.md) for the KCFG, file-transfer,
and safety boundaries.
## Limits