commit e4bf6a1bebe70976a42b9ace03640802bf917cc3 Author: Nicu Hodos Date: Tue Apr 30 08:06:55 2024 +0200 add mqtt with ha components configure Pollin switches diff --git a/gateway/include/ha.h b/gateway/include/ha.h new file mode 100644 index 0000000..4879828 --- /dev/null +++ b/gateway/include/ha.h @@ -0,0 +1,199 @@ +#pragma once + +#include + +#define JSON_SIZE 512 +#define TOPIC_SIZE 255 +#define DEVICE_ID "rc-gateway" + +namespace Ha { + + struct Component { + const char* name; + char* id; + const char* type; + char configTopic[TOPIC_SIZE]; + + Component(const char* name, const char* id, const char* type) : name(name), id((char*)id), type(type) { + sprintf(configTopic, "homeassistant/%s/rc-gateway/%s/config", type, id); + } + + virtual void buildUniqueId(char* uniqueId) = 0; + + virtual void buildConfig(JsonDocument& jsonDoc) { + buildDeviceConfig(jsonDoc); + jsonDoc["name"] = name; + char uniqueId[50]; + buildUniqueId(uniqueId); + jsonDoc["unique_id"] = uniqueId; + } + + virtual void buildDeviceConfig(JsonDocument& jsonDoc) { + JsonObject device = jsonDoc.createNestedObject("device"); + device["name"] = "RC Gateway"; + device["model"] = "Huzzah Esp8266"; + device["manufacturer"] = "Adafruit"; + JsonArray connections = device.createNestedArray("connections"); + JsonArray mac = connections.createNestedArray(); + mac.add("mac"); + mac.add(WiFi.macAddress()); + JsonArray identifiers = device.createNestedArray("identifiers"); + identifiers.add(DEVICE_ID); + } + }; + + struct Command : Component { + char commandTopic[TOPIC_SIZE]; + + Command(const char* name, const char* id, const char* type) : Component(name, id, type) { + } + + void buildUniqueId(char* uniqueId) override { + sprintf(uniqueId, "rc_gateway_%s", id); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Component::buildConfig(jsonDoc); + jsonDoc["command_topic"] = commandTopic; + } + + }; + + typedef void (*onMessage)(const char* msg); + struct Button : Command { + static constexpr const char* type = "button"; + onMessage f; + + Button(const char* name, const char* id, onMessage f) : Command(name, id, type), f(f) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s", type, id); + } + + }; + + struct Switch : Command { + static constexpr const char* type = "switch"; + char stateTopic[TOPIC_SIZE]; + const char* area; + uint16_t (*publisher)(const char* topic, const char* message); + virtual void onCommand(const char* msg){} + + Switch(const char* name, const char* id, const char* area, uint16_t (*publisher)(const char* topic, const char* message) = nullptr) + : Command(name, id, type), area(area), publisher(publisher) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); + } + + void buildDeviceConfig(JsonDocument& jsonDoc) override { + JsonObject device = jsonDoc.createNestedObject("device"); + device["name"] = name; + device["via_device"] = DEVICE_ID; + device["suggested_area"] = area; + JsonArray identifiers = device.createNestedArray("identifiers"); + identifiers.add(id); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Command::buildConfig(jsonDoc); + jsonDoc["name"] = nullptr; + jsonDoc["device_class"] = "outlet"; + jsonDoc["retain"] = true; + if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; + } + + Switch* withStateTopic() { + sprintf(stateTopic, "homeassistant/%s/rc-gateway/%s/state", type, id); + return this; + } + + void publishState(bool state) { + publisher(stateTopic, state ? "ON" : "OFF"); + } + + }; + + struct PollinSwitch : Switch { + const char* group; + unsigned char channel; + + PollinSwitch(const char* name, const char* area, const char* group, const unsigned char channel, uint16_t (*publisher)(const char* topic, const char* message)) + : Switch(name, Protocol_1::buildId(group, channel), area, publisher), group(group), channel(channel) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); + } + + void onCommand(const char* msg) override { + (String{ "ON" }.equals(msg)) ? mySwitch.switchOn(group, channel) : mySwitch.switchOff(group, channel); + publisher(stateTopic, msg); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Switch::buildConfig(jsonDoc); + JsonObject device = jsonDoc["device"]; + device["manufacturer"] = "Pollin"; + } + + }; + + struct Sensor : Component { + const char* deviceClass; + const char* unitMeasure; + const char* valueTemplate; + static constexpr const char* stateTopic = "homeassistant/sensor/rc-gateway/state"; + + Sensor(const char* name, const char* id) : Component(name, id, "sensor") { + } + + void buildUniqueId(char* uniqueId) override { + sprintf(uniqueId, "living_room_%s", id); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Component::buildConfig(jsonDoc); + jsonDoc["device_class"] = deviceClass; + jsonDoc["unit_of_measurement"] = unitMeasure; + jsonDoc["value_template"] = valueTemplate; + jsonDoc["state_topic"] = stateTopic; + } + + void buildDeviceConfig(JsonDocument& jsonDoc) override { + JsonObject device = jsonDoc.createNestedObject("device"); + device["name"] = "Living room"; + device["model"] = "BPM280"; + device["suggested_area"] = "Living room"; + device["via_device"] = "rc-gateway"; + JsonArray identifiers = device.createNestedArray("identifiers"); + identifiers.add("esp-clock-living-room"); + } + + }; + + struct TemperatureSensor : Sensor { + TemperatureSensor(const char* name, const char* id) : Sensor(name, id) { + deviceClass = "temperature"; + unitMeasure = "°C"; + valueTemplate = "{{ value_json.temperature }}"; + } + }; + + struct HumiditySensor : Sensor { + HumiditySensor(const char* name, const char* id) : Sensor(name, id) { + deviceClass = "humidity"; + unitMeasure = "%"; + valueTemplate = "{{ value_json.humidity }}"; + } + }; + + struct PressureSensor : Sensor { + PressureSensor(const char* name, const char* id) : Sensor(name, id) { + deviceClass = "pressure"; + unitMeasure = "hPa"; + valueTemplate = "{{ value_json.pressure }}"; + } + }; + + struct AltitudeSensor : Sensor { + AltitudeSensor(const char* name, const char* id) : Sensor(name, id) { + deviceClass = "distance"; + unitMeasure = "m"; + valueTemplate = "{{ value_json.altitude }}"; + } + }; +} diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h new file mode 100644 index 0000000..40a3813 --- /dev/null +++ b/gateway/include/mqtt.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include "ha.h" + + +#define MQTT_HOST IPAddress(192, 168, 5, 11) +#define MQTT_PORT 1883 + +namespace Mqtt { + + AsyncMqttClient client; + + void publishInit(); + void publishBme280(); + void disconnect(); + Task tReConnect(5 * TASK_MINUTE, TASK_FOREVER, []() { + Serial.println("Connecting to MQTT..."); + client.connect(); + }, &ts); + Task tPublishInit(TASK_IMMEDIATE, TASK_ONCE, publishInit, &ts); + + const char* mainTopic = "homeassistant/+/rc-gateway/#"; + + void disconnect() { + client.unsubscribe(mainTopic); + client.disconnect(); + } + + uint16_t publish(const char* topic, const char* message) { + return client.publish(topic, 0, true, message); + } + + Ha::Sensor* sensors[] = { + // new Ha::TemperatureSensor{"Temperature", "temperature"}, + // new Ha::HumiditySensor{"Humidity", "humidity"}, + // new Ha::PressureSensor{"Pressure", "pressure"}, + // new Ha::AltitudeSensor{"Altitude", "altitude"} + }; + + Ha::Button* buttons[] = { + new Ha::Button{"Restart", "restart", + [](const char* msg) { + if (String { "PRESS" }.equals(msg)) ESP.restart(); + } + } + }; + + Ha::Switch* switches[] = { + (new Ha::PollinSwitch{"Meeting sensor", "Dining room", "00001", 1, publish})->withStateTopic(), + (new Ha::PollinSwitch{"Fire Tv", "Living room", "00001", 2, publish})->withStateTopic(), + (new Ha::PollinSwitch{"Diningroom player", "Dining room", "00001", 3, publish})->withStateTopic(), + (new Ha::PollinSwitch{"Train", "Playroom", "11111", 4, publish})->withStateTopic() + }; + + unordered_map mapSwitches; + + void publishComponentConfig(Ha::Component& component) { + StaticJsonDocument jsonDoc; + component.buildConfig(jsonDoc); + + char message[JSON_SIZE]; + serializeJson(jsonDoc, message); + + publish(component.configTopic, message); + } + + void publishInit() { + // for (Ha::Component* cmp : sensors) { + // publishComponentConfig(*cmp); + // } + for (Ha::Component* cmp : buttons) { + publishComponentConfig(*cmp); + } + for (Ha::Switch* cmp : switches) { + mapSwitches.insert({string(cmp->id), cmp}); + publishComponentConfig(*cmp); + } + ts.deleteTask(tPublishInit); + } + + void publishBme280() { + // StaticJsonDocument<255> jsonDoc; + // jsonDoc["temperature"] = Bme::data.temp; + // jsonDoc["humidity"] = Bme::data.humidity; + // jsonDoc["pressure"] = Bme::data.pressure; + // jsonDoc["altitude"] = Bme::data.altitude; + // char message[255]; + // serializeJson(jsonDoc, message); + // publish(Ha::Sensor::stateTopic, message); + } + + void onMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + char msg[len + 1]; + memcpy(msg, payload, len); + msg[len] = 0; + for (Ha::Button* cmd : buttons) { + if (String{ cmd->commandTopic }.equals(topic) && cmd->f != nullptr) { + cmd->f(msg); + return; + } + } + for (Ha::Switch* cmd : switches) { + if (String{ cmd->commandTopic }.equals(topic)) { + cmd->onCommand(msg); + return; + } + } + } + + void setup() { + client.onConnect([](bool sessionPresent) { + tPublishInit.enable(); + client.subscribe(mainTopic, 0); + tReConnect.disable(); + Serial.println("Connected to MQTT"); + }); + client.onDisconnect([](AsyncMqttClientDisconnectReason reason) { + tReConnect.enableDelayed(); + Serial.println("Disconnected from MQTT"); + }); + client.onMessage(onMessage); + client.setServer(MQTT_HOST, MQTT_PORT); + } +}