The project file
A ctrl32 project is a single YAML file. Text, readable, declarative — you can open it in any editor, read it without the tool that made it, and put it in version control like source code.
That is not an implementation detail that leaked out. It is a deliberate choice, and it gives you things a binary project format cannot.
Why it matters
You can diff it. Two versions of a project, side by side, showing exactly what changed: this setpoint, that rung, this screen. Ask anybody who has tried to answer "what changed since Tuesday?" in a binary engineering tool what that is worth.
You can put it in git. Branches for a variant, variables for what was delivered, a history of who changed what and when. A project from two years ago is a checkout away.
You can review it. A change to the equipment's logic is a change worth reading before it is uploaded, and a text diff is something a second person can actually read.
You can generate it. Twenty near-identical variants of one equipment are a script, not twenty afternoons of clicking.
It outlives the tool. Even with no ctrl32 installed, the file still says what it does. Nothing about your work is locked inside a format only one program understands.
What is in it
The sections, in the order they usually appear:
| Section | Holds |
|---|---|
project |
name, target board, resolution, orientation |
themes |
colour sets the styles resolve against |
states |
reusable three-state colour sets |
variables |
every variable: type, unit, scaling, deadband, source |
pous |
program units — ladder, FBD or ST |
logic |
the blocks and how they are wired |
alarms |
condition, limit, hysteresis, delay, priority, text |
icons |
which glyphs the project uses |
screens |
the board's own screens and their widgets |
web_screens |
the browser's screens |
assets |
fonts and images |
Peripherals, buses and services appear under their own keys as the project uses them.
Everything is optional except project. A file with a project section
and nothing else is a valid, empty project.
What it looks like
project:
name: thermostat
target: olimex_esp32_evb
resolution: [240, 320]
orientation: portrait
tags:
- { name: temp, datatype: f32, unit: "°C", decimals: 1,
deadband: 0.2 }
- { name: setpoint, datatype: f32, unit: "°C", decimals: 1,
init: 21, access: rw }
- { name: heating, datatype: bool, access: rw,
source: { gpio: { pin: 32 } } }
logic:
- { type: HYST, name: thermo, in: { IN: temp },
param: { ON: 21, OFF: 20 }, out: { Q: heating } }
screens:
- name: main
widgets:
- { type: value, rect: [20, 40, 200, 60], tag: temp }
- { type: led, rect: [20, 120, 40, 40], tag: heating }
That is a working thermostat: a measurement, a setpoint, a relay, a comparator with hysteresis, and a screen showing both. Nine lines of logic and layout.
Compiled, not interpreted
The board does not read this file. The editor compiles it into a binary pack — string table, variable table, screens, bytecode — and the board maps that straight out of flash.
The reasons are practical: parsing YAML on a microcontroller would cost memory the setup needs, and a screen has to be on the glass in under a millisecond. So the readable form is what you keep, and the compiled form is what runs.
The consequence worth remembering: the YAML is the source of truth. The pack is a build artefact — regenerate it any time from the project, and never edit it by hand.
Compatibility
The format is versioned and the rules are strict, because a project you made two years ago has to open today:
- The meaning of an existing key never changes. A key that meant something in one version means the same in every later one.
- A newer editor keeps keys it does not recognise, rather than dropping them. Opening a project in an older editor and saving it does not silently strip what the older one could not see.
- New features are new keys, with defaults chosen so that a file written before they existed still means what it meant.
The board is stricter than the editor on purpose: a pack from a newer editor than the firmware is refused outright, rather than loaded with the parts the firmware does not understand quietly missing. A screen without the behaviour its author intended is worse than a screen that will not load. See Download to the board.
Working with it in version control
A few things make the diffs better:
Commit the YAML, not the pack. The pack is generated and changes
completely when anything does. *.visu belongs in .gitignore.
Keep one thing per line where you can. The flow style above (- { …
}) is compact and readable, and it means a changed variable is one changed
line rather than a moved block.
Let the editor write it. Round-tripping through the editor produces stable, consistent output — the same project saved twice is byte for byte identical, so a diff only ever shows what you actually changed.
Assets live beside it
Fonts and images are separate files in an assets/ folder next to the
project, referenced by name. They are binary and they do not diff — but
they change rarely, and keeping them out of the YAML keeps the part you
read and review textual.