Profile model¶
A profile is an implementation of the IProfile interface, which describes the behaviour of a specific device. The library interacts with the product only through this interface.
IProfile interface¶
class IProfile {
public:
virtual ~IProfile() = default;
virtual void onOnline() = 0;
virtual void loop() = 0;
virtual void getConfig(JsonDocument& out) = 0;
virtual bool applyConfig(int id, int val) = 0;
virtual void buildInfoJson(char* buf, size_t len) const = 0;
};
When the library calls each method¶
| Method | When called | What it must do |
|---|---|---|
onOnline() |
On the first CloudStateMachine transition to Online |
Load config from NVS, apply to hardware |
loop() |
Every iteration of IdryerRuntime::loop() |
Timers, animations, sensor polling |
buildInfoJson(buf, len) |
On transition to Online; on ping |
Serialize device info payload |
getConfig(out) |
On invoke device.getConfig |
Fill doc with current config |
applyConfig(id, val) |
On commands/set |
Apply parameter, save to NVS |
Example: LedStripProfile¶
LedStripProfile is the profile implementation for Storage Link. Located in src/storage/led_strip/.
class LedStripProfile : public IProfile {
public:
explicit LedStripProfile(LedStripExecutor* executor);
void onOnline() override;
void loop() override;
void getConfig(JsonDocument& out) override;
bool applyConfig(int id, int val) override;
void buildInfoJson(char* buf, size_t len) const override;
static void normalizeGroups(); // fix NVS state of toggle groups
static uint8_t selectedStripType(); // 0=WS2812B, 1=APA102
static uint8_t selectedColorOrder(); // 0=GRB, 1=RGB, 2=BRG, 3=BGR
static constexpr const char* DEVICE_TYPE = "storage_link";
static constexpr const char* HW_VERSION = "1.0";
static constexpr const char* FW_VERSION = "1.0.0";
private:
LedStripExecutor* executor_;
};
onOnline() applies the current LED strip configuration (LED count, brightness) to LedStripExecutor.
applyConfig(id, val) accepts a parameter ID from menu_ids.h and a new value. Saves to NVS via the menu object. Parameters such as strip_type and color_order require a reboot — FastLED is initialized once at startup.
buildInfoJson builds the payload for idryer/{serial}/info. Field composition is defined by the product. Storage Link publishes:
{
"deviceType": "storage_link",
"firmwareVersion": "1.0.0",
"hardwareVersion": "1.0",
"timestamp": "2026-04-28T10:00:00Z"
}
For devices with multiple chamber units (iDryer LINK), it is typical to add workTimeCounter, unitsCount, and a units array describing capabilities.
ActionDispatcher¶
ActionDispatcher routes two command types without std::function (plain function pointers to conserve heap):
// Invoke: action with name and arguments
using InvokeHandler = bool (*)(const char* action, JsonObjectConst args, void* ctx);
// Set: setting a single parameter
using SetCallback = void (*)(JsonObjectConst data, void* ctx);
Registration in setup():
// Invoke — delegates to LedStripExecutor
dispatcher.setInvokeHandler(
[](const char* action, JsonObjectConst args, void* /*ctx*/) -> bool {
return s_executor.execute(action, args);
}, nullptr);
// Set — passes id/val to LedStripProfile
dispatcher.setSetCallback(
[](JsonObjectConst data, void* /*ctx*/) {
int id = data["id"] | -1;
int val = data["val"] | -1;
s_profile.applyConfig(id, val);
}, nullptr);
IdryerRuntime calls dispatcher.handleInvoke(data) and dispatcher.handleSet(data) when the corresponding MQTT commands arrive.
Creating a new profile¶
- Create a class inheriting from
IProfile. - Implement all five methods.
- Pass a pointer to the profile into the
IdryerRuntimeconstructor. - Register handlers in
ActionDispatcherforinvokeandsetcommands.
There are no restrictions on what the profile does inside its methods — it has full visibility into the product context.