> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cerulion.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Messages and schemas

> Use built-in ROS 2 message types in a node, and define a custom workspace schema.

Every node port carries a typed message. This guide shows how to use the built-in ROS 2 message types, how fixed and variable fields differ when you write them, and how to define your own workspace schema.

For the full type list and reference details, see the [message types reference](/cerulion/reference/message-types) and [graph and schema files reference](/cerulion/reference/graph-and-schema-files).

## Use a ROS 2 message type

Cerulion ships `native_ros2_messages`, generated from ROS 2 Jazzy `.msg` files. Import the type you need from its package module:

```rust theme={null}
use cerulion_core::prelude::*;
use native_ros2_messages::sensor_msgs::Image;
```

Reference a type as a port by giving it a field on the node struct:

```rust theme={null}
#[cerulion_node(period_ms = 33)]
#[derive(Default)]
struct Camera {
    #[output(data, encoding)]
    image: Image,
}
```

In a graph, schemas are written with a slash (`sensor_msgs/Image`). The colon form `sensor_msgs::Image` is also accepted and normalized to the slash form.

## Fixed vs variable fields

How you write a message field depends on its size:

* **Fixed primitive fields** (numbers, bytes, for example `height`, `width`, `is_bigendian`) live in a fixed section and are written directly. The macro derefs to that section and writes straight to shared memory.
* **Variable-length fields** (`string`, `T[]`, nested types) have no fixed size, so the macro routes them through a generated setter. You must list each one in the `#[output(...)]` attribute.

For `sensor_msgs/Image`, `height` and `width` are fixed, while `encoding` (a `string`) and `data` (a `uint8[]`) are variable:

```rust theme={null}
#[cerulion_node_impl]
impl Camera {
    fn tick(&mut self) -> Result<(), NodeError> {
        self.image.height = 480;        // fixed field, direct shared-memory write
        self.image.width = 640;         // fixed field, direct shared-memory write
        self.image.encoding = "rgb8";   // variable field — plain assignment, listed in #[output]
        Ok(())
    }
}
```

<Warning>
  A variable field that is **not** listed in `#[output(...)]` cannot be written
  through `self.<port>.<field>`. List simple variable fields by name
  (`#[output(data, encoding)]`), and nested-typed fields with the `complex(...)`
  form, for example `#[output(data, complex(header))]`.
</Warning>

The ROS 2 primitive types map to Rust as follows (a representative subset):

| ROS 2 type       | Rust type |
| ---------------- | --------- |
| `bool`           | `bool`    |
| `uint8` / `byte` | `u8`      |
| `int32`          | `i32`     |
| `uint32`         | `u32`     |
| `float64`        | `f64`     |
| `string`         | `String`  |
| `T[]`            | `Vec<T>`  |

## Define a custom schema

When no built-in type fits, define a workspace schema.

<Steps>
  <Step title="Create the schema file" icon="file-plus">
    `cerulion schema create <name>` writes `schemas/<PascalName>.yaml` with a skeleton.

    ```bash theme={null}
    cerulion schema create Reading
    ```

    ```text theme={null}
    Created schema 'Reading'
    ```

    The generated `schemas/Reading.yaml`:

    ```yaml theme={null}
    schemas:
      Reading:
        description: ""
        fields:
          # Add fields: type name
    ```
  </Step>

  <Step title="Add fields" icon="pen">
    Edit `schemas/Reading.yaml` to declare fields as `type name`. Use ROS 2
    primitive types; `T[]` marks a variable-length array.

    ```yaml theme={null}
    schemas:
      Reading:
        description: "A single sensor reading"
        fields:
          float64 value:
          uint64 timestamp:
    ```
  </Step>

  <Step title="Inspect it" icon="circle-info">
    `cerulion schema info <name>` reports the name, description, field count, and a hash.

    ```bash theme={null}
    cerulion schema info Reading
    ```

    ```text theme={null}
    Schema: Reading
    Description: A single sensor reading
    Fields: 2
    Hash: 0x0123456789abcdef
    ```
  </Step>
</Steps>

<Info>
  `schema info` first tries a ROS 2 `.msg` lookup for qualified names like
  `sensor_msgs::Image`, printing fields, Rust types, and the minimum wire size.
  If no `.msg` is found, it falls back to the workspace YAML schema and prints
  the description, fields, and FNV-1a hash shown above.
</Info>

Delete a schema you no longer need:

```bash theme={null}
cerulion schema delete Reading
```

```text theme={null}
Deleted schema 'Reading'
```

## Next steps

<CardGroup cols={2}>
  <Card title="Define a node" icon="box" href="/cerulion/guides/define-a-node" color="#0080FF">
    Put these message types to work on node ports.
  </Card>

  <Card title="Message types" icon="list" href="/cerulion/reference/message-types" color="#0080FF">
    Every available package and type.
  </Card>
</CardGroup>
