diff --git a/gateway/include/Protocol_1.h b/gateway/include/Protocol_1.h index ccbc329..6d9dd85 100644 --- a/gateway/include/Protocol_1.h +++ b/gateway/include/Protocol_1.h @@ -27,10 +27,9 @@ public: rcSwitch["raw_value"] = value; } - static char* buildId(const char* group, const unsigned char channel) { - char* uId = new char[30]; + static std::string buildId(const char* group, const unsigned char channel) { + char uId[30]; sprintf(uId, "%s_%d", group, channel); - return uId; + return std::string{ uId }; } - }; diff --git a/gateway/include/Protocol_2.h b/gateway/include/Protocol_2.h index f618898..589154e 100644 --- a/gateway/include/Protocol_2.h +++ b/gateway/include/Protocol_2.h @@ -33,16 +33,4 @@ public: } } - static char* buildId(const char* id) { - char* uId = new char[30]; - sprintf(uId, "%s", id); - return uId; - } - - static char* buildId(unsigned int id) { - char* uId = new char[30]; - sprintf(uId, "%d", id); - return uId; - } - }; \ No newline at end of file diff --git a/gateway/include/devices.h b/gateway/include/devices.h index 6ae8b46..46d395e 100644 --- a/gateway/include/devices.h +++ b/gateway/include/devices.h @@ -7,66 +7,66 @@ using namespace Ha; DeviceConfig* gatewayDevice = (new DeviceConfig{MAIN_DEVICE_ID})->withName("RC Gateway")->withManufacturer("Adafruit")->withModel("Huzzah Esp8266"); -DeviceConfig* atTinyDevice = (new DeviceConfig{})->withManufacturer("Atmel")->withModel("AtTiny85")->withParent(gatewayDevice); -struct OilTankRoomSensorBuilder { - static Sensor* build(const char* id) { - auto device = (new DeviceConfig{*atTinyDevice})->withName("Oil tank room")->withArea("Basement"); - device->id = id; - Sensor* sensor = (new Ha::TemperatureSensor(id))->asDevice(device); - (new VoltageSensor{sensor->id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"})->ofDevice((id)); - (new BatterySensor{sensor->id, "Battery level", "{{ ((states('sensor.oil_tank_room_battery_voltage')|float-2.5)|round(2)*100/2)|int }}"})->ofDevice(id); - return sensor; - } -}; - -struct OilTankSensor : Sensor { - OilTankSensor(const char* id) : Sensor("Depth", id) { - deviceClass = "distance"; - unitMeasure = "cm"; - valueTemplate = "{{ value_json.sensor.value }}"; - } -}; - -struct OilTankLevelSensor : Sensor { - OilTankLevelSensor(const char* id) : Sensor("Level", id) { - unitMeasure = "%"; - valueTemplate = "{{ 100 - ((value_json.sensor.value-7)|float*100/110)|round(2) }}"; +namespace OilTank { + Sensor* buildRoomSensor(const char* id) { + auto device = DeviceConfig::create(id) + ->withName("Oil tank room") + ->withManufacturer("Atmel") + ->withModel("AtTiny85") + ->withArea("Basement") + ->withParent(gatewayDevice); + return Builder::instance(id) + .asDevice(device) + .withValueTemplate("{{ value_json.sensor.temperature }}") + .withDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .withDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_room_battery_voltage')|float-2.5)|round(2)*100/2)|int }}"}) + .build(); } - void buildUniqueId(char* uniqueId) override { - sprintf(uniqueId, "level_%s", id); + Sensor* buildTankSensor(const char* id) { + auto device = DeviceConfig::create(id) + ->withName("Oil tank") + ->withManufacturer("Arduino") + ->withModel("Pro Mini") + ->withArea("Basement") + ->withParent(gatewayDevice); + return Builder::instance(new Sensor{ "Depth", id }) + .asDevice(device) + .withDeviceClass("distance") + .withUnitMseasure("cm") + .withValueTemplate("{{ value_json.sensor.value }}") + .withSecondary( + Builder::instance(new Sensor{ "Level", id }) + .withUnitMseasure("%") + .withValueTemplate("{{ 100 - ((value_json.sensor.value-7)|float*100/110)|round(2) }}") + .build() + ) + .withDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .withDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_battery_voltage')|float-3.6)|round(2)*100/1.6)|int }}"}) + .build(); } - - void buildConfigTopic() override { - sprintf(configTopic, "homeassistant/%s/%s/level_%s/config", type, MAIN_DEVICE_ID, id); - } -}; - -struct OilSensorBuilder { - static Sensor* build(const char* id) { - auto device = (new DeviceConfig{*atTinyDevice})->withName("Oil tank")->withArea("Basement"); - device->id = id; - Sensor* sensor = (new OilTankSensor(id))->asDevice(device); - (new OilTankLevelSensor(sensor->id))->ofDevice(id); - (new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"})->ofDevice(id); - (new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_battery_voltage')|float-3.6)|round(2)*100/1.6)|int }}"})->ofDevice(id); - return sensor; - } -}; +} struct PollinSwitch : Switch { const char* group; unsigned char channel; PollinSwitch(const char* name, const char* group, const unsigned char channel, const char* area = nullptr) - : Switch(name, Protocol_1::buildId(group, channel)), group(group), channel(channel) { - asDevice((new DeviceConfig{id})->withName(name)->withManufacturer("Pollin")->withArea(area)->withParent(gatewayDevice)); + : Switch(name, [group, channel]{ + // copy id from string into a new pointer, to avoid memory leaks + string s = Protocol_1::buildId(group, channel); + char* uId = new char[s.length() + 1]; + strcpy(uId, s.c_str()); + return uId; + }()), group(group), channel(channel) { + mainDevice = (new DeviceConfig{id})->withName(name)->withManufacturer("Pollin")->withArea(area)->withParent(gatewayDevice); + deviceClass = "outlet"; p1Switches.insert({ string(id), this }); } void onCommand(const char* msg) override { - (String{ "ON" }.equals(msg)) ? mySwitch.switchOn(group, channel) : mySwitch.switchOff(group, channel); + strcmp("ON", msg) == 0 ? mySwitch.switchOn(group, channel) : mySwitch.switchOff(group, channel); publisher(stateTopic, msg); } @@ -80,7 +80,8 @@ struct EasyHomeSwitch : Switch { : Switch(name, id) { memcpy(&this->on[4], on, 4 * sizeof(unsigned long)); memcpy(&this->off[4], off, 4 * sizeof(unsigned long)); - asDevice((new DeviceConfig{id})->withName(name)->withManufacturer("Intertek")->withModel("Easy Home")->withArea(area)->withParent(gatewayDevice)); + mainDevice = (new DeviceConfig{id})->withName(name)->withManufacturer("Intertek")->withModel("Easy Home")->withArea(area)->withParent(gatewayDevice); + deviceClass = "outlet"; for (int i = 0; i < 8; i++) { onSwitches.insert({ this->on[i], this }); offSwitches.insert({ this->off[i], this }); @@ -89,7 +90,7 @@ struct EasyHomeSwitch : Switch { void onCommand(const char* msg) override { mySwitch.setProtocol(4); - String{ "ON" }.equals(msg) ? mySwitch.send(on[4], 24) : mySwitch.send(off[4], 24); + strcmp("ON", msg) == 0 ? mySwitch.send(on[4], 24) : mySwitch.send(off[4], 24); publisher(stateTopic, msg); } }; diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 61515cc..c8a4f33 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -8,6 +8,7 @@ namespace Ha { uint16_t (*publisher)(const char* topic, const char* message); + typedef void (*onMessage)(const char* msg); struct DeviceConfig { const char* id = nullptr; @@ -17,9 +18,11 @@ namespace Ha { const char* area = nullptr; DeviceConfig* parent = nullptr; - DeviceConfig() {} DeviceConfig(const char* id) : id(id) {} - DeviceConfig(DeviceConfig& d) : id(d.id), name(d.name), model(d.model), manufacturer(d.manufacturer), area(d.area), parent(d.parent) {} + + static DeviceConfig* create(const char* id) { + return new DeviceConfig{ id }; + } void buildConfig(JsonDocument& jsonDoc) { JsonObject device = jsonDoc.createNestedObject("device"); @@ -72,18 +75,15 @@ namespace Ha { components.add(this); } - virtual void buildUniqueId(char* uniqueId) = 0; - + virtual void buildUniqueId(char* uniqueId) { + sprintf(uniqueId, "%s_%s", MAIN_DEVICE_ID, id); + } + virtual void buildConfigTopic() { - if (String{"diagnostic"}.equals(entityCategory) && deviceClass) { - sprintf(configTopic, "homeassistant/%s/%s/%s_%s/config", type, MAIN_DEVICE_ID, deviceClass, id); - } else { - sprintf(configTopic, "homeassistant/%s/%s/%s/config", type, MAIN_DEVICE_ID, id); - } + sprintf(configTopic, "homeassistant/%s/%s/%s/config", type, MAIN_DEVICE_ID, id); } virtual void buildConfig(JsonDocument& jsonDoc) { - buildConfigTopic(); if (mainDevice) mainDevice->buildConfig(jsonDoc); if (entityCategory) jsonDoc["entity_category"] = entityCategory; if (deviceClass) jsonDoc["device_class"] = deviceClass; @@ -91,52 +91,118 @@ namespace Ha { char uniqueId[50]; buildUniqueId(uniqueId); jsonDoc["unique_id"] = uniqueId; + buildConfigTopic(); + } + + void publishConfig() { + StaticJsonDocument jsonDoc; + buildConfig(jsonDoc); + + char message[JSON_SIZE]; + serializeJson(jsonDoc, message); + + publisher(configTopic, message); + } + + void publishCleanupConfig() { + publisher(configTopic, ""); } }; - - template - struct EntityConfig : Component { - - EntityConfig(const char* name, const char* id, const char* type) : Component(name, id, type) { - } - - T* asDevice(DeviceConfig* deviceConfig) { - mainDevice = deviceConfig; - return static_cast(this); - } - - T* ofDevice(const char* id) { - mainDevice = new DeviceConfig{id}; - return static_cast(this); - } - }; - List Component::components; - template - struct Command : EntityConfig { - char commandTopic[TOPIC_SIZE]; + struct AbstractBuilder { + static List builders; - Command(const char* name, const char* id, const char* type) : EntityConfig(name, id, type) { - sprintf(commandTopic, "homeassistant/%s/%s/%s", type, MAIN_DEVICE_ID, id); + AbstractBuilder() { + builders.add(this); } - void buildUniqueId(char* uniqueId) override { - sprintf(uniqueId, "%s_%s", MAIN_DEVICE_ID, this->id); + static void deleteAll() { + List::exec(builders, [](AbstractBuilder* builder) + { delete builder; }); + } + }; + List AbstractBuilder::builders; + + template + struct Builder : AbstractBuilder { + T* cmp; + + Builder(T* cmp) : AbstractBuilder(), cmp(cmp) {} + + Builder(const char* id) : AbstractBuilder() { + cmp = new T{id}; + } + + static Builder& instance(T* c) { + return *(new Builder(c)); + } + + static Builder& instance(const char* id) { + return *(new Builder(id)); + } + + T* build() { + return static_cast(cmp); + } + + Builder& withDeviceClass(const char* value) { + cmp->deviceClass = value; + return *this; + } + + Builder& withUnitMseasure(const char* value) { + cmp->unitMeasure = value; + return *this; + } + + Builder& withValueTemplate(const char* value) { + cmp->valueTemplate = value; + return *this; + } + + Builder& withSecondary(Component* c) { + c->mainDevice = new DeviceConfig{ cmp->id }; + return *this; + } + + Builder& withDiagnostic(Component* c) { + c->entityCategory = "diagnostic"; + c->mainDevice = new DeviceConfig{ cmp->id }; + return *this; + } + + Builder& asDevice(DeviceConfig* deviceConfig) { + cmp->mainDevice = deviceConfig; + return *this; + } + }; + + struct Command : Component { + char commandTopic[TOPIC_SIZE]; + onMessage f; + static unordered_map mapCommands; + + Command(const char* name, const char* id, const char* type, onMessage f) : Component(name, id, type), f(f) { + sprintf(commandTopic, "homeassistant/%s/%s/%s/set", type, MAIN_DEVICE_ID, id); + mapCommands.insert({ string(commandTopic), this }); + } + + virtual void onCommand(const char* msg) { + if (f) f(msg); } void buildConfig(JsonDocument& jsonDoc) override { - EntityConfig::buildConfig(jsonDoc); + Component::buildConfig(jsonDoc); jsonDoc["command_topic"] = commandTopic; } }; + unordered_map Command::mapCommands; - typedef void (*onMessage)(const char* msg); - struct Button : Command