embedded-rpc
no_std request/response synchronization for async tasks (Embassy-style executors). This is not a wire protocol: it is an in-memory channel built on embassy_sync mutexes and async wakers.
Crate rename: This crate was previously published on crates.io as embassy-rpc. Depend on embedded-rpc and use the embedded_rpc crate root instead of embassy_rpc.
What this crate does
- Single in-flight RPC: At most one request and one response are queued in the internal state machine.
- Client:
RpcService::requestsends a request and awaitsResult<Resp, RequestDroppedError>. - Server:
RpcService::servewaits for the next request and returns(Req, ServedRequest). The server must callServedRequest::respondwith a success value, or drop the handle to signal failure to the client. - Multiple clients: Several tasks may call
request(), but they are serialized: a second caller blocks until the previous RPC fully completes (response delivered and the client slot released).
Usage
- Create a shared
RpcService<M, Req, Resp>with a mutex typeM: RawMutexappropriate for your platform (e.g.CriticalSectionRawMutexon many MCUs). - Run a server task that loops on
serve().await, inspects or mutates theReqvalue from the tuple, then callsrespond(resp)on theServedRequest. - Run client task(s) that call
request(req).awaitand handleOk(resp)vsErr(RequestDroppedError).
Req and Resp are yours: they can be integers, enums, or types that borrow data from the client for the duration of the call (see the example below).
Example (simple types)
use RpcService;
use CriticalSectionRawMutex;
static SERVICE: = new;
async
async
Example (client lends a buffer)
Req can carry &mut [u8] (or other borrows) so the server writes into the client’s memory for the duration of one RPC. The RpcService must live no longer than the borrowed data, so keep it on the stack (or in an owning struct) together with the buffer—see the server_writes_through_client_buffer_slice integration test in this repository.
use RpcService;
use CriticalSectionRawMutex;
async
async
Run server_task and client_task concurrently on the same service (spawn or join, depending on your executor).
Considerations
RequestDroppedError: TreatErr(RequestDroppedError)as “no normal response” (server droppedServedRequestwithout callingrespond). Do not assumeOkif the server may abort handling.ServedRequest: Afterrespond, the completion handle is consumed. Dropping it without callingrespondcompletes the RPC withRequestDroppedErrorfor the client.- Lifetimes: If
Reqborrows from the client (e.g.&mut [u8]), theRpcServicevalue must not outlive those borrows. A stack-scoped service is often the right shape;Arc<RpcService<...>>with stack-borrowed payloads is usually incompatible without restructuring. - No built-in timeouts: Compose with your executor’s timeout helpers if you need deadlines on
request()orserve(). - Integration tests and
ThreadModeRawMutex: Withembassy-sync’sstdfeature,ThreadModeRawMutexmay only be used from a thread named"main". The crate’s tests spawn such a thread; on embedded firmware, prefer a mutex that matches your interrupt/preemption model.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.