Wire Protocol & Schema Versioning
The wire protocol is the most important compatibility contract in Pico de
Gallo. All protocol types live in pico-de-gallo-internal, and both the host
and the firmware compile against that crate.
postcard encoding rules
Pico de Gallo uses postcard for compact binary encoding. That brings one rule that every contributor must understand.
Important
postcard encodes enum variants by variant index, not by the numeric value in
#[repr(...)]. Reordering variants in a wire-visible enum is a silent ABI break.
So this is safe:
- append a new enum variant at the end.
And this is breaking:
- reorder variants,
- remove a variant,
- rename a variant that old peers still expect.
The warning comments in pico-de-gallo-internal are there for a reason; keep
them.
The shared protocol crate
pico-de-gallo-internal defines:
- endpoint marker types,
- request and response structs,
- topic message types,
- protocol constants like
MAX_TRANSFER_SIZE, - schema-version constants generated at build time.
The crate also uses the use-std feature to switch certain response types
between owned host buffers and borrowed firmware buffers:
- host build:
Vec<u8> - firmware build:
&[u8]
That lets the host own received data while the firmware can answer from a shared scratch buffer without heap allocation.
Endpoints and topics
Endpoints are normal request/response RPCs declared with endpoints!. Topics
are push-style messages declared with topics!.
In practice:
- endpoints cover commands like
i2c/read,spi/transfer, anddevice/info, - topics cover asynchronous server-to-client events.
Today the main topic is GPIO event streaming:
| Kind | Example | Direction | Purpose |
|---|---|---|---|
| Endpoint | i2c/read | host → device → host | Request/response RPC |
| Endpoint | device/info | host → device → host | Compatibility probe |
| Topic | gpio/event | device → host | Push edge notifications |
A short slice of the endpoint catalog looks like this:
| Path | What it does |
|---|---|
ping | Echo test payload |
version | Report firmware version |
device/info | Report firmware, schema, and capabilities |
i2c/read | Read from an I2C target |
spi/transfer | Full-duplex SPI transfer |
gpio/subscribe | Start GPIO event monitoring |
For the full list, see the Endpoint Catalog.
Schema versioning
The schema version constants are not handwritten. pico-de-gallo-internal
generates SCHEMA_VERSION_MAJOR, SCHEMA_VERSION_MINOR, and
SCHEMA_VERSION_PATCH in build.rs from the crate’s [package].version.
Caution
Do not edit
SCHEMA_VERSION_*constants directly. Bump thepico-de-gallo-internalcrate version and letbuild.rsregenerate them.
Before 1.0, the minor version is the breaking axis. After 1.0, that role moves to the major version.
That means a pre-1.0 bump is required when you:
- add or remove an endpoint or topic,
- change a request or response type,
- append a new wire enum variant.
Host/firmware compatibility checks
The host library exposes PicoDeGallo::validate(). It calls device/info,
reads the firmware’s schema version, and rejects mismatches early.
If validation fails, the host returns:
LegacyFirmwarewhen the firmware is too old to supportdevice/info, orSchemaMismatchwhen the host and firmware disagree on the schema version.
This turns an otherwise confusing runtime failure into an explicit compatibility error.
Lockstep releases for protocol changes
A wire change is never just one crate. Per the project rules, the same release cycle must update:
pico-de-gallo-internal,pico-de-gallo-firmware,pico-de-gallo-lib,pico-de-gallo-hal,pico-de-gallo-ffi,pico-de-gallo-app,pyco-de-gallo.
Important
release-pleasedoes not know that the protocol crate and firmware are wire-coupled. Lockstep is enforced by contributors, not by automation.
For contributor policy and the full compatibility rules, see
AGENTS.md.