pamoja_serial/lib.rs
1#![cfg_attr(not(test), no_std)]
2
3//! Serial-line packet framing for the pamoja SDK.
4//!
5//! A serial line, whether a bare UART, an RS232/RS485 link, or a USB-serial bridge, is a
6//! raw stream of bytes with no notion of where one message ends and the next begins. The
7//! parts a robot or a field node talks to over that line, motor controllers, GPS
8//! receivers, LiDAR, and a long tail of cheap sensors, each send packets, so something has
9//! to mark packet boundaries in the stream and survive the noise of a long cable. The
10//! answer is byte stuffing: reserve one byte value as the frame delimiter and encode the
11//! payload so that value can never occur inside it.
12//!
13//! This crate is that framing layer, as pure logic with no serial port and no allocation:
14//!
15//! - [`slip`] - SLIP (RFC 1055), the simplest serial framing there is: an `END` byte ends
16//! a packet, and an escape pair carries `END` or the escape byte itself when they appear
17//! in the data. Ubiquitous and trivial, at a worst case of doubling the payload.
18//! - [`cobs`] - COBS (Consistent Overhead Byte Stuffing), which removes the zero byte from
19//! the payload so a single zero delimits packets unambiguously, at a bounded worst case
20//! of one byte of overhead per 254. This is the framing motor-control and robotics links
21//! reach for when the overhead has to stay small and predictable.
22//!
23//! Each module both [encodes](slip::encode) a packet into a frame and [decodes](slip::decode)
24//! one back, rejecting a frame that arrived corrupt. Each also offers a streaming decoder,
25//! [`slip::SlipDecoder`] and [`cobs::CobsDecoder`], that reassembles whole frames from the
26//! stream a byte at a time, because a UART hands an application arbitrary chunks rather
27//! than tidy packets. It is the streaming decoder, not the one-shot call, that a real
28//! serial read loop uses.
29//!
30//! Everything is exact byte work over caller-provided buffers, so the same framing runs on
31//! the smallest microcontroller hanging off the bus. Driving the serial line itself, the
32//! baud rate and the bytes on the wire, arrives with the hardware-I/O layer; this is the
33//! framing half ahead of it.
34//!
35//! # Examples
36//!
37//! ```
38//! use pamoja_serial::{cobs, slip};
39//!
40//! let payload = b"gps:37.42,-122.08";
41//! let mut framed = [0u8; 64];
42//! let mut restored = [0u8; 64];
43//!
44//! // SLIP frames a packet with a delimiter byte, escaping any that appear in the data.
45//! let n = slip::encode(payload, &mut framed)?;
46//! let m = slip::decode(&framed[..n], &mut restored)?;
47//! assert_eq!(&restored[..m], payload);
48//!
49//! // COBS frames the same packet with bounded overhead and a single zero delimiter.
50//! let n = cobs::encode(payload, &mut framed)?;
51//! assert_eq!(framed[n - 1], 0x00); // the frame delimiter
52//! let m = cobs::decode(&framed[..n], &mut restored)?;
53//! assert_eq!(&restored[..m], payload);
54//! # Ok::<(), pamoja_serial::SerialError>(())
55//! ```
56
57pub mod cobs;
58mod error;
59pub mod slip;
60
61pub use error::SerialError;