From 138c7b5cd8cc2a5a20ef98a7540320dd329f4c7f Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 17 Oct 2024 17:15:43 +0200 Subject: [PATCH 01/11] read heap stats once and put them in both sensors and json attributes --- src/esp.h | 19 +++++++++++++++---- src/ha.h | 5 ++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/esp.h b/src/esp.h index 0fe1095..7180f6b 100644 --- a/src/esp.h +++ b/src/esp.h @@ -12,9 +12,16 @@ namespace HaESP { } activeSensors; Task tHeap(5 * TASK_MINUTE, TASK_FOREVER, []() { - Sensor::mapSensors["heap_fragmentation"]->updateState(to_string(ESP.getHeapFragmentation()).c_str()); - Sensor::mapSensors["heap_free"]->updateState(to_string(ESP.getFreeHeap()).c_str()); - Sensor::mapSensors["heap_max_free_block"]->updateState(to_string(ESP.getMaxFreeBlockSize()).c_str()); + uint32_t hfree; + uint32_t hmax; + uint8_t hfrag; + ESP.getHeapStats(&hfree, &hmax, &hfrag); + + char value[256]; + sprintf(value, "{\"fragmentation\":%d,\"heap\":{\"Heap free\":\"%d B\",\"Heap max free block\":\"%d B\"}}", hfrag, hfree, hmax); + Sensor::mapSensors["heap_fragmentation"]->updateState(value); + Sensor::mapSensors["heap_free"]->updateState(to_string(hfree).c_str()); + Sensor::mapSensors["heap_max_free_block"]->updateState(to_string(hmax).c_str()); }, &ts); Task tRestartInfo(TASK_IMMEDIATE, TASK_ONCE, []() { @@ -23,9 +30,13 @@ namespace HaESP { template Builder& heapStats(Builder& builder) { - builder.addDiagnostic(Builder::instance(new Sensor{ "Heap fragmentation", "heap_fragmentation" }) + auto heap = new Sensor{ "Heap fragmentation", "heap_fragmentation" }; + builder.addDiagnostic(Builder::instance(heap) .withUnitMeasure("%") .withPrecision(0) + .withValueTemplate("{{ value_json.fragmentation }}") + .overrideConfig("json_attributes_topic", heap->stateTopic) + .overrideConfig("json_attributes_template", "{{ value_json.heap | tojson }}") .build()); builder.addDiagnostic(Builder::instance(new Sensor{ "Heap free", "heap_free" }) .withDeviceClass("data_size") diff --git a/src/ha.h b/src/ha.h index 7103b45..364fef8 100644 --- a/src/ha.h +++ b/src/ha.h @@ -205,8 +205,7 @@ namespace Ha { return *this; } - template - Builder& overrideConfig(const char* key, V value) { + Builder& overrideConfig(const char* key, float value) { cmp->jsonNumbers.add({key, value}); return *this; } @@ -293,6 +292,7 @@ namespace Ha { struct StateConfig { static unordered_map mapStateTopics; + char stateTopic[TOPIC_SIZE]; StateConfig(Component* cmp) : cmp(cmp) {} @@ -305,7 +305,6 @@ namespace Ha { } protected: - char stateTopic[TOPIC_SIZE]; Component* cmp; void buildConfig(JsonDocument& jsonDoc) { if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; From baa3d2d0b868692f85cd8b0a8c4d0953e3989a70 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Fri, 18 Oct 2024 09:14:49 +0200 Subject: [PATCH 02/11] - avoid possible buffer overflows by using snprintf - uniqueId is a field now - optimizes string copy - store long strings in FLASH memory --- src/esp.h | 4 ++-- src/ha.h | 27 +++++++++++++-------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/esp.h b/src/esp.h index 7180f6b..cfa0034 100644 --- a/src/esp.h +++ b/src/esp.h @@ -17,8 +17,8 @@ namespace HaESP { uint8_t hfrag; ESP.getHeapStats(&hfree, &hmax, &hfrag); - char value[256]; - sprintf(value, "{\"fragmentation\":%d,\"heap\":{\"Heap free\":\"%d B\",\"Heap max free block\":\"%d B\"}}", hfrag, hfree, hmax); + char value[128]; + snprintf_P(value, sizeof(value), PSTR("{\"fragmentation\":%d,\"heap\":{\"Heap free\":\"%d B\",\"Heap max free block\":\"%d B\"}}"), hfrag, hfree, hmax); Sensor::mapSensors["heap_fragmentation"]->updateState(value); Sensor::mapSensors["heap_free"]->updateState(to_string(hfree).c_str()); Sensor::mapSensors["heap_max_free_block"]->updateState(to_string(hmax).c_str()); diff --git a/src/ha.h b/src/ha.h index 364fef8..0299def 100644 --- a/src/ha.h +++ b/src/ha.h @@ -7,6 +7,7 @@ using namespace std; #define JSON_SIZE 512 #define TOPIC_SIZE 255 +#define BASE_TOPIC "homeassistant/%s/%s/%s" namespace Ha { uint16_t(*publisher)(const char* topic, const char* message); @@ -116,12 +117,13 @@ namespace Ha { } protected: - virtual void buildUniqueId(char* uniqueId) { - sprintf(uniqueId, "%s_%s", MAIN_DEVICE_ID, id); + char uniqueId[50]; + virtual void buildUniqueId() { + snprintf(uniqueId, sizeof(uniqueId), "%s_%s", MAIN_DEVICE_ID, id); } virtual void buildConfigTopic() { - sprintf(configTopic, "homeassistant/%s/%s/%s/config", type, MAIN_DEVICE_ID, id); + snprintf(configTopic, sizeof(configTopic), BASE_TOPIC"/config", type, MAIN_DEVICE_ID, id); } virtual void buildComponentConfig(JsonDocument& jsonDoc) = 0; @@ -131,8 +133,7 @@ namespace Ha { if (entityCategory) jsonDoc["entity_category"] = entityCategory; if (deviceClass) jsonDoc["device_class"] = deviceClass; jsonDoc["name"] = name; - char uniqueId[50]; - buildUniqueId(uniqueId); + buildUniqueId(); jsonDoc["unique_id"] = uniqueId; buildConfigTopic(); @@ -261,7 +262,7 @@ namespace Ha { 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); + snprintf(commandTopic, sizeof(commandTopic), BASE_TOPIC"/set", type, MAIN_DEVICE_ID, id); mapCommands.insert({ string(commandTopic), this }); } @@ -297,7 +298,7 @@ namespace Ha { StateConfig(Component* cmp) : cmp(cmp) {} void withStateTopic() { - sprintf(stateTopic, "homeassistant/%s/%s/%s/state", cmp->type, MAIN_DEVICE_ID, cmp->id); + snprintf(stateTopic, sizeof(stateTopic), BASE_TOPIC"/state", cmp->type, MAIN_DEVICE_ID, cmp->id); } void updateState(const char* message) { @@ -336,9 +337,7 @@ namespace Ha { : Command(name, id, "number", f), StateConfig(this), min(min), max(max), step(step) {} void updateState(unsigned int value) { - char message[32]; - sprintf(message, "%u", value); - StateConfig::updateState(message); + StateConfig::updateState(to_string(value).c_str()); } void restoreFromState() { @@ -366,17 +365,17 @@ namespace Ha { } protected: - void buildUniqueId(char* uniqueId) override { + void buildUniqueId() override { if (multiValueComponent && deviceClass) { - sprintf(uniqueId, "%s_%s_%s", MAIN_DEVICE_ID, deviceClass, id); + snprintf(uniqueId, sizeof(uniqueId), "%s_%s_%s", MAIN_DEVICE_ID, deviceClass, id); } else { - Component::buildUniqueId(uniqueId); + Component::buildUniqueId(); } } void buildConfigTopic() override { if (multiValueComponent && deviceClass) { - sprintf(configTopic, "homeassistant/%s/%s/%s_%s/config", type, MAIN_DEVICE_ID, deviceClass, id); + snprintf(configTopic, sizeof(configTopic), BASE_TOPIC"_%s""/config", type, MAIN_DEVICE_ID, deviceClass, id); } else { Component::buildConfigTopic(); } From fe8b33df72de8d1de8a514132d91bc6687a4290b Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Fri, 18 Oct 2024 12:18:19 +0200 Subject: [PATCH 03/11] optimize ArduinoJson serialization: avoid copies of strings --- src/ha.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ha.h b/src/ha.h index 0299def..f1840c1 100644 --- a/src/ha.h +++ b/src/ha.h @@ -88,7 +88,7 @@ namespace Ha { const char* entityCategory = nullptr; const char* deviceClass = nullptr; const char* name = nullptr; - char* id = nullptr; + const char* id = nullptr; const char* type = nullptr; char configTopic[TOPIC_SIZE]; JsonPairs jsonBooleans; @@ -98,7 +98,7 @@ namespace Ha { static List components; bool multiValueComponent = false; - Component(const char* name, const char* id, const char* type) : name(name), id((char*)id), type(type) { + Component(const char* name, const char* id, const char* type) : name(name), id(id), type(type) { components.add(this); } @@ -134,7 +134,7 @@ namespace Ha { if (deviceClass) jsonDoc["device_class"] = deviceClass; jsonDoc["name"] = name; buildUniqueId(); - jsonDoc["unique_id"] = uniqueId; + jsonDoc["unique_id"] = (const char*)uniqueId; buildConfigTopic(); buildComponentConfig(jsonDoc); @@ -276,7 +276,7 @@ namespace Ha { virtual void buildCommandConfig(JsonDocument& jsonDoc) = 0; void buildComponentConfig(JsonDocument& jsonDoc) override { - jsonDoc["command_topic"] = commandTopic; + jsonDoc["command_topic"] = (const char*)commandTopic; if (retain) jsonDoc["retain"] = true; buildCommandConfig(jsonDoc); } @@ -308,7 +308,7 @@ namespace Ha { protected: Component* cmp; void buildConfig(JsonDocument& jsonDoc) { - if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; + if (stateTopic[0]) jsonDoc["state_topic"] = (const char*)stateTopic; } }; From 7755950a27a05f6e0ad950429796495d66bae3cd Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 24 Oct 2024 18:26:36 +0200 Subject: [PATCH 04/11] give maps more meaningful names and introduce StatefulCommand --- src/ha.h | 36 +++++++++++++++++++----------------- src/mqtt.h | 6 +++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/ha.h b/src/ha.h index f1840c1..31870bd 100644 --- a/src/ha.h +++ b/src/ha.h @@ -259,11 +259,11 @@ namespace Ha { struct Command : Component { bool retain = false; - static unordered_map mapCommands; + static unordered_map mapCommandTopics; Command(const char* name, const char* id, const char* type, onMessage f) : Component(name, id, type), f(f) { snprintf(commandTopic, sizeof(commandTopic), BASE_TOPIC"/set", type, MAIN_DEVICE_ID, id); - mapCommands.insert({ string(commandTopic), this }); + mapCommandTopics.insert({ string(commandTopic), this }); } virtual void onCommand(const char* msg) { @@ -292,7 +292,6 @@ namespace Ha { }; struct StateConfig { - static unordered_map mapStateTopics; char stateTopic[TOPIC_SIZE]; StateConfig(Component* cmp) : cmp(cmp) {} @@ -312,38 +311,41 @@ namespace Ha { } }; - struct Switch : Command, StateConfig { + struct StatefulCommand : Command, StateConfig { + static unordered_map mapRestoreStateTopics; - Switch(const char* name, const char* id, onMessage f = nullptr) : Command(name, id, "switch", f), StateConfig(this) {} + StatefulCommand(const char* name, const char* id, const char* type, onMessage f) + : Command(name, id, "number", f), StateConfig(this) {} + + void restoreFromState() { + mapRestoreStateTopics.insert({stateTopic, this}); + } + }; + + struct Switch : StatefulCommand { + + Switch(const char* name, const char* id, onMessage f = nullptr) : StatefulCommand(name, id, "switch", f) {} void updateState(bool state) { StateConfig::updateState(state ? "ON" : "OFF"); } - void restoreFromState() { - mapStateTopics.insert({stateTopic, this}); - } - protected: void buildCommandConfig(JsonDocument& jsonDoc) override { StateConfig::buildConfig(jsonDoc); } }; - struct Number : Command, StateConfig { + struct Number : StatefulCommand { 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) {} + : StatefulCommand(name, id, "number", f), min(min), max(max), step(step) {} void updateState(unsigned int value) { StateConfig::updateState(to_string(value).c_str()); } - void restoreFromState() { - mapStateTopics.insert({stateTopic, this}); - } - protected: void buildCommandConfig(JsonDocument& jsonDoc) override { StateConfig::buildConfig(jsonDoc); @@ -433,7 +435,7 @@ namespace Ha { List Component::components; List AbstractBuilder::builders; - unordered_map Command::mapCommands; + unordered_map Command::mapCommandTopics; unordered_map Sensor::mapSensors; - unordered_map StateConfig::mapStateTopics; + unordered_map StatefulCommand::mapRestoreStateTopics; } diff --git a/src/mqtt.h b/src/mqtt.h index 9f2be03..796aa78 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -47,13 +47,13 @@ namespace Mqtt { memcpy(msg, payload, len); msg[len] = 0; auto strTopic = string{ topic }; - auto cmd = Command::mapCommands[strTopic]; + auto cmd = Command::mapCommandTopics[strTopic]; if (cmd) cmd->onCommand(msg); - cmd = StateConfig::mapStateTopics[strTopic]; + cmd = StatefulCommand::mapRestoreStateTopics[strTopic]; if (cmd) { cmd->onCommand(msg); - StateConfig::mapStateTopics.erase(strTopic); + StatefulCommand::mapRestoreStateTopics.erase(strTopic); } } From 3a374bf685ac6941c09746edfacba6dcb6961e24 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Mon, 28 Oct 2024 08:46:48 +0100 Subject: [PATCH 05/11] no need to use friend in DeviceConfig --- src/ha.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ha.h b/src/ha.h index 31870bd..48e57c8 100644 --- a/src/ha.h +++ b/src/ha.h @@ -13,9 +13,7 @@ namespace Ha { uint16_t(*publisher)(const char* topic, const char* message); typedef void (*onMessage)(const char* msg); - struct Component; struct DeviceConfig { - friend struct Component; const char* id = nullptr; const char* name = nullptr; const char* model = nullptr; @@ -52,9 +50,6 @@ namespace Ha { return *this; } - protected: - DeviceConfig(const char* id) : id(id) {} - void buildConfig(JsonDocument& jsonDoc) { JsonObject device = jsonDoc.createNestedObject("device"); if (name) device["name"] = name; From 47a5bfc81c9957ede0f736c625b4ee30ec00f00d Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Mon, 28 Oct 2024 08:50:00 +0100 Subject: [PATCH 06/11] make min, max & step optional --- src/ha.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ha.h b/src/ha.h index 48e57c8..5886ee6 100644 --- a/src/ha.h +++ b/src/ha.h @@ -196,6 +196,21 @@ namespace Ha { return *this; } + Builder& withMin(unsigned int value) { + cmp->min = value; + return *this; + } + + Builder& withMax(unsigned int value) { + cmp->max = value; + return *this; + } + + Builder& withStep(unsigned int value) { + cmp->step = value; + return *this; + } + Builder& overrideConfig(const char* key, bool value) { cmp->jsonBooleans.add({key, value}); return *this; @@ -334,8 +349,7 @@ namespace Ha { struct Number : StatefulCommand { unsigned int min, max, step; - Number(const char* name, const char* id, unsigned int min, unsigned int max, unsigned int step, onMessage f) - : StatefulCommand(name, id, "number", f), min(min), max(max), step(step) {} + Number(const char* name, const char* id, onMessage f) : StatefulCommand(name, id, "number", f) {} void updateState(unsigned int value) { StateConfig::updateState(to_string(value).c_str()); From 2a5fb84d83f9f332408b819c15f260fdeedf7bad Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Mon, 28 Oct 2024 08:55:23 +0100 Subject: [PATCH 07/11] bug fix - StatefulCommand was wrongly initialized with number type, instead of the incoming parameter --- src/ha.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ha.h b/src/ha.h index 5886ee6..ee64123 100644 --- a/src/ha.h +++ b/src/ha.h @@ -325,7 +325,7 @@ namespace Ha { static unordered_map mapRestoreStateTopics; StatefulCommand(const char* name, const char* id, const char* type, onMessage f) - : Command(name, id, "number", f), StateConfig(this) {} + : Command(name, id, type, f), StateConfig(this) {} void restoreFromState() { mapRestoreStateTopics.insert({stateTopic, this}); From 1787f20ddb6b2f313db8a0c7cd18b94a856e0caf Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Mon, 28 Oct 2024 11:49:56 +0100 Subject: [PATCH 08/11] refactoring: combine composition and inheritance for building the configuration --- library.json | 2 +- src/ha.h | 113 ++++++++++++++++++++++++++++----------------------- 2 files changed, 63 insertions(+), 52 deletions(-) diff --git a/library.json b/library.json index 04fd2d0..8e8ed86 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json", "name": "ha-mqtt", - "version": "1.3.1", + "version": "1.4.0", "description": "Home Assistant classes for integration with MQTT auto discovery", "repository": { "type": "git", diff --git a/src/ha.h b/src/ha.h index ee64123..ff1e640 100644 --- a/src/ha.h +++ b/src/ha.h @@ -13,7 +13,11 @@ namespace Ha { uint16_t(*publisher)(const char* topic, const char* message); typedef void (*onMessage)(const char* msg); - struct DeviceConfig { + struct Config { + virtual void buildConfig(JsonDocument& jsonDoc) = 0; + }; + + struct DeviceConfig : Config { const char* id = nullptr; const char* name = nullptr; const char* model = nullptr; @@ -50,7 +54,7 @@ namespace Ha { return *this; } - void buildConfig(JsonDocument& jsonDoc) { + void buildConfig(JsonDocument& jsonDoc) override { JsonObject device = jsonDoc.createNestedObject("device"); if (name) device["name"] = name; if (model) device["model"] = model; @@ -60,9 +64,12 @@ namespace Ha { JsonArray identifiers = device.createNestedArray("identifiers"); identifiers.add(id); } + + private: + DeviceConfig(const char* id) : id(id) {} }; - struct Component { + struct Component : Config { template struct JsonPairs { @@ -121,9 +128,7 @@ namespace Ha { snprintf(configTopic, sizeof(configTopic), BASE_TOPIC"/config", type, MAIN_DEVICE_ID, id); } - virtual void buildComponentConfig(JsonDocument& jsonDoc) = 0; - - void buildConfig(JsonDocument& jsonDoc) { + void buildConfig(JsonDocument& jsonDoc) override { if (mainDevice) mainDevice->buildConfig(jsonDoc); if (entityCategory) jsonDoc["entity_category"] = entityCategory; if (deviceClass) jsonDoc["device_class"] = deviceClass; @@ -132,8 +137,6 @@ namespace Ha { jsonDoc["unique_id"] = (const char*)uniqueId; buildConfigTopic(); - buildComponentConfig(jsonDoc); - jsonBooleans.addToJson(jsonDoc); jsonNumbers.addToJson(jsonDoc); jsonStrings.addToJson(jsonDoc); @@ -267,12 +270,12 @@ namespace Ha { } }; - struct Command : Component { + struct Command : Config { bool retain = false; static unordered_map mapCommandTopics; - Command(const char* name, const char* id, const char* type, onMessage f) : Component(name, id, type), f(f) { - snprintf(commandTopic, sizeof(commandTopic), BASE_TOPIC"/set", type, MAIN_DEVICE_ID, id); + Command(Component* cmp, onMessage f) : f(f), cmp(cmp) { + snprintf(commandTopic, sizeof(commandTopic), BASE_TOPIC"/set", cmp->type, MAIN_DEVICE_ID, cmp->id); mapCommandTopics.insert({ string(commandTopic), this }); } @@ -283,28 +286,29 @@ namespace Ha { protected: char commandTopic[TOPIC_SIZE]; onMessage f; - virtual void buildCommandConfig(JsonDocument& jsonDoc) = 0; - void buildComponentConfig(JsonDocument& jsonDoc) override { + void buildConfig(JsonDocument& jsonDoc) override { jsonDoc["command_topic"] = (const char*)commandTopic; if (retain) jsonDoc["retain"] = true; - buildCommandConfig(jsonDoc); + } + private: + Component* cmp; + }; + + struct Button : Component, Command { + + Button(const char* name, const char* id, onMessage f = nullptr) : Component(name, id, "button"), Command(this, f) {} + + void buildConfig(JsonDocument& jsonDoc) override { + Component::buildConfig(jsonDoc); + Command::buildConfig(jsonDoc); } }; - struct Button : Command { - - Button(const char* name, const char* id, onMessage f = nullptr) : Command(name, id, "button", f) {} - - protected: - void buildCommandConfig(JsonDocument& jsonDoc) override {} - - }; - - struct StateConfig { + struct State : Config { char stateTopic[TOPIC_SIZE]; - StateConfig(Component* cmp) : cmp(cmp) {} + State(Component* cmp) : cmp(cmp) {} void withStateTopic() { snprintf(stateTopic, sizeof(stateTopic), BASE_TOPIC"/state", cmp->type, MAIN_DEVICE_ID, cmp->id); @@ -315,66 +319,80 @@ namespace Ha { } protected: - Component* cmp; - void buildConfig(JsonDocument& jsonDoc) { + void buildConfig(JsonDocument& jsonDoc) override { if (stateTopic[0]) jsonDoc["state_topic"] = (const char*)stateTopic; } + private: + Component* cmp; }; - struct StatefulCommand : Command, StateConfig { + struct StatefulCommand : Command, State { static unordered_map mapRestoreStateTopics; - StatefulCommand(const char* name, const char* id, const char* type, onMessage f) - : Command(name, id, type, f), StateConfig(this) {} + StatefulCommand(Component* cmp, onMessage f) : Command(cmp, f), State(cmp) {} void restoreFromState() { mapRestoreStateTopics.insert({stateTopic, this}); } + + protected: + void buildConfig(JsonDocument& jsonDoc) override { + Command::buildConfig(jsonDoc); + State::buildConfig(jsonDoc); + } }; - struct Switch : StatefulCommand { + struct Switch : Component, StatefulCommand { - Switch(const char* name, const char* id, onMessage f = nullptr) : StatefulCommand(name, id, "switch", f) {} + Switch(const char* name, const char* id, onMessage f = nullptr) : Component(name, id, "switch"), StatefulCommand(this, f) {} void updateState(bool state) { - StateConfig::updateState(state ? "ON" : "OFF"); + State::updateState(state ? "ON" : "OFF"); } - protected: - void buildCommandConfig(JsonDocument& jsonDoc) override { - StateConfig::buildConfig(jsonDoc); + void buildConfig(JsonDocument& jsonDoc) override { + Component::buildConfig(jsonDoc); + StatefulCommand::buildConfig(jsonDoc); } }; - struct Number : StatefulCommand { + struct Number : Component, StatefulCommand { unsigned int min, max, step; - Number(const char* name, const char* id, onMessage f) : StatefulCommand(name, id, "number", f) {} + Number(const char* name, const char* id, onMessage f) : Component(name, id, "number"), StatefulCommand(this, f) {} void updateState(unsigned int value) { - StateConfig::updateState(to_string(value).c_str()); + State::updateState(to_string(value).c_str()); } - protected: - void buildCommandConfig(JsonDocument& jsonDoc) override { - StateConfig::buildConfig(jsonDoc); + void buildConfig(JsonDocument& jsonDoc) override { + Component::buildConfig(jsonDoc); + StatefulCommand::buildConfig(jsonDoc); jsonDoc["min"] = min; jsonDoc["max"] = max; jsonDoc["step"] = step; } }; - struct Sensor : Component, StateConfig { + struct Sensor : Component, State { const char* unitMeasure = nullptr; const char* valueTemplate = nullptr; unsigned int precision = 2; static unordered_map mapSensors; - Sensor(const char* name, const char* id) : Component(name, id, "sensor"), StateConfig(this) { + Sensor(const char* name, const char* id) : Component(name, id, "sensor"), State(this) { withStateTopic(); mapSensors.insert({ string(id), this }); } + void buildConfig(JsonDocument& jsonDoc) override { + Component::buildConfig(jsonDoc); + State::buildConfig(jsonDoc); + if (unitMeasure) jsonDoc["unit_of_measurement"] = unitMeasure; + if (valueTemplate) jsonDoc["value_template"] = valueTemplate; + if (isNumericSensor()) jsonDoc["suggested_display_precision"] = precision; + } + protected: void buildUniqueId() override { if (multiValueComponent && deviceClass) { @@ -392,13 +410,6 @@ namespace Ha { } } - void buildComponentConfig(JsonDocument& jsonDoc) override { - StateConfig::buildConfig(jsonDoc); - if (unitMeasure) jsonDoc["unit_of_measurement"] = unitMeasure; - if (valueTemplate) jsonDoc["value_template"] = valueTemplate; - if (isNumericSensor()) jsonDoc["suggested_display_precision"] = precision; - } - private: bool isNumericSensor() { return deviceClass || unitMeasure; From 92b5837538a4ad8c1b861f4c406429300b854d4f Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Mon, 28 Oct 2024 12:05:34 +0100 Subject: [PATCH 09/11] change parameters' order in Component constructor - id is mandatory --- src/ha.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ha.h b/src/ha.h index ff1e640..216559f 100644 --- a/src/ha.h +++ b/src/ha.h @@ -87,11 +87,12 @@ namespace Ha { } }; - const char* entityCategory = nullptr; - const char* deviceClass = nullptr; - const char* name = nullptr; const char* id = nullptr; const char* type = nullptr; + + const char* name = nullptr; + const char* entityCategory = nullptr; + const char* deviceClass = nullptr; char configTopic[TOPIC_SIZE]; JsonPairs jsonBooleans; JsonPairs jsonNumbers; @@ -100,7 +101,7 @@ namespace Ha { static List components; bool multiValueComponent = false; - Component(const char* name, const char* id, const char* type) : name(name), id(id), type(type) { + Component(const char* id, const char* name, const char* type) : id(id), type(type), name(name) { components.add(this); } @@ -297,7 +298,7 @@ namespace Ha { struct Button : Component, Command { - Button(const char* name, const char* id, onMessage f = nullptr) : Component(name, id, "button"), Command(this, f) {} + Button(const char* name, const char* id, onMessage f = nullptr) : Component(id, name, "button"), Command(this, f) {} void buildConfig(JsonDocument& jsonDoc) override { Component::buildConfig(jsonDoc); @@ -344,7 +345,7 @@ namespace Ha { struct Switch : Component, StatefulCommand { - Switch(const char* name, const char* id, onMessage f = nullptr) : Component(name, id, "switch"), StatefulCommand(this, f) {} + Switch(const char* name, const char* id, onMessage f = nullptr) : Component(id, name, "switch"), StatefulCommand(this, f) {} void updateState(bool state) { State::updateState(state ? "ON" : "OFF"); @@ -359,7 +360,7 @@ namespace Ha { struct Number : Component, StatefulCommand { unsigned int min, max, step; - Number(const char* name, const char* id, onMessage f) : Component(name, id, "number"), StatefulCommand(this, f) {} + Number(const char* name, const char* id, onMessage f) : Component(id, name, "number"), StatefulCommand(this, f) {} void updateState(unsigned int value) { State::updateState(to_string(value).c_str()); @@ -380,7 +381,7 @@ namespace Ha { unsigned int precision = 2; static unordered_map mapSensors; - Sensor(const char* name, const char* id) : Component(name, id, "sensor"), State(this) { + Sensor(const char* name, const char* id) : Component(id, name, "sensor"), State(this) { withStateTopic(); mapSensors.insert({ string(id), this }); } From 0eec971560bb1a36c1fd3cc2a584337205fcc481 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Mon, 28 Oct 2024 12:13:42 +0100 Subject: [PATCH 10/11] add support for jsonAttributesTemplate and remove overrideConfig --- src/esp.h | 3 +-- src/ha.h | 46 ++++++++++------------------------------------ 2 files changed, 11 insertions(+), 38 deletions(-) diff --git a/src/esp.h b/src/esp.h index cfa0034..5cfb8a7 100644 --- a/src/esp.h +++ b/src/esp.h @@ -35,8 +35,7 @@ namespace HaESP { .withUnitMeasure("%") .withPrecision(0) .withValueTemplate("{{ value_json.fragmentation }}") - .overrideConfig("json_attributes_topic", heap->stateTopic) - .overrideConfig("json_attributes_template", "{{ value_json.heap | tojson }}") + .withJsonAttributes("{{ value_json.heap | tojson }}") .build()); builder.addDiagnostic(Builder::instance(new Sensor{ "Heap free", "heap_free" }) .withDeviceClass("data_size") diff --git a/src/ha.h b/src/ha.h index 216559f..ef4c2d7 100644 --- a/src/ha.h +++ b/src/ha.h @@ -70,22 +70,6 @@ namespace Ha { }; struct Component : Config { - - template - struct JsonPairs { - typedef pair JsonPair; - unordered_map jsonPairs; - - void add(JsonPair pair) { - jsonPairs.insert(pair); - } - - void addToJson(JsonDocument& jsonDoc) { - for (JsonPair el : jsonPairs) { - jsonDoc[el.first] = el.second; - } - } - }; const char* id = nullptr; const char* type = nullptr; @@ -94,9 +78,6 @@ namespace Ha { const char* entityCategory = nullptr; const char* deviceClass = nullptr; char configTopic[TOPIC_SIZE]; - JsonPairs jsonBooleans; - JsonPairs jsonNumbers; - JsonPairs jsonStrings; DeviceConfig* mainDevice = nullptr; static List components; bool multiValueComponent = false; @@ -137,10 +118,6 @@ namespace Ha { buildUniqueId(); jsonDoc["unique_id"] = (const char*)uniqueId; buildConfigTopic(); - - jsonBooleans.addToJson(jsonDoc); - jsonNumbers.addToJson(jsonDoc); - jsonStrings.addToJson(jsonDoc); } }; @@ -215,18 +192,8 @@ namespace Ha { return *this; } - Builder& overrideConfig(const char* key, bool value) { - cmp->jsonBooleans.add({key, value}); - return *this; - } - - Builder& overrideConfig(const char* key, float value) { - cmp->jsonNumbers.add({key, value}); - return *this; - } - - Builder& overrideConfig(const char* key, const char* value) { - cmp->jsonStrings.add({key, value}); + Builder& withJsonAttributes(const char* value) { + cmp->jsonAttributesTemplate = value; return *this; } @@ -308,6 +275,7 @@ namespace Ha { struct State : Config { char stateTopic[TOPIC_SIZE]; + const char* jsonAttributesTemplate = nullptr; State(Component* cmp) : cmp(cmp) {} @@ -321,7 +289,13 @@ namespace Ha { protected: void buildConfig(JsonDocument& jsonDoc) override { - if (stateTopic[0]) jsonDoc["state_topic"] = (const char*)stateTopic; + if (stateTopic[0]) { + jsonDoc["state_topic"] = (const char*)stateTopic; + if (jsonAttributesTemplate) { + jsonDoc["json_attributes_template"] = jsonAttributesTemplate; + jsonDoc["json_attributes_topic"] = (const char*)stateTopic; + } + } } private: Component* cmp; From 9930171d58d56ec18d9dc4650fd0e90da2ffdf6c Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Mon, 28 Oct 2024 12:37:15 +0100 Subject: [PATCH 11/11] simplify: move building of configTopic and uniqueId inside component --- src/ha.h | 51 ++++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/ha.h b/src/ha.h index ef4c2d7..111ebc1 100644 --- a/src/ha.h +++ b/src/ha.h @@ -77,7 +77,6 @@ namespace Ha { const char* name = nullptr; const char* entityCategory = nullptr; const char* deviceClass = nullptr; - char configTopic[TOPIC_SIZE]; DeviceConfig* mainDevice = nullptr; static List components; bool multiValueComponent = false; @@ -101,24 +100,35 @@ namespace Ha { } protected: - char uniqueId[50]; - virtual void buildUniqueId() { - snprintf(uniqueId, sizeof(uniqueId), "%s_%s", MAIN_DEVICE_ID, id); - } - - virtual void buildConfigTopic() { - snprintf(configTopic, sizeof(configTopic), BASE_TOPIC"/config", type, MAIN_DEVICE_ID, id); - } - void buildConfig(JsonDocument& jsonDoc) override { if (mainDevice) mainDevice->buildConfig(jsonDoc); if (entityCategory) jsonDoc["entity_category"] = entityCategory; if (deviceClass) jsonDoc["device_class"] = deviceClass; jsonDoc["name"] = name; - buildUniqueId(); - jsonDoc["unique_id"] = (const char*)uniqueId; + + buildUniqueId(jsonDoc); buildConfigTopic(); } + private: + char configTopic[TOPIC_SIZE]; + + void buildUniqueId(JsonDocument& jsonDoc) { + char uniqueId[50]; + if (multiValueComponent && deviceClass) { + snprintf(uniqueId, sizeof(uniqueId), "%s_%s_%s", MAIN_DEVICE_ID, deviceClass, id); + } else { + snprintf(uniqueId, sizeof(uniqueId), "%s_%s", MAIN_DEVICE_ID, id); + } + jsonDoc["unique_id"] = uniqueId; + } + + void buildConfigTopic() { + if (multiValueComponent && deviceClass) { + snprintf(configTopic, sizeof(configTopic), BASE_TOPIC"_%s""/config", type, MAIN_DEVICE_ID, deviceClass, id); + } else { + snprintf(configTopic, sizeof(configTopic), BASE_TOPIC"/config", type, MAIN_DEVICE_ID, id); + } + } }; struct AbstractBuilder { @@ -368,23 +378,6 @@ namespace Ha { if (isNumericSensor()) jsonDoc["suggested_display_precision"] = precision; } - protected: - void buildUniqueId() override { - if (multiValueComponent && deviceClass) { - snprintf(uniqueId, sizeof(uniqueId), "%s_%s_%s", MAIN_DEVICE_ID, deviceClass, id); - } else { - Component::buildUniqueId(); - } - } - - void buildConfigTopic() override { - if (multiValueComponent && deviceClass) { - snprintf(configTopic, sizeof(configTopic), BASE_TOPIC"_%s""/config", type, MAIN_DEVICE_ID, deviceClass, id); - } else { - Component::buildConfigTopic(); - } - } - private: bool isNumericSensor() { return deviceClass || unitMeasure;