Skip to content

Commit

Permalink
Rework basically the entire protocol (#53)
Browse files Browse the repository at this point in the history
This is a major change to postcard-rpc. This is a **very breaking wire change!**

In particular, this PR changes the following:

## Server Rework

Signifcantly reworks how "servers" are implemented, removing the previous embassy-usb-specific one in favor of more reusable parts, that allow for implementing a server generically over different transports. This new version still has an implementation for embassy-usb 0.3, but ALSO provides a channel-based implementation for testing, and I am likely to port a TCP-based one I have in `poststation` as well. This unlocks the ability to reuse the bulk of the existing code for supporting other transports, like UART, SPI, I2C, Ethernet, or even over radio.

## `define_dispatch` macro rework

As part of the Server Rework, I also mostly rewrote the `define_dispatch!` macro, which now can be used with ANY transport, not just embassy-usb.

This change also now allows servers to define topic handlers, so incoming published messages can be dispatched similar to endpoint dispatching. CC #15 

## Automatic Key Shrinking

Previously, we would always send the full 8-byte "hash of the path and schema" ID in every message, as well as checking at compile time whether there is a collision or not.

This PR takes that up a notch, now calculating the *smallest* hash key we can use (1, 2, 4, or 8 bytes) automatically at compile time, and uses that as our "native" mapping. This is similar to the concept of "Perfect Hash Functions".

## Variable Sequence Number size

Additionally, the client can now send sequence numbers of 1, 2, or 4 bytes. Previously, sequence numbers were a `varint(u32)`. Servers will always respond back with the same sequence number they received when replying to requests.

## Completely redo message headers

As we now have variable sized keys and sequence numbers, headers can now scale dynamically to the necessary size. CC #51 

Before, we had 8 byte keys (fixed) and 1-5 bytes (`varint(u32)`), meaning headers were between **9-13 bytes**.

Now, we use one byte as a discriminant, containing the key and seqno len, as well as a 4-bit version field, as well as the variable key and variable sequence number. This now means that headers are **3-13 bytes** (1B discriminant + 1/2/4/8B key + 1/2/4B sequence number), and will be **3 bytes in many common cases** where it is not necessary to disambiguate more than 256 in-flight messages (via sequence numbers) or 256 endpoints (though the liklihood of having a collision at 8 bits is higher than that due to the birthday problem).

When a Client first connects to a Server, it will always start by sending an 8B key. If the Server replies with a shorter key, the Client will then switch to using keys of that size. It is not necessary to ever hardcode what size keys are necessary, as this is calculated when the Server is compiled, and is automatically detected by the Client.

In general:

* The CLIENT (usually the PC) is in charge of picking the size of the sequence number
* The SERVER (usually the MCU) is in charge of picking the size of the message keys
  • Loading branch information
jamesmunns authored Oct 27, 2024
1 parent 4b8b3a3 commit 5d0ab79
Show file tree
Hide file tree
Showing 34 changed files with 4,964 additions and 2,021 deletions.
30 changes: 4 additions & 26 deletions example/firmware/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 83 additions & 41 deletions example/firmware/src/bin/comms-01.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,77 +3,119 @@

use defmt::info;
use embassy_executor::Spawner;
use embassy_rp::{
peripherals::USB,
usb::{self, Driver, Endpoint, Out},
};

use embassy_rp::{peripherals::USB, usb};
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;

use embassy_usb::UsbDevice;
use embassy_usb::{Config, UsbDevice};
use postcard_rpc::{
define_dispatch,
target_server::{buffers::AllBuffers, configure_usb, example_config, rpc_dispatch},
WireHeader,
header::VarHeader,
server::{
impls::embassy_usb_v0_3::{
dispatch_impl::{WireRxBuf, WireRxImpl, WireSpawnImpl, WireStorage, WireTxImpl},
PacketBuffers,
},
Dispatch, Server,
},
};

use static_cell::ConstStaticCell;
use workbook_fw::{get_unique_id, Irqs};
use workbook_icd::PingEndpoint;
use workbook_icd::{PingEndpoint, ENDPOINT_LIST, TOPICS_IN_LIST, TOPICS_OUT_LIST};
use {defmt_rtt as _, panic_probe as _};

pub struct Context;

static ALL_BUFFERS: ConstStaticCell<AllBuffers<256, 256, 256>> =
ConstStaticCell::new(AllBuffers::new());
type AppDriver = usb::Driver<'static, USB>;
type AppStorage = WireStorage<ThreadModeRawMutex, AppDriver, 256, 256, 64, 256>;
type BufStorage = PacketBuffers<1024, 1024>;
type AppTx = WireTxImpl<ThreadModeRawMutex, AppDriver>;
type AppRx = WireRxImpl<AppDriver>;
type AppServer = Server<AppTx, AppRx, WireRxBuf, MyApp>;

pub struct Context {}
static PBUFS: ConstStaticCell<BufStorage> = ConstStaticCell::new(BufStorage::new());
static STORAGE: AppStorage = AppStorage::new();

fn usb_config() -> Config<'static> {
let mut config = Config::new(0x16c0, 0x27DD);
config.manufacturer = Some("OneVariable");
config.product = Some("ov-twin");
config.serial_number = Some("12345678");

// Required for windows compatibility.
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
config.device_class = 0xEF;
config.device_sub_class = 0x02;
config.device_protocol = 0x01;
config.composite_with_iads = true;

config
}
define_dispatch! {
dispatcher: Dispatcher<
Mutex = ThreadModeRawMutex,
Driver = usb::Driver<'static, USB>,
Context = Context
>;
PingEndpoint => blocking ping_handler,
app: MyApp;
spawn_fn: spawn_fn;
tx_impl: AppTx;
spawn_impl: WireSpawnImpl;
context: Context;

endpoints: {
list: ENDPOINT_LIST;

| EndpointTy | kind | handler |
| ---------- | ---- | ------- |
| PingEndpoint | blocking | ping_handler |
};
topics_in: {
list: TOPICS_IN_LIST;

| TopicTy | kind | handler |
| ---------- | ---- | ------- |
};
topics_out: {
list: TOPICS_OUT_LIST;
};
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
// SYSTEM INIT
info!("Start");

let mut p = embassy_rp::init(Default::default());
let unique_id = get_unique_id(&mut p.FLASH).unwrap();
let unique_id = defmt::unwrap!(get_unique_id(&mut p.FLASH));
info!("id: {=u64:016X}", unique_id);

// USB/RPC INIT
let driver = usb::Driver::new(p.USB, Irqs);
let mut config = example_config();
config.manufacturer = Some("OneVariable");
config.product = Some("ov-twin");
let buffers = ALL_BUFFERS.take();
let (device, ep_in, ep_out) = configure_usb(driver, &mut buffers.usb_device, config);
let dispatch = Dispatcher::new(&mut buffers.tx_buf, ep_in, Context {});
let pbufs = PBUFS.take();
let config = usb_config();

spawner.must_spawn(dispatch_task(ep_out, dispatch, &mut buffers.rx_buf));
let context = Context;
let (device, tx_impl, rx_impl) = STORAGE.init(driver, config, pbufs.tx_buf.as_mut_slice());
let dispatcher = MyApp::new(context, spawner.into());
let vkk = dispatcher.min_key_len();
let mut server: AppServer = Server::new(
&tx_impl,
rx_impl,
pbufs.rx_buf.as_mut_slice(),
dispatcher,
vkk,
);
spawner.must_spawn(usb_task(device));
}

/// This actually runs the dispatcher
#[embassy_executor::task]
async fn dispatch_task(
ep_out: Endpoint<'static, USB, Out>,
dispatch: Dispatcher,
rx_buf: &'static mut [u8],
) {
rpc_dispatch(ep_out, dispatch, rx_buf).await;
loop {
// If the host disconnects, we'll return an error here.
// If this happens, just wait until the host reconnects
let _ = server.run().await;
}
}

/// This handles the low level USB management
#[embassy_executor::task]
pub async fn usb_task(mut usb: UsbDevice<'static, Driver<'static, USB>>) {
pub async fn usb_task(mut usb: UsbDevice<'static, AppDriver>) {
usb.run().await;
}

fn ping_handler(_context: &mut Context, header: WireHeader, rqst: u32) -> u32 {
info!("ping: seq - {=u32}", header.seq_no);
// ---

fn ping_handler(_context: &mut Context, _header: VarHeader, rqst: u32) -> u32 {
info!("ping");
rqst
}
Loading

0 comments on commit 5d0ab79

Please sign in to comment.