diff --git a/docs/src/architecture.md b/docs/src/architecture.md index 11f2cb3..b064813 100644 --- a/docs/src/architecture.md +++ b/docs/src/architecture.md @@ -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 diff --git a/docs/src/boards/geprc-f722-v2.md b/docs/src/boards/geprc-f722-v2.md index 529b4bb..8d2746b 100644 --- a/docs/src/boards/geprc-f722-v2.md +++ b/docs/src/boards/geprc-f722-v2.md @@ -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/ --baud 115200 + --source-system 255 --serial /dev/serial/by-id/ --baud 115200 ``` Open `http://127.0.0.1:4861`, Save the discovered fields, and manually diff --git a/docs/src/boards/kakute-f7-mini-v3.md b/docs/src/boards/kakute-f7-mini-v3.md index 592cfaa..dbd8cc4 100644 --- a/docs/src/boards/kakute-f7-mini-v3.md +++ b/docs/src/boards/kakute-f7-mini-v3.md @@ -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/ --baud 115200 + --source-system 255 --serial /dev/serial/by-id/ --baud 115200 ``` Open `http://127.0.0.1:4861`, edit the discovered fields, press Save, and diff --git a/docs/src/configuration.md b/docs/src/configuration.md index 7cf053e..ab8f891 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -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/ --baud 115200 + --source-system 255 --serial /dev/serial/by-id/ --baud 115200 ``` -Open `http://127.0.0.1:4861`. Edit discovered fields and press -Save. Save commits the complete candidate to the inactive flash slot and writes -the commit marker last. The active runtime does not change. Once Save completes, -manually power-cycle the controller to recover the committed journal and -activate it. +`--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. diff --git a/docs/src/errors.md b/docs/src/errors.md index 5072edc..7c972c1 100644 --- a/docs/src/errors.md +++ b/docs/src/errors.md @@ -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. diff --git a/docs/src/logs.md b/docs/src/logs.md index c9dbd29..3da1fc7 100644 --- a/docs/src/logs.md +++ b/docs/src/logs.md @@ -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. diff --git a/docs/src/mavlink.md b/docs/src/mavlink.md index 2c97096..2392bcd 100644 --- a/docs/src/mavlink.md +++ b/docs/src/mavlink.md @@ -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. diff --git a/docs/src/reference.md b/docs/src/reference.md index c04632d..7be7e7d 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -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. diff --git a/docs/src/safety-boundary.md b/docs/src/safety-boundary.md index 89ed2b5..efd99e6 100644 --- a/docs/src/safety-boundary.md +++ b/docs/src/safety-boundary.md @@ -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: diff --git a/docs/src/sitl.md b/docs/src/sitl.md index 6ed9291..e9ed2dd 100644 --- a/docs/src/sitl.md +++ b/docs/src/sitl.md @@ -36,8 +36,8 @@ Open , 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