Skip to content

InfraredService

Source: InfraredService.h

Row

struct Row
src/core/InfraredService.h:225

One binding: a remote code and what it drives.

learn is transient UI intent (arm this row for the next code), never persisted, which is why it is not written by restoreList.

Public Attributes

uint32_t id = 0

uint32_t code = 0 : the learned frame, 0 = unbound

bool learn = false

InputAction action {}

InfraredService

class InfraredService
src/core/InfraredService.h:49

Inherits: MoonModule, ListSource

A core, domain-neutral infrared-receiver peripheral: it decodes a remote on pin and drives other modules' controls.

The device's infrared input, and the same shape as the button service, which drives the same controls from a physical switch.

A list of learned codes, not a fixed set of actions. The first version carried five compiled actions (on/off, brightness up/down, palette next/prev), which made the firmware's opinion the user's ceiling: a remote has twenty keys, and adding a sixth action meant editing an array and reflashing. Now a row IS the binding, so twenty keys are twenty rows, each learned and each pointing wherever the user wants.

A fresh service starts empty, so the first use is "add a row, learn a key, pick a target". Shipping the old five as defaults would need the device catalog to express a list row, and its config push (planConfigOps) has only add / set / clearChildren: no row op exists on either side. Worth adding when a board wants pre-bound rows; not worth inventing for a default.

Learning any remote, with no code table. No firmware can carry a table for every remote in the world, so the binding is taught rather than shipped: set a row's learn flag and the next decoded code binds to it. That was true of the first version and stays true here; what changed is that you learn onto a row rather than onto one of five fixed slots.

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 its target as Module.control, so a remote press and an OSC message are indistinguishable to whatever they drive. Pointing a row at Control.switch1 puts the remote on the control surface where every other transport reaches the same switch; Drivers.on drives that control directly.

Not auto-wired. Factory-registered like AudioService: a board with a receiver adds it under Services through the device catalog, its pin carrying that board's infrared GPIO. On the SE16 the line shares GPIO 5 with the Ethernet MISO through the board's hardware switch.

Prior art: consumer remotes use the NEC protocol (a 32-bit address+command frame, LSB-first, ~9 ms lead burst); the ESP-IDF RMT peripheral decodes it (the espressif ir_nec_transceiver example). The decode itself lives behind platform::irRead.

InfraredService 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 onControlChanged(const char *) override : Cheap per-control reaction, tier 1 of the three-tier control-change split (mirrors MoonLight's onUpdate / requestMappings / onSizeChanged; see architecture.md § Rebuild propagation).

virtual inline void prepare() override : Tier-3 of the control-change split (see onControlChanged above): the module (re)allocates / recomputes whatever derived state it owns — an effect's heap, a Layer's mapping LUT, the Drivers output buffer.

virtual inline void tick() override

inline uint32_t latestCode() const : The last decoded code (0 = none yet).

inline void injectCodeForTest(uint32_t code) : Feed a decoded code as if it arrived from the receiver: the entry host unit tests drive, since platform::irRead is a stub on desktop.

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.

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.