Merge branch 'v2.0.0'
This commit is contained in:
commit
80c04a2d80
@ -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",
|
||||||
|
|||||||
33
src/esp.h
33
src/esp.h
@ -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
129
src/ha.h
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user