Message Format
The message_format defines the structure of messages exchanged between nodes through topics, services, and actions.
It is a map of field names to schema types. A native interface entry declares it inline in peppy.json5; an entry that carries a link_id into a contract or pairing document takes its shape from that document instead, using the same syntax there, and an inline message_format on such an entry is rejected at parse time. Such an entry may pin the length of arrays the document leaves generic through a refine block; see Refining the contract’s generic arrays.
message_format: { temperature: "f32", label: "string",}Each field value is a schema type: either a bare type token, or a structured schema with modifiers.
Primitive types
Section titled “Primitive types”A bare type string is the simplest form. These map directly to language-native types.
| Type | Alias | Rust type | Python type |
|---|---|---|---|
"bool" | bool | bool | |
"u8" | u8 | int | |
"u16" | u16 | int | |
"u32" | u32 | int | |
"u64" | u64 | int | |
"i8" | i8 | int | |
"i16" | i16 | int | |
"i32" | i32 | int | |
"i64" | i64 | int | |
"f32" | "float" | f32 | float |
"f64" | "double" | f64 | float |
"string" | "str" | String | str |
"bytes" | Vec<u8> | bytes | |
"time" | std::time::SystemTime | float |
Aliases can be used interchangeably with their canonical name (e.g. "float" is equivalent to "f32").
One field name is reserved: instance_id is rejected anywhere in a message_format, at the root or nested, because peppy’s transport metadata claims it.
Optional modifier
Section titled “Optional modifier”A field with a presence bit on the wire can be made optional by using the structured form with $optional:
error_msg: { $type: "string", $optional: true}| Field | Required | Description |
|---|---|---|
$type | Yes | string, bytes, or time (arrays and objects take $optional in their own structured forms) |
$optional | No | When true, the field may be absent. Defaults to false |
Scalar-copy types (bool, the integer types, f32, f64) carry no presence bit, so an absent value is indistinguishable from a zero one: $optional on them is rejected when the config is parsed. Model an optional scalar as an optional bytes/string or restructure the message.
Object
Section titled “Object”An object groups related fields into a nested structure. It generates a nested struct in Rust and a dataclass in Python.
header: { stamp: "time", frame_id: "u32"}| Field | Required | Description |
|---|---|---|
$type | No | Only valid value is "object". Can be omitted since the parser infers it from the structure |
$optional | No | When true, the entire object may be absent |
| other keys | No | Each additional key is a field with its own schema type |
Object fields can be any schema type, including arrays and nested objects:
sensor_reading: { $type: "object", header: { $type: "object", stamp: "time", frame_id: "u32" }, samples: { $type: "array", $items: "f32" }}An array represents a list of items of the same type.
distances: { $type: "array", $items: "f32"}| Field | Required | Description |
|---|---|---|
$type | Yes | Must be "array" |
$items | Yes | A primitive type or an object schema. Nested arrays (arrays of arrays) are not supported |
$length | No | Fixed number of elements. Only supported when $items is a numeric or boolean primitive (not supported for string, bytes, time, or object items). Omit for variable-length; a node implementing or consuming the document may then pin it through its entry’s refine block |
$optional | No | When true, the entire array may be absent |
Fixed-length array
Section titled “Fixed-length array”When $length is provided, the array has an exact number of elements:
position: { $type: "array", $items: "f32", $length: 3}This maps to [f32; 3] in Rust and list[float] in Python, where the generated code raises a ValueError when a list of any other length is encoded or decoded.
Variable-length array
Section titled “Variable-length array”Without $length, the array can contain any number of elements:
frame: { $type: "array", $items: "u8"}This maps to Vec<u8> in Rust and bytes in Python (for u8 items) or list[T] for other types.
Array of objects
Section titled “Array of objects”$items can be an object, allowing arrays of structured records:
frames: { $type: "array", $items: { $type: "object", name: "string", parent: "string", position: { $type: "array", $items: "i32", $length: 3 }, orientation: { $type: "array", $items: "i32", $length: 4 } }}This accepts messages like:
{ "frames": [ { "name": "link1", "parent": "world", "position": [0, 0, 1], "orientation": [1, 0, 0, 0] }, { "name": "link2", "parent": "link1", "position": [0, 1, 0], "orientation": [1, 0, 0, 0] } ]}Nesting rules
Section titled “Nesting rules”Schema types can be nested arbitrarily:
- Object fields can be primitives, arrays, or other objects
- Array items can be primitives or objects (nested arrays are not supported)
This enables complex hierarchical message structures. For example, a robotics transform tree:
message_format: { timestamp: "time", root_frame: "string", transforms: { $type: "array", $items: { $type: "object", name: "string", parent: "string", translation: { $type: "array", $items: "f64", $length: 3 }, rotation: { $type: "array", $items: "f64", $length: 4 } } }}Complete example
Section titled “Complete example”A full peppy.json5 topic using multiple schema types:
{ peppy_schema: "node/v1", manifest: { name: "arm_controller", tag: "v1" }, execution: { language: "rust", run_cmd: ["./target/release/arm_controller"], }, interfaces: { topics: { emits: [ { name: "arm_state", qos_profile: "sensor_data", message_format: { timestamp: "time", joint_positions: { $type: "array", $items: "f64", $length: 6 }, joint_velocities: { $type: "array", $items: "f64", $length: 6 }, end_effector: { $type: "object", position: { $type: "array", $items: "f64", $length: 3 }, orientation: { $type: "array", $items: "f64", $length: 4 }, gripper_open: "bool" } } } ] } }}