#serde #cobs #framing #micro-controller

no-std postcard-rpc

A no_std + serde compatible RPC library for Rust

22 releases (9 breaking)

0.10.7 Nov 15, 2024
0.10.1 Oct 31, 2024
0.6.0 Jul 29, 2024
0.4.0 Feb 23, 2024
0.3.1 Nov 26, 2023

#148 in Embedded development

Download history 151/week @ 2024-08-02 152/week @ 2024-08-09 142/week @ 2024-08-16 146/week @ 2024-08-23 214/week @ 2024-08-30 151/week @ 2024-09-06 190/week @ 2024-09-13 429/week @ 2024-09-20 281/week @ 2024-09-27 212/week @ 2024-10-04 368/week @ 2024-10-11 437/week @ 2024-10-18 555/week @ 2024-10-25 1225/week @ 2024-11-01 386/week @ 2024-11-08 546/week @ 2024-11-15

2,790 downloads per month
Used in 4 crates

MIT/Apache

295KB
5.5K SLoC

Postcard RPC

A host (PC) and client (MCU) library for handling RPC-style request-response types.

See overview.md for documentation.

See the postcard-rpc book for a walk-through example.

You can also watch James' RustNL talk for a video explainer of what this crate does.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.


lib.rs:

The goal of postcard-rpc is to make it easier for a host PC to talk to a constrained device, like a microcontroller.

See the repo for examples

Architecture overview

                ┌──────────┐      ┌─────────┐         ┌───────────┐
                │ Endpoint │      │ Publish │         │ Subscribe │
                └──────────┘      └─────────┘         └───────────┘
                  │     ▲       message│                │        ▲
   ┌────────┐ rqst│     │resp          │       subscribe│        │messages
 ┌─┤ CLIENT ├─────┼─────┼──────────────┼────────────────┼────────┼──┐
 │ └────────┘     ▼     │              ▼                ▼        │  │
 │       ┌─────────────────────────────────────────────────────┐ │  │
 │       │                     HostClient                      │ │  │
 │       └─────────────────────────────────────────────────────┘ │  │
 │         │                  │              ▲           │       |  │
 │         │                  │              │           │       │  │
 │         │                  │              │           ▼       │  │
 │         │                  │      ┌──────────────┬──────────────┐│
 │         │                  └─────▶│ Pending Resp │ Subscription ││
 │         │                         └──────────────┴──────────────┘│
 │         │                                 ▲              ▲       │
 │         │                                 └───────┬──────┘       │
 │         ▼                                         │              │
 │      ┌────────────────────┐            ┌────────────────────┐    │
 │      ││ Task: out_worker  │            │  Task: in_worker  ▲│    │
 │      ├┼───────────────────┤            ├───────────────────┼┤    │
 │      │▼  Trait: WireTx    │            │   Trait: WireRx   ││    │
 └──────┴────────────────────┴────────────┴────────────────────┴────┘
                   │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ▲
                   │   The Server + Client WireRx    │
                   │ │ and WireTx traits can be    │ │
                   │   impl'd for any wire           │
                   │ │ transport like USB, TCP,    │ │
                   │   I2C, UART, etc.               │
                   ▼ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │
  ┌─────┬────────────────────┬────────────┬────────────────────┬─────┐
  │     ││  Trait: WireRx    │            │   Trait: WireTx   ▲│     │
  │     ├┼───────────────────┤            ├───────────────────┼┤     │
  │     ││      Server       │       ┌───▶│       Sender      ││     │
  │     ├┼───────────────────┤       │    └────────────────────┘     │
  │     │▼ Macro: Dispatch   │       │               ▲               │
  │     └────────────────────┘       │               │               │
  │    ┌─────────┐ │ ┌──────────┐    │ ┌───────────┐ │ ┌───────────┐ │
  │    │  Topic  │ │ │ Endpoint │    │ │ Publisher │ │ │ Publisher │ │
  │    │   fn    │◀┼▶│ async fn │────┤ │   Task    │─┼─│   Task    │ │
  │    │ Handler │ │ │ Handler  │    │ └───────────┘ │ └───────────┘ │
  │    └─────────┘ │ └──────────┘    │               │               │
  │    ┌─────────┐ │ ┌──────────┐    │ ┌───────────┐ │ ┌───────────┐ │
  │    │  Topic  │ │ │ Endpoint │    │ │ Publisher │ │ │ Publisher │ │
  │    │async fn │◀┴▶│   task   │────┘ │   Task    │─┴─│   Task    │ │
  │    │ Handler │   │ Handler  │      └───────────┘   └───────────┘ │
  │    └─────────┘   └──────────┘                                    │
  │ ┌────────┐                                                       │
  └─┤ SERVER ├───────────────────────────────────────────────────────┘
    └────────┘

Defining a schema

Typically, you will define your "wire types" in a shared schema crate. This crate essentially defines the protocol used between two or more devices.

A schema consists of a couple of necessary items:

Wire types

We will need to define all of the types that we will use within our protocol. We specify normal Rust types, which will need to implement or derive three important traits:

  • serde's Serialize trait - which defines how we can convert a type into bytes on the wire
  • serde's Deserialize trait - which defines how we can convert bytes on the wire into a type
  • postcard_schema's Schema trait - which generates a reflection-style schema value for a given type.

Endpoints

Now that we have some basic types that will be used on the wire, we need to start building our protocol. The first thing we can build are [Endpoint]s, which represent a bidirectional "Request"/"Response" relationship. One of our devices will act as a Client (who makes a request, and receives a response), and the other device will act as a Server (who receives a request, and sends a response). Every request should be followed (eventually) by exactly one response.

An endpoint consists of:

  • The type of the Request
  • The type of the Response
  • A string "path", like an HTTP URI that uniquely identifies the endpoint.

Topics

Sometimes, you would just like to send data in a single direction, with no response. This could be for reasons like asynchronous logging, blindly sending sensor data periodically, or any other reason you can think of.

Topics have no "client" or "server" role, either device may decide to send a message on a given topic.

A topic consists of:

  • The type of the Message
  • A string "path", like an HTTP URI that uniquely identifies the topic.

Dependencies

~2–35MB
~547K SLoC