#pragma once #include #include "utils.h" #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; DeviceConfig* parent = nullptr; DeviceConfig(const char* id) : id(id) {} DeviceConfig(DeviceConfig& d) : id(d.id), name(d.name), model(d.model), manufacturer(d.manufacturer), area(d.area), parent(d.parent) {} 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(DeviceConfig* deviceConfig) { parent = deviceConfig; return this; } }; 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) = 0; virtual void buildConfigTopic() { if (String{"diagnostic"}.equals(entityCategory) && deviceClass) { sprintf(configTopic, "homeassistant/%s/%s/%s_%s/config", type, MAIN_DEVICE_ID, deviceClass, id); } else { sprintf(configTopic, "homeassistant/%s/%s/%s/config", type, MAIN_DEVICE_ID, id); } } virtual void buildConfig(JsonDocument& jsonDoc) { buildConfigTopic(); 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; } void publishConfig() { StaticJsonDocument jsonDoc; buildConfig(jsonDoc); char message[JSON_SIZE]; serializeJson(jsonDoc, message); publisher(configTopic, message); } void publishCleanupConfig() { publisher(configTopic, ""); } }; List Component::components; struct AbstractBuilder { static List builders; AbstractBuilder() { builders.add(this); } static void deleteAll() { List::exec(builders, [](AbstractBuilder* builder) { delete builder; }); } }; List AbstractBuilder::builders; 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& withSecondary(Component* c) { c->mainDevice = new DeviceConfig{ cmp->id }; return *this; } Builder& withDiagnostic(Component* c) { c->entityCategory = "diagnostic"; c->mainDevice = new DeviceConfig{ cmp->id }; return *this; } Builder& asDevice(DeviceConfig* deviceConfig) { cmp->mainDevice = deviceConfig; return *this; } }; struct Command : Component { 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) { buildCommandTopic(); mapCommands.insert({ string(commandTopic), this }); } virtual void onCommand(const char* msg) { if (f) f(msg); } virtual void buildCommandTopic() { sprintf(commandTopic, "homeassistant/%s/%s/%s", type, MAIN_DEVICE_ID, id); } void buildUniqueId(char* uniqueId) override { sprintf(uniqueId, "%s_%s", MAIN_DEVICE_ID, this->id); } void buildConfig(JsonDocument& jsonDoc) override { Component::buildConfig(jsonDoc); jsonDoc["command_topic"] = commandTopic; } }; unordered_map Command::mapCommands; struct Button : Command { Button(const char* name, const char* id, onMessage f = nullptr) : Command(name, id, "button", f) {} }; template struct StateConfig { char stateTopic[TOPIC_SIZE]; T* withStateTopic() { sprintf(stateTopic, "homeassistant/%s/%s/%s/state", ((T*)this)->type, MAIN_DEVICE_ID, ((T*)this)->id); return static_cast(this); } }; struct Switch : Command, StateConfig { Switch(const char* name, const char* id, onMessage f = nullptr) : Command(name, id, "switch", f) {} void buildCommandTopic() override { sprintf(commandTopic, "homeassistant/%s/%s/%s/set", type, MAIN_DEVICE_ID, id); } void buildConfig(JsonDocument& jsonDoc) override { Command::buildConfig(jsonDoc); jsonDoc["name"] = nullptr; jsonDoc["device_class"] = "outlet"; // jsonDoc["retain"] = true; if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; } void publishState(bool state) { publisher(stateTopic, state ? "ON" : "OFF"); } }; struct Sensor : Component, StateConfig { const char* unitMeasure = nullptr; const char* valueTemplate = nullptr; Sensor() : Component(name, Protocol_2::buildId(id), "sensor") { withStateTopic(); } Sensor(const char* name, const char* id) : Component(name, Protocol_2::buildId(id), "sensor") { withStateTopic(); } void buildUniqueId(char* uniqueId) override { sprintf(uniqueId, "%s_%s", deviceClass, id); } void buildConfig(JsonDocument& jsonDoc) override { Component::buildConfig(jsonDoc); if (unitMeasure) jsonDoc["unit_of_measurement"] = unitMeasure; if (valueTemplate) jsonDoc["value_template"] = valueTemplate; jsonDoc["state_topic"] = stateTopic; jsonDoc["suggested_display_precision"] = 2; } }; struct TemperatureSensor : Sensor { TemperatureSensor(const char* id) : Sensor("Temperature", id) { deviceClass = "temperature"; unitMeasure = "°C"; valueTemplate = "{{ value_json.sensor.temperature }}"; } }; 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 = "%"; // valueTemplate = "{{ value_json.sensor.humidity }}"; } }; struct PressureSensor : Sensor { PressureSensor(const char* id, const char* name = "Pressure") : Sensor(name, id) { deviceClass = "pressure"; unitMeasure = "hPa"; // valueTemplate = "{{ value_json.sensor.pressure }}"; } }; }