Merge branch 'memory-optimization' into v2.0.0

This commit is contained in:
Nicu Hodos 2025-10-11 16:18:47 +02:00
commit 403bc179ef
3 changed files with 51 additions and 31 deletions

View File

@ -6,7 +6,7 @@
using namespace std; using namespace std;
#define JSON_SIZE 512 #define JSON_SIZE 1024
#define TOPIC_SIZE 255 #define TOPIC_SIZE 255
#define CONFIG_TOPIC "homeassistant/%s/" MAIN_DEVICE_ID "/%s" #define CONFIG_TOPIC "homeassistant/%s/" MAIN_DEVICE_ID "/%s"
#define BASE_TOPIC MAIN_DEVICE_ID "/%s" #define BASE_TOPIC MAIN_DEVICE_ID "/%s"
@ -92,15 +92,13 @@ namespace Ha {
void publishConfig() { void publishConfig() {
StaticJsonDocument<JSON_SIZE> jsonDoc; StaticJsonDocument<JSON_SIZE> jsonDoc;
buildConfig(jsonDoc); buildConfig(jsonDoc);
auto configTopic = buildConfigTopic();
char message[JSON_SIZE] = {}; publisher(configTopic.get(), jsonDoc.as<string>().c_str());
serializeJson(jsonDoc, message);
publisher(configTopic, message);
} }
void publishCleanupConfig() { void publishCleanupConfig() {
publisher(configTopic, ""); auto configTopic = buildConfigTopic();
publisher(configTopic.get(), "");
} }
void toJson(JsonDocument& jsonDoc) { void toJson(JsonDocument& jsonDoc) {
@ -119,11 +117,8 @@ namespace Ha {
jsonDoc["name"] = name; jsonDoc["name"] = name;
buildUniqueId(jsonDoc); buildUniqueId(jsonDoc);
buildConfigTopic();
} }
private: private:
char configTopic[TOPIC_SIZE] = {};
void buildUniqueId(JsonDocument& jsonDoc) { void buildUniqueId(JsonDocument& jsonDoc) {
char uniqueId[50]; char uniqueId[50];
if (multiValueComponent && deviceClass) { if (multiValueComponent && deviceClass) {
@ -134,12 +129,14 @@ namespace Ha {
jsonDoc["unique_id"] = uniqueId; jsonDoc["unique_id"] = uniqueId;
} }
void buildConfigTopic() { unique_ptr<char[]> buildConfigTopic() {
unique_ptr<char[]> topic(new char[TOPIC_SIZE]);
if (multiValueComponent && deviceClass) { if (multiValueComponent && deviceClass) {
snprintf(configTopic, sizeof(configTopic), CONFIG_TOPIC"_%s""/config", type, deviceClass, id); snprintf(topic.get(), TOPIC_SIZE, CONFIG_TOPIC"_%s""/config", type, deviceClass, id);
} else { } else {
snprintf(configTopic, sizeof(configTopic), CONFIG_TOPIC"/config", type, id); snprintf(topic.get(), TOPIC_SIZE, CONFIG_TOPIC"/config", type, id);
} }
return topic;
} }
}; };
@ -149,8 +146,8 @@ namespace Ha {
inline static unordered_map<string, Command*> mapCommandIds; inline static unordered_map<string, Command*> mapCommandIds;
Command(Component* cmp, onMessage f) : f(f), cmp(cmp) { Command(Component* cmp, onMessage f) : f(f), cmp(cmp) {
snprintf(commandTopic, sizeof(commandTopic), BASE_TOPIC"/set", cmp->id); auto commandTopic = buildTopic();
mapCommandTopics.insert({ string(commandTopic), this }); mapCommandTopics.insert({ string(commandTopic.get()), this });
mapCommandIds.insert({ string(cmp->id), this }); mapCommandIds.insert({ string(cmp->id), this });
} }
@ -163,39 +160,48 @@ namespace Ha {
} }
protected: protected:
char commandTopic[TOPIC_SIZE] = {};
onMessage f; onMessage f;
void buildConfig(JsonDocument& jsonDoc) override { void buildConfig(JsonDocument& jsonDoc) override {
jsonDoc["command_topic"] = (const char*)commandTopic; auto commandTopic = buildTopic();
jsonDoc["command_topic"] = commandTopic.get();
if (retain) jsonDoc["retain"] = true; if (retain) jsonDoc["retain"] = true;
} }
private: private:
Component* cmp; Component* cmp;
unique_ptr<char[]> buildTopic() {
unique_ptr<char[]> topic(new char[TOPIC_SIZE]);
snprintf(topic.get(), TOPIC_SIZE, BASE_TOPIC "/set", cmp->id);
return topic;
}
}; };
struct State : Config { struct State : Config {
char stateTopic[TOPIC_SIZE] = {}; char* topic;
const char* jsonAttributesTemplate = nullptr; const char* jsonAttributesTemplate = nullptr;
const char* valueTemplate = nullptr; const char* valueTemplate = nullptr;
State(Component* cmp) : cmp(cmp) {} State(Component* cmp) : cmp(cmp) {
auto len = snprintf(nullptr, 0, BASE_TOPIC"/state", cmp->id);
topic = new char[len + 1];
}
void withStateTopic() { void withStateTopic() {
snprintf(stateTopic, sizeof(stateTopic), BASE_TOPIC"/state", cmp->id); sprintf(topic, BASE_TOPIC"/state", cmp->id);
} }
void updateState(const char* message) { void updateState(const char* message) {
if (stateTopic[0]) publisher(stateTopic, message); if (topic[0]) publisher(topic, message);
} }
protected: protected:
void buildConfig(JsonDocument& jsonDoc) override { void buildConfig(JsonDocument& jsonDoc) override {
if (stateTopic[0]) { if (topic[0]) {
jsonDoc["state_topic"] = (const char*)stateTopic; jsonDoc["state_topic"] = (const char*)topic;
if (jsonAttributesTemplate) { if (jsonAttributesTemplate) {
jsonDoc["json_attributes_template"] = jsonAttributesTemplate; jsonDoc["json_attributes_template"] = jsonAttributesTemplate;
jsonDoc["json_attributes_topic"] = (const char*)stateTopic; jsonDoc["json_attributes_topic"] = (const char*)topic;
} }
if (valueTemplate) jsonDoc["value_template"] = valueTemplate; if (valueTemplate) jsonDoc["value_template"] = valueTemplate;
} }
@ -209,9 +215,9 @@ namespace Ha {
StatefulCommand(Component* cmp, onMessage f) : Command(cmp, f), State(cmp) {} StatefulCommand(Component* cmp, onMessage f) : Command(cmp, f), State(cmp) {}
void restoreStateFromCommand() { void restoreStateFromTopic() {
withStateTopic(); withStateTopic();
mapRestoreStateTopics.insert({stateTopic, this}); mapRestoreStateTopics.insert({State::topic, this});
} }
protected: protected:
@ -508,12 +514,12 @@ namespace Ha {
[[deprecated("Use restoreStateFromCommand() instead")]] [[deprecated("Use restoreStateFromCommand() instead")]]
Builder& restoreFromState() { Builder& restoreFromState() {
cmp->restoreStateFromCommand(); cmp->restoreStateFromTopic();
return *this; return *this;
} }
Builder& restoreStateFromCommand() { Builder& restoreStateFromTopic() {
cmp->restoreStateFromCommand(); cmp->restoreStateFromTopic();
return *this; return *this;
} }
}; };

View File

@ -30,10 +30,10 @@ namespace WebServer {
server.on("/commands", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/commands", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream("application/json"); AsyncResponseStream *response = request->beginResponseStream("application/json");
DynamicJsonDocument jsonResponse(JSON_SIZE*10); DynamicJsonDocument jsonResponse(5120);
JsonArray array = jsonResponse.to<JsonArray>(); JsonArray array = jsonResponse.to<JsonArray>();
for (auto it = Command::mapCommandIds.begin(); it != Command::mapCommandIds.end(); ++it) { for (auto it = Command::mapCommandIds.begin(); it != Command::mapCommandIds.end(); ++it) {
StaticJsonDocument<JSON_SIZE/2> jsonDoc; StaticJsonDocument<256> jsonDoc;
it->second->toJson(jsonDoc); it->second->toJson(jsonDoc);
array.add(jsonDoc); array.add(jsonDoc);
} }

View File

@ -1,5 +1,6 @@
#include <unity.h> #include <unity.h>
#include <unordered_map> #include <unordered_map>
#include <memory>
#define MAIN_DEVICE_ID "test" #define MAIN_DEVICE_ID "test"
@ -208,6 +209,18 @@ void testBinarySensor(void) {
TEST_ASSERT_NOT_NULL(GenericSensor::mapSensors["id"]); TEST_ASSERT_NOT_NULL(GenericSensor::mapSensors["id"]);
} }
void testPublisher(void) {
Ha::publisher = [](const char* topic, const char* message) -> uint16_t {
TEST_ASSERT_EQUAL_STRING("{\"name\":\"a_name\",\"unique_id\":\"test_id\",\"command_topic\":\"test/id/set\"}", message);
return 0;
};
Switch s("a_name", "id");
StaticJsonDocument<256> doc;
s.buildConfig(doc);
s.publishConfig();
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
UNITY_BEGIN(); UNITY_BEGIN();
RUN_TEST(testDevice); RUN_TEST(testDevice);
@ -224,5 +237,6 @@ int main(int argc, char **argv) {
RUN_TEST(testSwitchWithState); RUN_TEST(testSwitchWithState);
RUN_TEST(testNumber); RUN_TEST(testNumber);
RUN_TEST(testBinarySensor); RUN_TEST(testBinarySensor);
RUN_TEST(testPublisher);
return UNITY_END(); return UNITY_END();
} }