Merge branch 'v2.0.0'

This commit is contained in:
Nicu Hodos 2025-10-11 16:19:14 +02:00
commit 80c04a2d80
8 changed files with 133 additions and 101 deletions

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json", "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
"name": "ha-mqtt", "name": "ha-mqtt",
"version": "1.13.0", "version": "2.0.0",
"description": "Home Assistant classes for integration with MQTT auto discovery", "description": "Home Assistant classes for integration with MQTT auto discovery",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -22,7 +22,7 @@ namespace HaESP {
ESP.getHeapStats(&hfree, &hmax, &hfrag); ESP.getHeapStats(&hfree, &hmax, &hfrag);
char value[128]; 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); snprintf_P(value, sizeof(value), PSTR("{\"fragmentation\":%lu,\"heap\":{\"Heap free\":\"%lu B\",\"Heap max free block\":\"%u B\"}}"), hfrag, hfree, hmax);
GenericSensor::mapSensors["heap_fragmentation"]->updateState(value); GenericSensor::mapSensors["heap_fragmentation"]->updateState(value);
GenericSensor::mapSensors["heap_free"]->updateState(to_string(hfree).c_str()); GenericSensor::mapSensors["heap_free"]->updateState(to_string(hfree).c_str());
GenericSensor::mapSensors["heap_max_free_block"]->updateState(to_string(hmax).c_str()); GenericSensor::mapSensors["heap_max_free_block"]->updateState(to_string(hmax).c_str());
@ -42,38 +42,43 @@ namespace HaESP {
template <class T> template <class T>
Builder<T>& heapStats(Builder<T>& builder) { Builder<T>& heapStats(Builder<T>& builder) {
builder.addDiagnostic(Builder<Sensor>::instance(new Sensor{ "Heap fragmentation", "heap_fragmentation" }) builder.addDiagnostic(Builder<Sensor>(new Sensor{ "Heap fragmentation", "heap_fragmentation" })
.withUnitMeasure("%") .withUnitMeasure("%")
.withPrecision(0) .withPrecision(0)
.withValueTemplate("{{ value_json.fragmentation }}") .withValueTemplate("{{ value_json.fragmentation }}")
.withJsonAttributes("{{ value_json.heap | tojson }}") .withJsonAttributes("{{ value_json.heap | tojson }}")
.build()); .build())
builder.addDiagnostic(Builder<Sensor>::instance(new Sensor{ "Heap free", "heap_free" }) .addDiagnostic(Builder<Sensor>(new Sensor{ "Heap free", "heap_free" })
.withDeviceClass("data_size") .withDeviceClass("data_size")
.withUnitMeasure("B") .withUnitMeasure("B")
.withPrecision(0) .withPrecision(0)
.build() .build())
); .addDiagnostic(Builder<Sensor>(new Sensor{ "Heap max free block", "heap_max_free_block" })
builder.addDiagnostic(Builder<Sensor>::instance(new Sensor{ "Heap max free block", "heap_max_free_block" })
.withDeviceClass("data_size") .withDeviceClass("data_size")
.withUnitMeasure("B") .withUnitMeasure("B")
.withPrecision(0) .withPrecision(0)
.build() .build())
); .addSecondary(new Button{"Update heap stats", "update_heap_stats",
[](const char* msg) {
if ((strcmp("PRESS", msg) == 0) && tHeap.isEnabled()) {
tHeap.cancel();
tHeap.restart();
}}});
activeSensors.heapStats = true; activeSensors.heapStats = true;
return builder; return builder;
} }
template <class T> template <class T>
Builder<T>& restartInfo(Builder<T>& builder) { Builder<T>& restartInfo(Builder<T>& builder) {
builder.addDiagnostic(Builder<Sensor>::instance((new Sensor{ "Restart reason", "restart_reason" })).build()); builder.addDiagnostic((new Sensor{ "Restart reason", "restart_reason" }));
activeSensors.restartInfo = true; activeSensors.restartInfo = true;
return builder; return builder;
} }
template <class T> template <class T>
Builder<T>& wifiInfo(Builder<T>& builder) { Builder<T>& wifiInfo(Builder<T>& builder) {
builder.addDiagnostic(Builder<Sensor>::instance(( builder.addDiagnostic(Builder<Sensor>((
new Sensor{ "WiFi Signal (RSSI)", "wifi_signal_strength" })) new Sensor{ "WiFi Signal (RSSI)", "wifi_signal_strength" }))
.withDeviceClass("signal_strength") .withDeviceClass("signal_strength")
.withUnitMeasure("dBm") .withUnitMeasure("dBm")
@ -85,12 +90,12 @@ namespace HaESP {
} }
Builder<Button>& restartButton() { Builder<Button>& restartButton() {
return Builder<Button>::instance(new Button{"Restart", "restart", return (new Builder<Button>(new Button{"Restart", "restart",
[](const char* msg) { [](const char* msg) {
if (strcmp("PRESS", msg) == 0) tRestart.restartDelayed(); if (strcmp("PRESS", msg) == 0) tRestart.restartDelayed();
} }
}) }))
.withIcon("mdi:restart"); ->withIcon("mdi:restart");
} }
void enableSensors() { void enableSensors() {

129
src/ha.h
View File

@ -6,13 +6,13 @@
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"
namespace Ha { namespace Ha {
uint16(*publisher)(const char*, const char*); uint16_t (*publisher)(const char*, const char*);
typedef void (*onMessage)(const char*); typedef void (*onMessage)(const char*);
struct Config { struct Config {
@ -75,6 +75,7 @@ namespace Ha {
const char* id = nullptr; const char* id = nullptr;
const char* type = nullptr; const char* type = nullptr;
const char* entityId = nullptr;
const char* name = nullptr; const char* name = nullptr;
const char* entityCategory = nullptr; const char* entityCategory = nullptr;
@ -91,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) {
@ -111,17 +110,15 @@ namespace Ha {
protected: protected:
void buildConfig(JsonDocument& jsonDoc) override { void buildConfig(JsonDocument& jsonDoc) override {
if (mainDevice) mainDevice->buildConfig(jsonDoc); if (mainDevice) mainDevice->buildConfig(jsonDoc);
if (entityId) jsonDoc["object_id"] = entityId;
if (entityCategory) jsonDoc["entity_category"] = entityCategory; if (entityCategory) jsonDoc["entity_category"] = entityCategory;
if (deviceClass) jsonDoc["device_class"] = deviceClass; if (deviceClass) jsonDoc["device_class"] = deviceClass;
if (icon) jsonDoc["icon"] = icon; if (icon) jsonDoc["icon"] = icon;
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) {
@ -132,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;
} }
}; };
@ -147,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 });
} }
@ -161,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;
} }
@ -207,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:
@ -258,14 +266,14 @@ namespace Ha {
}; };
struct Number : Component, StatefulCommand { struct Number : Component, StatefulCommand {
unsigned int min = 1; uint16_t min = 1;
unsigned int max = 100; uint16_t max = 100;
unsigned int step = 1; uint16_t step = 1;
const char* unitMeasure = nullptr; const char* unitMeasure = nullptr;
Number(const char* name, const char* id, onMessage f) : Component(id, name, "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) { void updateState(uint16_t value) {
State::updateState(to_string(value).c_str()); State::updateState(to_string(value).c_str());
} }
@ -280,13 +288,13 @@ namespace Ha {
}; };
struct Text : Component, StatefulCommand { struct Text : Component, StatefulCommand {
unsigned int min = 1; uint16_t min = 1;
unsigned int max = 100; uint16_t max = 100;
const char* pattern = nullptr; const char* pattern = nullptr;
Text(const char* name, const char* id, onMessage f) : Component(id, name, "text"), StatefulCommand(this, f) {} Text(const char* name, const char* id, onMessage f) : Component(id, name, "text"), StatefulCommand(this, f) {}
void updateState(unsigned int value) { void updateState(uint16_t value) {
State::updateState(to_string(value).c_str()); State::updateState(to_string(value).c_str());
} }
@ -315,7 +323,7 @@ namespace Ha {
}; };
struct BinarySensor : GenericSensor { struct BinarySensor : GenericSensor {
unsigned int off_delay_seconds = 0; uint16_t off_delay_seconds = 0;
BinarySensor(const char* id, const char* name = nullptr) : GenericSensor(id, name, "binary_sensor") {} BinarySensor(const char* id, const char* name = nullptr) : GenericSensor(id, name, "binary_sensor") {}
@ -327,7 +335,7 @@ namespace Ha {
struct Sensor : GenericSensor { struct Sensor : GenericSensor {
const char* unitMeasure = nullptr; const char* unitMeasure = nullptr;
unsigned int precision = 2; uint16_t precision = 2;
SensorStateClass sensorStateClass; SensorStateClass sensorStateClass;
Sensor(const char* name, const char* id) : GenericSensor(id, name, "sensor") {} Sensor(const char* name, const char* id) : GenericSensor(id, name, "sensor") {}
@ -385,38 +393,16 @@ namespace Ha {
} }
}; };
struct AbstractBuilder {
inline static List<AbstractBuilder> builders;
static void deleteAll() {
builders.forEach([](AbstractBuilder* builder) { delete builder; });
builders.empty();
}
protected:
AbstractBuilder() {
builders.add(this);
}
};
template <class T> template <class T>
struct Builder : AbstractBuilder { struct Builder {
T* cmp; T* cmp;
Builder(T* cmp) : AbstractBuilder(), cmp(cmp) {} Builder(T* cmp) : cmp(cmp) {}
Builder(const char* id) : AbstractBuilder() { Builder(const char* id) {
cmp = new T{ id }; cmp = new T{ id };
} }
static Builder& instance(T* c) {
return *(new Builder<T>(c));
}
static Builder& instance(const char* id) {
return *(new Builder<T>(id));
}
T* build() { T* build() {
return static_cast<T*>(cmp); return static_cast<T*>(cmp);
} }
@ -426,6 +412,11 @@ namespace Ha {
return *this; return *this;
} }
Builder& withEntityId(const char* value) {
cmp->entityId = value;
return *this;
}
Builder& withUnitMeasure(const char* value) { Builder& withUnitMeasure(const char* value) {
cmp->unitMeasure = value; cmp->unitMeasure = value;
return *this; return *this;
@ -441,7 +432,7 @@ namespace Ha {
return *this; return *this;
} }
Builder& withPrecision(unsigned int value) { Builder& withPrecision(uint16_t value) {
cmp->precision = value; cmp->precision = value;
return *this; return *this;
} }
@ -451,22 +442,22 @@ namespace Ha {
return *this; return *this;
} }
Builder& withMin(unsigned int value) { Builder& withMin(uint16_t value) {
cmp->min = value; cmp->min = value;
return *this; return *this;
} }
Builder& withMax(unsigned int value) { Builder& withMax(uint16_t value) {
cmp->max = value; cmp->max = value;
return *this; return *this;
} }
Builder& withStep(unsigned int value) { Builder& withStep(uint16_t value) {
cmp->step = value; cmp->step = value;
return *this; return *this;
} }
Builder& withOffDelaySeconds(unsigned int value) { Builder& withOffDelaySeconds(uint16_t value) {
cmp->off_delay_seconds = value; cmp->off_delay_seconds = value;
return *this; return *this;
} }
@ -523,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

@ -13,7 +13,7 @@ struct List {
void add(T* t) { void add(T* t) {
Container* c = new Container{t}; Container* c = new Container{t};
first == nullptr ? first = c : last->next = c; isEmpty() ? first = c : last->next = c;
last = c; last = c;
} }
@ -33,4 +33,8 @@ struct List {
first = last = nullptr; first = last = nullptr;
} }
bool isEmpty() {
return first == nullptr;
}
}; };

View File

@ -22,17 +22,15 @@ namespace Mqtt {
client.disconnect(); client.disconnect();
} }
uint16 publish(const char* topic, const char* message) { uint16_t publish(const char* topic, const char* message) {
return client.publish(topic, 0, true, message); return client.publish(topic, 0, true, message);
} }
void publishInit() { void publishInit() {
static bool firstTime = true; if (!Component::components.isEmpty()) {
if (firstTime) {
Component::components.forEach([](Component* c) { c->publishConfig(); }); Component::components.forEach([](Component* c) { c->publishConfig(); });
AbstractBuilder::deleteAll(); Component::components.empty();
HaESP::enableSensors(); HaESP::enableSensors();
firstTime = false;
} }
} }

View File

@ -6,9 +6,8 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "ha.h" #include "ha.h"
AsyncWebServer server(80);
namespace WebServer { namespace WebServer {
AsyncWebServer server(80);
void setup() { void setup() {
@ -31,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);
} }
@ -46,7 +45,9 @@ namespace WebServer {
server.onNotFound([](AsyncWebServerRequest *request) { server.onNotFound([](AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found"); request->send(404, "text/plain", "Not found");
}); });
}
void start() {
server.begin(); server.begin();
} }

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"
@ -8,7 +9,9 @@
using namespace Ha; using namespace Ha;
void setUp(void) { void setUp(void) {
// set stuff up here Ha::publisher = [](const char* topic, const char* message) -> uint16_t {
return 0;
};
} }
void tearDown(void) { void tearDown(void) {
@ -38,7 +41,7 @@ void testButton(void) {
b.buildConfig(doc); b.buildConfig(doc);
TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID"_id", doc["unique_id"]); TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID"_id", doc["unique_id"]);
TEST_ASSERT_EQUAL_STRING("homeassistant/button/" MAIN_DEVICE_ID "/id/set", doc["command_topic"]); TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID "/id/set", doc["command_topic"]);
TEST_ASSERT_FALSE(doc["retain"]); TEST_ASSERT_FALSE(doc["retain"]);
TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]); TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]); TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]);
@ -53,14 +56,14 @@ void testSensor(void) {
s.buildConfig(doc); s.buildConfig(doc);
TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID"_id", doc["unique_id"]); TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID"_id", doc["unique_id"]);
TEST_ASSERT_EQUAL_STRING("homeassistant/sensor/" MAIN_DEVICE_ID "/id/state", doc["state_topic"]); TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID "/id/state", doc["state_topic"]);
TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]); TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]); TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["entity_category"]); TEST_ASSERT_EQUAL_STRING(NULL, doc["entity_category"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["unit_of_measurement"]); TEST_ASSERT_EQUAL_STRING(NULL, doc["unit_of_measurement"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["value_template"]); TEST_ASSERT_EQUAL_STRING(NULL, doc["value_template"]);
TEST_ASSERT_EQUAL_INT(0, doc["suggested_display_precision"]); TEST_ASSERT_EQUAL_INT(0, doc["suggested_display_precision"]);
TEST_ASSERT_NOT_NULL(Sensor::mapSensors["id"]); TEST_ASSERT_NOT_NULL(GenericSensor::mapSensors["id"]);
} }
void testNumericSensor1(void) { void testNumericSensor1(void) {
@ -90,7 +93,7 @@ void testSwitch(void) {
s.buildConfig(doc); s.buildConfig(doc);
TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID"_id", doc["unique_id"]); TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID"_id", doc["unique_id"]);
TEST_ASSERT_EQUAL_STRING("homeassistant/switch/" MAIN_DEVICE_ID "/id/set", doc["command_topic"]); TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID "/id/set", doc["command_topic"]);
TEST_ASSERT_FALSE(doc["retain"]); TEST_ASSERT_FALSE(doc["retain"]);
TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]); TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]); TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]);
@ -107,7 +110,7 @@ void testSwitchWithState(void) {
StaticJsonDocument<256> doc; StaticJsonDocument<256> doc;
s.buildConfig(doc); s.buildConfig(doc);
TEST_ASSERT_EQUAL_STRING("homeassistant/switch/" MAIN_DEVICE_ID "/id/state", doc["state_topic"]); TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID "/id/state", doc["state_topic"]);
} }
void testNumber(void) { void testNumber(void) {
@ -121,7 +124,7 @@ void testNumber(void) {
TEST_ASSERT_EQUAL_INT16(100, doc["max"]); TEST_ASSERT_EQUAL_INT16(100, doc["max"]);
TEST_ASSERT_EQUAL_INT16(1, doc["step"]); TEST_ASSERT_EQUAL_INT16(1, doc["step"]);
TEST_ASSERT_EQUAL_STRING("homeassistant/number/" MAIN_DEVICE_ID "/id/set", doc["command_topic"]); TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID "/id/set", doc["command_topic"]);
TEST_ASSERT_FALSE(doc["retain"]); TEST_ASSERT_FALSE(doc["retain"]);
TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]); TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]); TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]);
@ -191,6 +194,33 @@ void testBatterySensor(void) {
TEST_ASSERT_EQUAL_INT(2, doc["suggested_display_precision"]); TEST_ASSERT_EQUAL_INT(2, doc["suggested_display_precision"]);
} }
void testBinarySensor(void) {
BinarySensor s("id", "a_name");
StaticJsonDocument<256> doc;
s.buildConfig(doc);
TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID"_id", doc["unique_id"]);
TEST_ASSERT_EQUAL_STRING(MAIN_DEVICE_ID "/id/state", doc["state_topic"]);
TEST_ASSERT_EQUAL_STRING("a_name", doc["name"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["device_class"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["entity_category"]);
TEST_ASSERT_EQUAL_STRING(NULL, doc["value_template"]);
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);
@ -206,5 +236,7 @@ int main(int argc, char **argv) {
RUN_TEST(testSwitch); RUN_TEST(testSwitch);
RUN_TEST(testSwitchWithState); RUN_TEST(testSwitchWithState);
RUN_TEST(testNumber); RUN_TEST(testNumber);
RUN_TEST(testBinarySensor);
RUN_TEST(testPublisher);
return UNITY_END(); return UNITY_END();
} }

View File

@ -30,6 +30,7 @@ void test_empty(void) {
list.empty(); list.empty();
TEST_ASSERT_EQUAL_PTR(nullptr, list.first); TEST_ASSERT_EQUAL_PTR(nullptr, list.first);
TEST_ASSERT_EQUAL_PTR(list.first, list.last); TEST_ASSERT_EQUAL_PTR(list.first, list.last);
TEST_ASSERT_TRUE(list.isEmpty());
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {