Skip to content

ButtonService

Source: ButtonService.h

Row

struct Row
src/core/ButtonService.h:172

One button: where it is wired, what it drives, and the debounce state it carries between polls.

The state is per row because two buttons bounce independently.

Public Attributes

uint32_t id = 0

int8_t pin = -1

bool activeLow = true : a switch to ground with a pull-up: the usual wiring

InputAction action {}

bool open = false : the pin was accepted by the seam, so it is worth polling

bool pressed = false : the settled state

bool candidate = false : the level being timed

uint16_t sinceChange = 0 : ms the candidate has held

ButtonService

class ButtonService
src/core/ButtonService.h:44

Inherits: MoonModule, ListSource

A core, domain-neutral push-button peripheral: a list of buttons, each on its own GPIO, each driving a control.

The physical twin of a UI click, and the same shape as the infrared service, which drives the same controls from a remote.

A list, because boards have more than one. A QuinLED Dig-Next-2 has three buttons and a stage rig has a pedalboard; a single pin and a single target was the shape of the first version and it did not survive the second board. Rows are added, edited and deleted at runtime through the generic list machinery, so a board's buttons come from its catalog entry rather than from firmware.

How it acts. Through Scheduler::setControl, the one generic control-set primitive that /api/control, Improv, MQTT, the WLED bridge and OSC all use. A row names a target as Module.control, so a press and an OSC message are indistinguishable to whatever they drive. Pointing a row at Control.switch1 (the recommended path) puts the button on the control surface where every other transport can also reach it; pointing it at Drivers.on drives that control directly. Both are the same mechanism.

Momentary vs latching, because a wall switch and a stage foot pedal want opposite things. A toggle row flips its target on each press and ignores the release (press-to-toggle, a light switch); a set row writes its value while held and 0 on release (hold-to-activate, a pedal for a burst effect). A foot pedal is electrically a momentary switch on a jack, so it needs no module of its own: it is a row with kind = set.

Debounced here, not in the platform. A bouncing contact is a property of the switch, so the seam reports the pad and the module owns the time constant. Polled on tick20ms: a press lasts tens of milliseconds and 50 Hz catches it, where the render tick would sample a switch thousands of times a second to learn the same thing.

Not auto-wired. Factory-registered like AudioService and InfraredService: a board with buttons adds it under Services through the device catalog, with a row per button.

ButtonService card

Public Methods

virtual inline ModuleRole role() const override : Role for type identification (no RTTI needed).

virtual inline void defineControls() override : defineControls MUST be idempotent and pure: only controls_.clear() + controls_.addX().

virtual inline void setup() override : Default lifecycle propagates to children.

virtual inline void tick20ms() override : Poll every configured button at 50 Hz.

inline bool isEditableList() const override : Editable: rows are the whole point of this module, so the UI shows add and delete and the list API accepts them.

inline uint8_t listRowCount() const override

inline void writeListRow(JsonSink & sink, uint8_t row) const override

inline void writeListRowDetail(JsonSink & sink, uint8_t row) const override : The row's EDITABLE fields: what the UI builds inputs from, so a button is retargeted on the card rather than through the API.

inline void writeListOptionSets(JsonSink & sink) const override : The target-type options, shared across every row rather than repeated in each.

inline bool addListRow(uint32_t & outId) override

inline bool deleteListRow(uint32_t id) override

inline bool setListRowField(uint32_t id, const char * field, const char * valueJson) override

inline bool restoreList(const char * json, const char * key) override : Rebuild the rows from the persisted list, then open every pin they name.