Skip to content

IrService

Source: IrService.h

Action

struct Action
src/core/IrService.h:120

Public Attributes

constchar * button

constchar * codeCtrl

constchar *constchar * control

int delta

ActionKind kind

IrService

class IrService
src/core/IrService.h:51

Inherits: MoonModule

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

It is the device's IR input — the role the WLED-app bridge plays for the phone app, expressed for a physical remote.

How it acts. Every action routes through Scheduler::setControl(module, control, value), the one generic control-set primitive (also used by /api/control, Improv, and the WLED bridge). IR never reaches into another module's internals; it composes against that primitive, so adding a new action is one row in kActions — not new plumbing. The actions are brightness up/down and palette prev/next, each a relative nudge of a target control.

Buttons vs the remote. Each action is a UI button (press it → the nudge happens now) AND a learnable remote binding. The two share one path: a button press and a matching remote code both call runAction.

Learning (any remote, no firmware table). Pick an action in the learn select; the next decoded IR code binds to it (stored in that action's code … control, which persists like any control). Press that remote button afterwards and its code is looked up → the action runs. This is more flexible than a fixed per-remote preset table (MoonLight's model): it works with any remote, the user teaches it live. A code bound to nothing is shown in last code and ignored.

Relative adjust. An action nudges a target control by a signed delta, clamped to the control's own [min, max] — read generically from the target's ControlDescriptor, so the same code adjusts a 0–255 brightness slider and an N-option palette Select without knowing either's domain. A future LightsControl hub (docs/backlog) absorbs this as the normalised interface effects read; latestCode() is the seam it consumes (the AudioService::latestFrame pattern).

Not auto-wired. Factory-registered like AudioService, so a board with an IR receiver adds it under the Services container via the installer device catalog (its pin carrying that board's IR GPIO) or the user adds it from the UI. On the SE16 the IR line shares GPIO 5 with the Ethernet MISO through the board's hardware switch; the pin is the receiver input.

Prior art: consumer IR 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.

IrService 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 prepare() override : Pure build (see MoonModule::prepare): rebuild the fast uint32 lookup from the persisted hex strings (persistence restores the codeStr_ Text controls, so parse each back post-load so a learned binding survives a reboot) and report readiness.

virtual inline void release() override : Release the IR RX channel (opened lazily in loop) so its pin is free for another module.

virtual inline void onControlChanged(constchar *) 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 tick() override

inline uint32_t latestCode() const : The last decoded IR code (0 = none yet). The seam a future LightsControl consumes.

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