This document describes how digital outputs work in cm::IOManager.
IOManager digital outputs are settings-driven and provide:
- GPIO configuration via Settings (pin, polarity)
- A consistent API to set and read output state
- Optional runtime controls (checkbox/state button/momentary button)
For analog channels, see:
docs/IO-AnalogInputs.mddocs/IO-AnalogOutputs.md
Example (active-low relay):
ioManager.addDigitalOutput(cm::IOManager::DigitalOutputBinding{
.id = "relay27",
.name = "Relay 27",
.defaultPin = 27,
.defaultActiveLow = true,
.defaultEnabled = true,
});Each output has settings for:
GPIO(P): which GPIO pin to useLOW-Active(L): logic inversion
Outputs also have an enable concept (defaultEnabled), which is applied before the first preferences load.
Note: currently the enable flag is not persisted as a setting (defaults only).
To keep keys short (ESP32 Preferences limit), IOManager uses slot-based keys:
- Outputs:
IO%02uX(e.g.IO00P)
Suffix meanings for outputs:
P= pinL= active-low
Important: slot-based keys depend on the order of addDigitalOutput(...) calls.
If you reorder outputs in code, previously stored pins may appear to "move" to another output.
IOManager applies the desired output state in update():
void loop() {
ioManager.update();
}When you call:
ioManager.set("relay27", true);the manager stores desiredState and ensures the GPIO is configured and written.
Place settings separately, then register runtime controls via addDigitalOutputToLive(...).
Checkbox (toggle):
ioManager.addDigitalOutputToSettingsGroup("relay27", "Digital - I/O", "Digital Outputs", "Relay 27", 5);
ioManager.addDigitalOutputToLive(
cm::IOManager::RuntimeControlType::Checkbox,
"relay27",
5,
"DO",
"Digital Outputs",
"controls",
"Relay 27"
).onChangeCallback([](bool v){ /* ... */ });Momentary button (press=true, release=false):
ioManager.addDigitalOutputToSettingsGroup("holdbutton", "Digital - I/O", "Digital Outputs", "Holdbutton", 4);
ioManager.addDigitalOutputToLive(
cm::IOManager::RuntimeControlType::MomentaryButton,
"holdbutton",
4,
"DO",
"Digital Outputs",
"controls",
"Holdbutton",
"Running",
"Push"
).onChangeCallback([](bool v){ /* ... */ });- Inputs and outputs can share the same runtime group (
"inputs","controls", etc.). - Use short IDs and keep the number of IO items stable to avoid slot key drift.
- If you need stable persisted mapping independent of add-order, the next step is to switch from slot keys to ID-based keys (requires migration strategy).
Typical sketch order:
addDigitalOutput(...)/addDigitalOutputToSettingsGroup(...)/addDigitalOutputToLive(...)ConfigManager.loadAll()(loads persisted pins/polarity)ioManager.begin()(appliespinMode(...)and initializes states)- In
loop():ioManager.update()continuously applies desired output states
| Method | Overloads / Variants | Description | Notes |
|---|---|---|---|
cm::IOManager::addDigitalOutput |
addDigitalOutput(const DigitalOutputBinding& binding)addDigitalOutput(const char* id, int pin = -1, bool activeLow = false, bool registerSettings = true, int order = 100) |
Registers digital output channels and metadata. | Supports struct-based and inline registration. |
cm::IOManager::addDigitalOutputToSettingsGroup |
addDigitalOutputToSettingsGroup(...) (2 overloads) |
Places digital output settings into Settings UI. | Overloads support page/card/group variants. |
cm::IOManager::addDigitalOutputToLive |
addDigitalOutputToLive(RuntimeControlType type, const char* id, int order, const char* pageName, const char* cardName, const char* groupName, const char* labelOverride = nullptr, const char* onLabel = nullptr, const char* offLabel = nullptr) |
Adds runtime controls for digital outputs (checkbox/state/momentary/button). | Returns LiveControlHandleBool. |