Skip to content

122 aimdb serial connector cobs framed serialuart transport tokio serial embedded io async#126

Merged
lxsaah merged 5 commits into
mainfrom
122-aimdb-serial-connector-cobs-framed-serialuart-transport-tokio-serial-embedded-io-async
Jun 6, 2026
Merged

122 aimdb serial connector cobs framed serialuart transport tokio serial embedded io async#126
lxsaah merged 5 commits into
mainfrom
122-aimdb-serial-connector-cobs-framed-serialuart-transport-tokio-serial-embedded-io-async

Conversation

@lxsaah
Copy link
Copy Markdown
Contributor

@lxsaah lxsaah commented Jun 5, 2026

aimdb-serial-connector — COBS-framed serial/UART transport

Closes #122 (follow-up to #39 / #120). Design: doc 041 — phase 6 embedded transports.

Overview

A new transport connector — the serial sibling of aimdb-uds-connector — that lets a
board speak the AimX remote-access protocol over a serial line. A sensor MCU can dial
a gateway over UART, and (riding the no_std AimxDispatch from #120) an MCU can also
serve a host over UART.

The crate contributes only the transport: the Dialer/Listener/Connection triple plus
thin SerialClient/SerialServer sugar over the serial:// scheme. The AimX codec,
dispatch, and the runtime-neutral session engines (run_client / serve) are reused
verbatim from aimdb-core.

Wire protocol

Same compact AimX JSON as UDS, but framed with COBS (Consistent Overhead Byte Stuffing)
and a 0x00 delimiter instead of a newline. COBS rewrites a payload so it never contains a
0x00, then a single 0x00 marks the frame boundary — self-synchronizing on a lossy,
unframed serial medium, so a receiver that joins mid-stream resynchronizes on the next
sentinel. AimX JSON never contains a raw 0x00, so overhead is one byte per ~254.

Two runtime halves

Half Target Connection Notes
tokio-runtime std, host/gateway TokioSerialConnection<S> over any AsyncRead + AsyncWrite (real tokio_serial::SerialStream, or tokio::io::duplex() in tests) Rides the generic SessionClientConnector / SessionServerConnector.
embassy-runtime no_std + alloc, MCU EmbassySerialConnection<Rd, Wr> generic over embedded-io-async Read/Write halves (the Uart::split() shape) Hand-rolls ConnectorBuilder, force-Sends the single-core futures via aimdb-embassy-adapter's SendFutureWrapper. Reconnect off by default (the UART peripheral is moved in).

The Embassy half is the first raw-peripheral session connector — MQTT/KNX sidestep the
Send + Sync problem by pulling a Send + Sync embassy_net::Stack from the runtime
adapter rather than owning a peripheral. The unsafe impl Send/Sync here rest on the same
single-core, cooperative Embassy-executor invariant SendFutureWrapper documents (no
preemption / thread migration).

Key components

  • framing module — the shared COBS frame codec: encode_frame plus a chunk-tolerant
    FrameAccumulator. Pure no_std + alloc, so the round-trip is unit-tested independent of
    any transport.
  • SerialClient / SerialServer sugar on both halves; apply_writable mirrors the UDS
    connector (marks the policy's writable records so record.list advertises the writable
    flag).

Robustness

  • Bounded frame buffer. FrameAccumulator caps un-delimited buffering at
    DEFAULT_MAX_FRAME (8 KiB); a longer run with no sentinel is treated as a desync, dropped,
    and resynced on the next sentinel — so a stuck/garbage stream can't grow the buffer toward
    an OOM on a small embedded heap. (with_max_frame allows a custom cap.)
  • Undecodable frame is skipped, not fatal. recv drops a chunk that fails to COBS-decode
    (line noise, or a mid-stream join) and resyncs rather than tearing down the session —
    transient corruption costs one frame, not the whole connection. This matters most on
    Embassy, where reconnect: false over a moved-in UART means a fatal read error couldn't
    recover.
  • Embassy sends in ring-sized (64-byte) chunks. A HAL BufferedUart::write is
    atomic-or-error (embassy-stm32 returns BufferTooLong for a single write larger than its
    TX ring), so a frame bigger than the buffer (e.g. a record.list reply) is split.
  • Tokio dialer flushes the OS input buffer on connect. A real serial port retains bytes
    across opens, so a half-read reply from a prior (killed) session won't desync the first
    frame.

Examples

  • examples/serial_demo.rs (host, --features _test-tokio) — AimX client/server over a
    device path (a board's ST-LINK VCP at /dev/ttyACM0, or a socat PTY pair for a
    no-hardware smoke). Modes: client, server, set (write path), raw (low-level debug).
  • examples/embassy-serial-connector-demo/ — STM32H563ZI Nucleo firmware serving the
    counter (read-only) and setting (writable) records over USART3 ↔ the ST-LINK Virtual
    COM Port. The no_std SerialServer + AimxDispatch on real silicon, flashed via
    probe-rs, queried from the host over the wire.

Testing

  • cobs_framing.rs — COBS round-trips across arbitrary chunk boundaries (incl. byte-by-byte
    delivery), empty payloads, leading-sentinel resync, buffer-cap overflow + resync, and
    large-frame pass-through.
  • tokio_roundtrip.rs — end-to-end AimX (record.get / record.list) over a
    tokio::io::duplex() pipe (production serve + AimxDispatch answering run_client),
    plus a corrupt-frame resync test.
  • embassy_smoke.rs — the exact run_client<SerialDialer, _, EmbassyAdapter>
    monomorphization an MCU uses, driven on the host over a real loopback UART.
  • Makefile: make test runs both halves; make test-embedded cross-checks the Embassy half
    on thumbv7em-none-eabihf; make clippy lints both (tokio + embassy + embassy/defmt).

Hardware-validated end-to-end on an STM32H563ZI Nucleo over the ST-LINK VCP:
record.list / record.get / record.set and streaming subscriptions all round-trip
MCU↔host.

lxsaah added 4 commits June 5, 2026 11:59
- Implemented roundtrip tests for COBS framing in `cobs_framing.rs` to ensure proper encoding and decoding of frames regardless of chunk sizes.
- Added tests for single frame, byte-by-byte delivery, multiple frames across chunk boundaries, and empty payloads.
- Verified that encoded frames do not contain the sentinel byte except as a terminator.

feat: implement Embassy client-exit smoke test

- Created `embassy_smoke.rs` to test the RPC engine over the real Embassy serial transport using a loopback UART.
- Validated that the engine can handle requests and responses correctly, ensuring proper COBS framing and decoding.

feat: add Tokio roundtrip test for AimX over serial transport

- Developed `tokio_roundtrip.rs` to test end-to-end communication using a duplex stream as a mock serial connection.
- Ensured that the AimX server and client can communicate correctly over the serial transport, verifying COBS framing in both directions.

chore: initialize embassy-serial-connector-demo example

- Created a new example project demonstrating AimX over serial on STM32H563ZI.
- Configured the project with necessary dependencies and build settings for the STM32 platform.
- Added README documentation for setup and usage instructions, including hardware requirements and troubleshooting tips.

chore: add build script and flash script for demo

- Implemented `build.rs` to configure linker arguments for the STM32 target.
- Added `flash.sh` script to facilitate flashing the firmware to the STM32H563ZI using probe-rs.

chore: set up .cargo/config.toml and .gitignore for demo

- Configured `.cargo/config.toml` for the STM32 target and added a `.gitignore` file to exclude build artifacts.
@lxsaah lxsaah self-assigned this Jun 5, 2026
@lxsaah lxsaah marked this pull request as ready for review June 6, 2026 20:24
@lxsaah lxsaah merged commit 82d4a32 into main Jun 6, 2026
7 checks passed
@lxsaah lxsaah deleted the 122-aimdb-serial-connector-cobs-framed-serialuart-transport-tokio-serial-embedded-io-async branch June 6, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aimdb-serial-connector — COBS-framed serial/UART transport (tokio-serial + embedded-io-async)

1 participant