The fixo.json schema
File format for Fixo types, presets, and templates
Reference for the file format every Fixo resource is stored in. Each fixo.json is self-contained — it declares its kind via fixoType, supplies metadata, and references sibling source files by relative path. The folder name is cosmetic; only the file content matters to FixoLibrary.
Discovery in one sentence
FixoLibrary::scan(builtinDir, customDir)walks both trees recursively, parses everyfixo.jsonit finds, and routes each one to the matching Book byfixoTypevalue.
Common fields (all fixoType values)
| Field | Type | Required | Description |
|---|---|---|---|
fixoType |
string | yes | Discriminator: "actuator-type", "sensor-type", "lobe-type", "board-type", "preset", "stanza-template", "preset-template" |
name |
string | yes | Stable id used in cross-references (kebab-case by convention) |
title |
string | no | Display title (defaults to name) |
description |
string | no | Short user-facing description |
icon |
string | no | Filename of an SVG sibling, or a :/... resource path |
sources |
string[] | no | Sibling files (.hpp, .cpp, .h, .c) that the firmware generator includes when this resource is used |
mainHeader |
string | no | One of sources — the header the generated firmware actually #includes |
builtin |
bool | no | true for resources that ship in the libfixo qrc; false (default) for user-authored files |
fixoType: "actuator-type"
{
"fixoType": "actuator-type",
"name": "rc-servo",
"title": "RC servo",
"description": "Standard hobby RC servo (PWM, 500-2500 µs)",
"icon": "rc-servo.svg",
"sources": ["rc-servo.hpp"],
"mainHeader": "rc-servo.hpp",
"builtin": true,
"pinCount": 1,
"pinPurposes": ["pwm"],
"valueRep": {
"totalBits": 16,
"fractionalBits": 0,
"isSigned": false,
"isFloat": false
}
}
| Field | Type | Required | Description |
|---|---|---|---|
pinCount |
int | yes | How many pins one instance occupies |
pinPurposes |
string[] | yes | Length must equal pinCount; e.g. ["pwm"], ["pwm", "direction"] |
valueRep.totalBits |
int | yes | Total bits in the wire-encoded value |
valueRep.fractionalBits |
int | no | For fixed-point values; default 0 |
valueRep.isSigned |
bool | no | Default false |
valueRep.isFloat |
bool | no | If true, the value is IEEE-754 float; ignores fractional bits |
fixoType: "sensor-type"
{
"fixoType": "sensor-type",
"name": "encoder",
"title": "Rotary encoder",
"sources": ["encoder.hpp"],
"mainHeader": "encoder.hpp",
"builtin": true,
"pinCount": 2,
"pinPurposes": ["channel_a", "channel_b"],
"sensorType": "encoder"
}
| Field | Type | Required | Description |
|---|---|---|---|
pinCount |
int | yes | Pins per instance |
pinPurposes |
string[] | yes | One label per pin |
sensorType |
string | yes | One of: digital_input, analog_input, encoder, counter, limit_switch, tachometer |
fixoType: "lobe-type"
{
"fixoType": "lobe-type",
"name": "legged-gait",
"title": "Legged gait engine",
"sources": ["legged-gait.hpp", "inverse-kinematics.hpp"],
"mainHeader": "legged-gait.hpp",
"builtin": true,
"placement": "both",
"inputParameters": ["speed", "steering"],
"outputPorts": [
{"name": "leg_0_coxa", "tags": ["leg_0", "coxa", "joint"], "min": -1.0, "max": 1.0},
{"name": "leg_0_femur", "tags": ["leg_0", "femur", "joint"], "min": -1.0, "max": 1.0}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
placement |
enum | yes | "board-only", "native-only", or "both" |
inputParameters |
string[] | no | Names of runtime-tunable parameters (e.g. speed, steering) |
inputPorts |
port[] | no | Lobe inputs (used by the control widget) |
outputPorts |
port[] | no | Lobe outputs (matched against actuators by tags) |
A port object has the shape { "name": str, "tags": [str], "min": number, "max": number, "direction": "Input" | "Output" }. See the data model.
fixoType: "board-type"
{
"fixoType": "board-type",
"name": "arduino-uno",
"title": "Arduino Uno",
"description": "ATmega328P-based board (14 digital, 6 analog)",
"icon": "arduino-uno.svg",
"sources": ["arduino-uno.hpp"],
"mainHeader": "arduino-uno.hpp",
"builtin": true,
"boardName": "arduino-uno"
}
| Field | Type | Required | Description |
|---|---|---|---|
boardName |
string | yes | Name of the matching record in libboard's board database (provides pin map, MCU details, USB IDs, bootloader config) |
fixoType: "preset"
{
"fixoType": "preset",
"name": "hexy-explorer",
"version": 1,
"board": "arduino-mega2560",
"actuators": [
{
"type": "rc-servo",
"name": "leg_0_coxa",
"pin": 22,
"trim": 0,
"min": -90, "max": 90,
"reverse": false,
"semanticTags": ["leg_0", "coxa"]
}
],
"sensors": [],
"stanzas": [
{
"id": "walk",
"lobeType": "legged-gait",
"namedWidget": "JoystickWidget",
"mapping": [
{"lobePort": "leg_0_coxa", "actuatorIndex": 0}
],
"inputs": [
{"name": "speed", "min": 0.0, "max": 1.0, "default": 0.5},
{"name": "steering", "min": -1.0, "max": 1.0, "default": 0.0}
]
}
]
}
Field-by-field semantics live in Fixo Data Model. The schema validator only enforces the shapes; cross-references (e.g. that type resolves to a known actuator type) are checked at preset-load time.
fixoType: "stanza-template"
{
"fixoType": "stanza-template",
"name": "differential-drive",
"title": "Differential drive",
"description": "Two-wheel tank steering with throttle and turn inputs",
"builtin": true,
"lobeType": "wheeled",
"namedWidget": "CarSteeringWidget",
"expectedOutputs": [
{"name": "left_motor", "tags": ["wheel", "left", "motor"]},
{"name": "right_motor", "tags": ["wheel", "right", "motor"]}
],
"expectedInputs": [
{"name": "throttle", "min": -1.0, "max": 1.0},
{"name": "steering", "min": -1.0, "max": 1.0}
]
}
When instantiated against a preset, FixoStanzaTemplate::instantiate(stanzaId, actuators) runs auto-connect (Jaccard tag-similarity, greedy one-to-one assignment) and returns a fully-populated FixoStanza with the mapping set. See Stanza Templates.
fixoType: "preset-template"
{
"fixoType": "preset-template",
"name": "hexapod",
"title": "Hexapod walker",
"description": "Six-legged walker with three joints per leg",
"builtin": true,
"parameters": {
"board": {"type": "board-type", "default": "arduino-mega2560"},
"name": {"type": "string"},
"legCount": {"type": "int", "default": 6}
},
"preset": {
"name": "{{name}}",
"board": "{{board}}",
"actuators": [],
"sensors": [],
"stanzas": [
{"id": "walk", "lobeType": "template:hexapod-walk"}
]
},
"forEachActuators": [
{
"countParameter": "legCount",
"pattern": {
"type": "rc-servo",
"name": "leg_{{index}}_coxa",
"semanticTags": ["leg_{{index}}", "coxa"]
}
}
]
}
The substitution language is documented in Preset Templates. Key points:
{{paramName}}is substituted in any string fieldparameters[...].typeis one of"board-type","int","string"forEachActuatorsexpansion is driven by an int parameter (countParameter);{{index}}substitutes per iterationlobeType: "template:..."refers to aFixoStanzaTemplateto expand at instantiate time- Arithmetic (
{{a * b}}) is not supported — use a single int parameter for the count
Discovery layout
A typical custom storage tree looks like this:
<personality>/fixo/
├── presets/
│ └── hexy-explorer.fixo.json
├── actuator-types/
│ └── my-custom-servo/
│ ├── fixo.json
│ └── my-custom-servo.hpp
├── lobe-types/
│ └── my-trim-lobe/
│ ├── fixo.json
│ └── my-trim-lobe.hpp
├── stanza-templates/
└── preset-templates/
Folders are organisational only. FixoLibrary::scan() walks recursively; it'll route a file regardless of the directory it sits in.
See also
- Fixo Data Model — what the fields mean
- Books and FixoLibrary — what happens after a
fixo.jsonis loaded - Create a custom actuator type — author your own
- Stanza Templates — output-port matching details
- Preset Templates — substitution language details