#pragma once #include #include "utils.h" using namespace std; #define JSON_SIZE 512 #define TOPIC_SIZE 255 namespace Ha { uint16_t(*publisher)(const char* topic, const char* message); typedef void (*onMessage)(const char* msg); struct DeviceConfig { const char* id = nullptr; const char* name = nullptr; const char* model = nullptr; const char* manufacturer = nullptr; const char* area = nullptr; const DeviceConfig* parent = nullptr; static DeviceConfig& create(const char* id) { return *(new DeviceConfig{ id }); } void buildConfig(JsonDocument& jsonDoc) { JsonObject device = jsonDoc.createNestedObject("device"); if (name) device["name"] = name; if (model) device["model"] = model; if (manufacturer) device["manufacturer"] = manufacturer; if (area) device["suggested_area"] = area; if (parent) device["via_device"] = parent->id; JsonArray identifiers = device.createNestedArray("identifiers"); identifiers.add(id); } DeviceConfig& withName(const char* value) { name = value; return *this; } DeviceConfig& withModel(const char* value) { model = value; return *this; } DeviceConfig& withManufacturer(const char* value) { manufacturer = value; return *this; } DeviceConfig& withArea(const char* value) { area = value; return *this; } DeviceConfig& withParent(const DeviceConfig* deviceConfig) { parent = deviceConfig; return *this; } protected: DeviceConfig(const char* id) : id(id) {} }; struct Component { const char* entityCategory = nullptr; const char* deviceClass = nullptr; const char* name = nullptr; char* id = nullptr; const char* type = nullptr; char configTopic[TOPIC_SIZE]; DeviceConfig* mainDevice = nullptr; static List components; Component(const char* name, const char* id, const char* type) : name(name), id((char*)id), type(type) { components.add(this); } virtual void buildUniqueId(char* uniqueId) { sprintf(uniqueId, "%s_%s", MAIN_DEVICE_ID, id); } virtual void buildConfigTopic() { sprintf(configTopic, "homeassistant/%s/%s/%s/config", type, MAIN_DEVICE_ID, id); } virtual void buildConfig(JsonDocument& jsonDoc) { if (mainDevice) mainDevice->buildConfig(jsonDoc); if (entityCategory) jsonDoc["entity_category"] = entityCategory; if (deviceClass) jsonDoc["device_class"] = deviceClass; jsonDoc["name"] = name; char uniqueId[50]; buildUniqueId(uniqueId); jsonDoc["unique_id"] = uniqueId; buildConfigTopic(); } void publishConfig() { StaticJsonDocument jsonDoc; buildConfig(jsonDoc); char message[JSON_SIZE]; serializeJson(jsonDoc, message); publisher(configTopic, message); } void publishCleanupConfig() { publisher(configTopic, ""); } }; struct AbstractBuilder { static List builders; AbstractBuilder() { builders.add(this); } static void deleteAll() { builders.forEach([](AbstractBuilder* builder) { delete builder; }); builders.empty(); } }; template struct Builder : AbstractBuilder { T* cmp; Builder(T* cmp) : AbstractBuilder(), cmp(cmp) {} Builder(const char* id) : AbstractBuilder() { cmp = new T{ id }; } static Builder& instance(T* c) { return *(new Builder(c)); } static Builder& instance(const char* id) { return *(new Builder(id)); } T* build() { return static_cast(cmp); } Builder& withDeviceClass(const char* value) { cmp->deviceClass = value; return *this; } Builder& withUnitMseasure(const char* value) { cmp->unitMeasure = value; return *this; } Builder& withValueTemplate(const char* value) { cmp->valueTemplate = value; return *this; } Builder& addSecondary(Component* c) { if (cmp->mainDevice) c->mainDevice = &DeviceConfig::create(cmp->mainDevice->id); return *this; } Builder& addDiagnostic(Component* c) { c->entityCategory = "diagnostic"; return addSecondary(c); } Builder& addConfiguration(Component* c) { c->entityCategory = "config"; return addSecondary(c); } Builder& asDevice(DeviceConfig* deviceConfig) { cmp->mainDevice = deviceConfig; return *this; } Builder& withStateTopic() { cmp->withStateTopic(); return *this; } Builder& withRetain(bool retain = true) { cmp->retain = retain; return *this; } Builder& restoreFromState() { cmp->restoreFromState(); return *this; } }; struct Command : Component { bool retain = false; char commandTopic[TOPIC_SIZE]; onMessage f; static unordered_map mapCommands; Command(const char* name, const char* id, const char* type, onMessage f) : Component(name, id, type), f(f) { sprintf(commandTopic, "homeassistant/%s/%s/%s/set", type, MAIN_DEVICE_ID, id); mapCommands.insert({ string(commandTopic), this }); } virtual void onCommand(const char* msg) { if (f) f(msg); } void buildConfig(JsonDocument& jsonDoc) override { Component::buildConfig(jsonDoc); jsonDoc["command_topic"] = commandTopic; if (retain) jsonDoc["retain"] = true; } }; struct Button : Command { Button(const char* name, const char* id, onMessage f = nullptr) : Command(name, id, "button", f) {} }; struct StateConfig { char stateTopic[TOPIC_SIZE]; static unordered_map mapStateTopics; Component* cmp; StateConfig(Component* cmp) : cmp(cmp) {} void withStateTopic() { sprintf(stateTopic, "homeassistant/%s/%s/%s/state", cmp->type, MAIN_DEVICE_ID, cmp->id); } void buildConfig(JsonDocument& jsonDoc) { if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; } void updateState(const char* message) { if (stateTopic[0]) publisher(stateTopic, message); } }; struct Switch : Command, StateConfig { Switch(const char* name, const char* id, onMessage f = nullptr) : Command(name, id, "switch", f), StateConfig(this) {} void buildConfig(JsonDocument& jsonDoc) override { Command::buildConfig(jsonDoc); StateConfig::buildConfig(jsonDoc); } void updateState(bool state) { StateConfig::updateState(state ? "ON" : "OFF"); } void restoreFromState() { mapStateTopics.insert({stateTopic, this}); } }; struct Number : Command, StateConfig { unsigned int min, max, step; Number(const char* name, const char* id, unsigned int min, unsigned int max, unsigned int step, onMessage f) : Command(name, id, "number", f), StateConfig(this), min(min), max(max), step(step) {} void buildConfig(JsonDocument& jsonDoc) override { Command::buildConfig(jsonDoc); StateConfig::buildConfig(jsonDoc); jsonDoc["min"] = min; jsonDoc["max"] = max; jsonDoc["step"] = step; } void updateState(unsigned int value) { char message[32]; sprintf(message, "%u", value); StateConfig::updateState(message); } void restoreFromState() { mapStateTopics.insert({stateTopic, this}); } }; struct Sensor : Component, StateConfig { const char* unitMeasure = nullptr; const char* valueTemplate = nullptr; static unordered_map mapSensors; Sensor(const char* name, const char* id) : Component(name, id, "sensor"), StateConfig(this) { withStateTopic(); mapSensors.insert({ id, this }); } void buildUniqueId(char* uniqueId) override { if (deviceClass) { sprintf(uniqueId, "%s_%s_%s", MAIN_DEVICE_ID, deviceClass, id); } else { Component::buildUniqueId(uniqueId); } } void buildConfigTopic() override { if (deviceClass) { sprintf(configTopic, "homeassistant/%s/%s/%s_%s/config", type, MAIN_DEVICE_ID, deviceClass, id); } else { Component::buildConfigTopic(); } } void buildConfig(JsonDocument& jsonDoc) override { Component::buildConfig(jsonDoc); StateConfig::buildConfig(jsonDoc); if (unitMeasure) jsonDoc["unit_of_measurement"] = unitMeasure; if (valueTemplate) jsonDoc["value_template"] = valueTemplate; jsonDoc["suggested_display_precision"] = 2; } }; struct TemperatureSensor : Sensor { TemperatureSensor(const char* id, const char* name = "Temperature") : Sensor(name, id) { deviceClass = "temperature"; unitMeasure = "°C"; } }; struct VoltageSensor : Sensor { VoltageSensor(const char* id, const char* name, const char* valueTemplate) : Sensor(name, id) { this->valueTemplate = valueTemplate; deviceClass = "voltage"; unitMeasure = "V"; } }; struct BatterySensor : Sensor { BatterySensor(const char* id, const char* name, const char* valueTemplate) : Sensor(name, id) { this->valueTemplate = valueTemplate; deviceClass = "battery"; unitMeasure = "%"; } }; struct HumiditySensor : Sensor { HumiditySensor(const char* id, const char* name = "Humidity") : Sensor(name, id) { deviceClass = "humidity"; unitMeasure = "%"; } }; struct PressureSensor : Sensor { PressureSensor(const char* id, const char* name = "Pressure") : Sensor(name, id) { deviceClass = "pressure"; unitMeasure = "hPa"; } }; List Component::components; List AbstractBuilder::builders; unordered_map Command::mapCommands; unordered_map Sensor::mapSensors; unordered_map StateConfig::mapStateTopics; }