Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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, and device/info,
  • topics cover asynchronous server-to-client events.

Today the main topic is GPIO event streaming:

KindExampleDirectionPurpose
Endpointi2c/readhost → device → hostRequest/response RPC
Endpointdevice/infohost → device → hostCompatibility probe
Topicgpio/eventdevice → hostPush edge notifications

A short slice of the endpoint catalog looks like this:

PathWhat it does
pingEcho test payload
versionReport firmware version
device/infoReport firmware, schema, and capabilities
i2c/readRead from an I2C target
spi/transferFull-duplex SPI transfer
gpio/subscribeStart 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 the pico-de-gallo-internal crate version and let build.rs regenerate 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:

  • LegacyFirmware when the firmware is too old to support device/info, or
  • SchemaMismatch when 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:

  1. pico-de-gallo-internal,
  2. pico-de-gallo-firmware,
  3. pico-de-gallo-lib,
  4. pico-de-gallo-hal,
  5. pico-de-gallo-ffi,
  6. pico-de-gallo-app,
  7. pyco-de-gallo.

Important

release-please does 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.