tpt-e-link

Rust

Transport-agnostic, no_std, zero-allocation protocol crate shared between TPT host and embedded device firmware — the single source of truth for the wire format.

0 stars0 forks0 watchersApache License 2.0

Languages

Rust100.0%
README

tpt-e-link

CI crates.io docs.rs

tpt-e-link is the foundational, transport-agnostic communication protocol for the TPT Embedded Edge Ecosystem. It is the single source of truth for how hosts (like tpt-basestation) and devices (like tpt-e-node) communicate.

It is designed to be strictly typed, no_std compatible, and deterministic, ensuring that the host and device can never fall out of sync regarding the "language" they speak.

Goals

  • Zero-Desync Guarantee — by sharing this exact crate between host and device, protocol changes cause compile-time errors rather than runtime bugs.
  • Transport Agnostic — defines what is being sent, not how. The protocol works seamlessly over UART, BLE, TCP, or WebSockets without changing the core logic.
  • no_std & Zero-Allocation — the core serialization and parsing logic runs on bare-metal microcontrollers with <64KB RAM, avoiding heap allocations entirely.
  • Mock-First — a pure-Rust mock transport layer (behind the std feature) lets the entire protocol be tested in CI without physical hardware.

Non-Goals

  • Hardware Initializationtpt-e-link does not know what a GPIO pin or a UART peripheral is. That is the job of tpt-e-node.
  • Cloud Connectivity — this is strictly a local, peer-to-peer protocol.

Quickstart

use tpt_e_link::frame::{encode, FrameDecoder, MAX_FRAME_SIZE};
use tpt_e_link::{Arch, Capability, DeviceManifest, LinkMessage, Version};

// Build a manifest and encode it as a wire frame.
let mut manifest = DeviceManifest::new(*b"node-001", Version::new(0, 1, 0), Arch::Esp32Xtensa);
manifest.push_capability(Capability::Wifi).unwrap();

let mut buf = [0u8; MAX_FRAME_SIZE];
let len = encode(&LinkMessage::Handshake(manifest), &mut buf).unwrap();
let frame = &buf[..len]; // send this over UART/BLE/TCP/...

// On the receiving end, feed bytes to a `FrameDecoder` as they arrive.
let mut decoder = FrameDecoder::new();
decoder.feed(frame).unwrap();
if let Ok(Some(LinkMessage::Handshake(manifest))) = decoder.try_parse() {
    println!("device {:?} reporting in", manifest.device_id);
}

See examples/loopback.rs for a complete, runnable end-to-end demo (handshake → telemetry → heartbeat) over the mock transport:

cargo run --example loopback --features std

Feature flags

  • default (no features): pure no_std, zero-alloc core — message types, postcard serialization, and the framing layer.
  • std: unlocks a mock TCP transport (transport::mock) for host-side integration testing in CI, without requiring physical hardware.
  • defmt: derives defmt::Format for every public type, for firmware that logs over RTT.

Crate layout

  • message — the LinkMessage envelope enum and its payload types (DeviceManifest, TelemetryFrame, LogLine, CommandPayload, AckPayload).
  • transport — the LinkTransport trait (native async fn in trait) and, under std, a mock transport implementation.
  • frame — the wire framing layer: magic bytes, length prefix, message type, postcard payload, and CRC32, including partial-read and resync-on-garbage handling.

Wire compatibility

postcard/serde serialize enums by declaration-order index, not by name. Capability, Arch, LogLevel, and MessageType all pin explicit discriminants for this reason — only append new variants at the end; reordering or removing one silently changes what already-deployed device firmware transmits.

DeviceManifest::protocol_version carries the crate's PROTOCOL_VERSION wire format version, separate from a device's own firmware version, so a host/device pair running mismatched tpt-e-link versions can detect it explicitly instead of getting opaque decode/CRC errors. Bump PROTOCOL_VERSION whenever a change isn't backward compatible. tests/wire_format.rs pins the exact encoded bytes of every message type as a regression guard against accidental wire-format changes.

MSRV

Rust 1.75+ (required for native async fn in traits).

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.