Stow Schema
Overview
The Stow schema is defined by a stow.yaml file inside your application’s root directory. The schema includes user-defined enums, structs, item categories, and the Stow items themselves. This stow.yaml can be composed with additional YAML fragments and board-specific overrides, see Fragments & Overrides.
The Stow is automatically generated by the build system based on the stow.yaml. The generated code can be found within the build folder under gantry_generated.
Schema File
The stow.yaml file contains the following sections:
enums: Enum types and their possible valuesstructs: Custom struct types and their fieldsroles: Possible external device rolescategories: Categories for organizing itemsitems: The items in the Stow
The description fields do not impact the firmware, they just serve as comments in the schema file and within the generated source code.
Enums
The enums sections lists custom enumerations and their named values. Enum items reference one of these defined enums.
Example:
enums:
Toggle:
description: "A toggle to enable or disable"
values:
- name: DISABLED
value: 0
- name: ENABLED
value: 1
SpiChipSelect:
description: "A SPI chip select index"
values:
- name: SPI_CS0
value: 0
- name: SPI_CS1
value: 1
The enum values can be accessed by including generated_stow_enums.h. For example, enum Toggle will have the value Toggle_ENABLED.
Structs
Structs are composite types with named fields. Fields can be any Stow type, including nested structs. The constraints for each field are defined within the struct definition.
Example:
structs:
- name: SpiBuffer
description: "A SPI buffer containing a chip select and data"
fields:
- name: CS
type: ENUM
constraints:
- enum: SpiChipSelect
- name: Buffer
type: BUFFER
constraints:
- min_len: 0
- max_len: 512
The structs can be accessed by including the relevant generated_struct_*.h (e.g. generated_struct_SpiBuffer.h). In this example, the struct is accessible in C code a SpiBuffer_t.
Roles
Items in the Stow have role-based access permissions. The roles section defines possible external client roles. There can be up to 16 roles defined.
Example:
roles:
- User
- Clinician
- Developer
The roles are converted into a bitfield (included via generated_stow_items.h). In this example, an external client may have the roles STOW_ROLE_CLINICIAN | STOW_ROLE_DEVELOPER indicating that the client is both a clinician and a developer.
Categories
Categories are strings used to group related items. Each item may belong to multiple categories. Categories do not serve a functional purpose within the firmware. Instead, external devices reading the Stow will see the categories for each item. This information can be used to organize items (e.g. in a user interface).
categories:
- System
- BLE
- SPI
Items
Items are the individual data entires in the Stow. Each item has:
name: The name of the data item.categories: List of categories that the item belongs to.type: The datatype of the item.storage: Storage mode for the item.permissions:readandwriteaccess controls. Each is a list of roles able to access the item. TheANYkeyword can be used to allow access to all roles. TheINTERNALkeyword can be used to restrict access from all roles; only the firmware can access these items.default: The default value of the item.constraints: The item’s value constraints.custom_validate(optional): Name of a C function providing additional validation beyond type-level constraints.custom_get(optional): Name of a C function to override the default get behavior.custom_set(optional): Name of a C function to override the default set behavior.
After generation, items are accessed via the STOW_ID_<NAME> enum (e.g. STOW_ID_DEVICE_NAME). All IDs are defined in generated_stow_items.h.
Built-in Items
The first two items in the Stow built-in and included by default, they are not defined in the stow.yaml. The first is the StowHash, a SHA256 hash string of the current Stow description. The Stow hash allows clients cache the Stow description. The second is the FirmwareVersion, a string derived from the Zephyr VERSION file.
Custom Interface
The custom_get, custom_set, and custom_validate fields each accept a C identifier naming a function that the application provides. For when and how these functions are called, see the API docs.
Each name must be a valid C identifier. Any field that is omitted defaults to the standard interface behavior.
- name: SensorReading
description: "Live sensor reading (computed on demand)"
categories: [Sensor]
type: INT
storage: EPHEMERAL
permissions:
- read: ANY
- write: INTERNAL
default: 0
constraints:
- min: -1000
- max: 1000
custom_get: sensor_reading_get
- name: TargetTemperature
description: "Target temperature with application-specific validation"
categories: [System]
type: FLOAT
storage: PERSISTENT
permissions:
- read: ANY
- write: [User]
default: 20.0
constraints:
- min: 10.0
- max: 40.0
custom_validate: target_temp_validate
custom_set: target_temp_set
Constraints by Type
Integer / Float
Integer and float constraints define a range of valid values.
constraints:
- min: -100
- max: 100
String / Byte Array / Buffer
String, Byte Array, and Buffer constraints define the minimum and maximum lengths for the buffer.
constraints:
- min_len: 0
- max_len: 64
Enum
Enum constraints reference one of the defined enums. The referenced enum definition contains a list of possible values.
constraints:
- enum: MyEnumName
Struct
Struct constraints reference one of the defined structs. The referenced struct contains the constraints for each field in its definition.
constraints:
- struct: MyStructName
Example:
items:
- name: DeviceName
description: "The name of the device"
categories: [System]
type: STRING
storage: PERSISTENT
permissions:
- read: ANY
- write: [User]
default: "Gantry"
constraints:
- min_len: 3
- max_len: 25
- name: SerialNumber
description: "The device serial number"
categories: [System]
type: STRING
storage: TOFU
permissions:
- read: ANY
- write: [Developer]
default: "0000000000000000"
constraints:
- min_len: 16
- max_len: 16
- name: BleConnectionState
description: "The current BLE connection state"
categories: [System, BLE]
type: ENUM
storage: EPHEMERAL
permissions:
- read: ANY
- write: INTERNAL
default: DISCONNECTED
constraints:
- enum: BleConnectionState
- name: TestInt
description: "Test int value"
categories: [System, Test]
type: INT
storage: EPHEMERAL
permissions:
- read: [User, Clinician]
- write: [Clinician]
default: 0
constraints:
- min: -100
- max: 100
custom_validate: test_custom_validator
- name: TestFloat
description: "Test float value"
categories: [System, Test]
type: FLOAT
storage: EPHEMERAL
permissions:
- read: ANY
- write: ANY
default: 21.5
constraints:
- min: 15.0
- max: 30.0
- name: TestBytes
description: "Test bytes"
categories: [Test]
type: BYTE_ARRAY
storage: EPHEMERAL
permissions:
- read: ANY
- write: ANY
default:
[0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
constraints:
- min_len: 12
- max_len: 12
- name: TestBuffer
description: "Test buffer"
categories: [Test]
type: BUFFER
storage: EPHEMERAL
permissions:
- read: ANY
- write: ANY
default: []
constraints:
- min_len: 0
- max_len: 20
- name: SpiTxBuffer
description: "SPI TX buffer"
categories: [SPI]
type: STRUCT
storage: EPHEMERAL
permissions:
- read: ANY
- write: ANY
default:
- field: CS
value: SPI_CS0
- field: Buffer
value: []
constraints:
- struct: SpiBuffer
Fragments & Overrides
A Stow definition can be assembled from multiple YAML fragments instead of a single stow.yaml. This allows a module’s items live in their own file, be reused across apps, and be overridden per app or per board.
By convention, fragment files are named *.stow.yaml. The fully composed definition is written to merged.stow.yaml.
Note
CONFIG_GANTRY_STOW requires CMake >= 3.29 to defer fragment composition such that an app (or other modules) can define additional stow fragments.
Registering a fragment
Call gantry_stow_add_fragment(<path>) from your app’s CMakeLists.txt:
gantry_stow_add_fragment(${CMAKE_CURRENT_SOURCE_DIR}/ble.stow.yaml)
A fragment has the same shape as stow.yaml (enums, structs, roles, categories, items), but need not be complete.
Merge order
Fragments are composed lowest to highest precedence:
Fragments registered via
gantry_stow_add_fragment(), in registration order.The app’s own
stow.yamlcontent. This is applied on top of all registered fragments, so it’s the natural place to override values for a specific app.An auto-discovered board fragment,
boards/<BOARD>.stow.yaml. Always highest precedence, if present.
Overrides
Enums, structs, and items are matched by name across fragments:
Duplicate definitions are merged.
If a later fragment redefines something differently, only the fields it specifies are patched onto the earlier definition and unspecified fields are inherited.
This means a fragment can define an item in full, and a later layer can override just what it needs to change:
# ble.stow.yaml
items:
- name: DeviceName
description: "The name of the device"
categories: [General]
type: STRING
storage: PERSISTENT
permissions:
- read: ANY
- write: [Session]
default: "BleDevice"
constraints:
- min_len: 3
- max_len: 25
# stow.yaml
items:
- name: DeviceName
default: "Gantry"
roles and categories are merged as an ordered union of unique strings.