diff --git a/README.md b/README.md new file mode 100644 index 0000000..810d518 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Gateway & sensors + +## Branches +Each sensor has a dedicated branch. E.g.: +* temp_sensor +* oil_sensor + +The gateway uses `master` as the main branch. Other sensors' branches get merged once they are ready for production. + +## Release flow diff --git a/gateway/include/devices.h b/gateway/include/devices.h index bc90c58..589f7fb 100644 --- a/gateway/include/devices.h +++ b/gateway/include/devices.h @@ -25,8 +25,8 @@ namespace OilTank { return Builder::instance(id) .asDevice(device) .withValueTemplate("{{ value_json.sensor.temperature }}") - .asDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) - .asDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.servers_room_battery_voltage')|float-2.5)|round(2)*100/2)|int }}"}) + .addDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .addDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.servers_room_battery_voltage')|float-2.5)|round(2)*100/2)|int }}"}) .build(); } @@ -42,14 +42,14 @@ namespace OilTank { .withDeviceClass("distance") .withUnitMseasure("cm") .withValueTemplate("{{ value_json.sensor.value }}") - .asSecondary( + .addSecondary( Builder::instance(new Sensor{ "Level", id }) .withUnitMseasure("%") .withValueTemplate("{{ 100 - ((value_json.sensor.value-7)|float*100/110)|round(2) }}") .build() ) - .asDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) - .asDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_battery_voltage')|float-3.6)|round(2)*100/1.6)|int }}"}) + .addDiagnostic(new VoltageSensor{id, "Battery voltage", "{{ value_json.sensor.diagnostic.voltage }}"}) + .addDiagnostic(new BatterySensor{id, "Battery level", "{{ ((states('sensor.oil_tank_battery_voltage')|float-3.6)|round(2)*100/1.6)|int }}"}) .build(); } } @@ -59,7 +59,7 @@ struct PollinSwitch : Switch { unsigned char channel; PollinSwitch(const char* name, const char* group, const unsigned char channel, const char* area = nullptr) - : Switch(name, [group, channel]{ + : Switch(nullptr, [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]; @@ -67,6 +67,7 @@ struct PollinSwitch : Switch { return uId; }()), group(group), channel(channel) { mainDevice = &DeviceConfig::create(id).withName(name).withManufacturer("Pollin").withArea(area).withParent(gatewayDevice); + withStateTopic(); deviceClass = "outlet"; p1Switches.insert({ string(id), this }); } @@ -83,10 +84,11 @@ struct EasyHomeSwitch : Switch { unsigned long off[8] = { 4483146, 4626810, 4661562, 4819642 }; EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4], const char* area = nullptr) - : Switch(name, id) { + : Switch(nullptr, id) { memcpy(&this->on[4], on, 4 * sizeof(unsigned long)); memcpy(&this->off[4], off, 4 * sizeof(unsigned long)); mainDevice = &DeviceConfig::create(id).withName(name).withManufacturer("Intertek").withModel("Easy Home").withArea(area).withParent(gatewayDevice); + withStateTopic(); deviceClass = "outlet"; for (int i = 0; i < 8; i++) { onSwitches.insert({ this->on[i], this }); @@ -120,12 +122,12 @@ Command* commands[] = { .withParent(gatewayDevice) ) .build(), - (new EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }, "Basement"})->withStateTopic(), - (new EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, "Basement"})->withStateTopic(), - (new PollinSwitch{"Meeting sensor", "00001", 1, "Dining room"})->withStateTopic(), - (new PollinSwitch{"Fire Tv", "00001", 2, "Living room"})->withStateTopic(), - (new PollinSwitch{"Diningroom player", "00001", 3, "Dining room"})->withStateTopic(), - (new PollinSwitch{"Train", "11111", 4, "Playroom"})->withStateTopic() + new EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }, "Basement"}, + new EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, "Basement"}, + new PollinSwitch{"Meeting sensor", "00001", 1, "Dining room"}, + new PollinSwitch{"Fire Tv", "00001", 2, "Living room"}, + new PollinSwitch{"Diningroom player", "00001", 3, "Dining room"}, + new PollinSwitch{"Train", "11111", 4, "Playroom"} }; Sensor* sensors[] = { diff --git a/gateway/include/ha.h b/gateway/include/ha.h deleted file mode 100644 index 6a56af9..0000000 --- a/gateway/include/ha.h +++ /dev/null @@ -1,314 +0,0 @@ -#pragma once - -#include -#include "utils.h" - -#define JSON_SIZE 512 -#define TOPIC_SIZE 255 - -namespace Ha { - uint16_t(*publisher)(const char* topic, const char* message); - typedef void (*onMessage)(const char* msg); - - struct DeviceConfig { - const char* id = nullptr; - const char* name = nullptr; - const char* model = nullptr; - const char* manufacturer = nullptr; - const char* area = nullptr; - const DeviceConfig* parent = nullptr; - - static DeviceConfig& create(const char* id) { - return *(new DeviceConfig{ id }); - } - - void buildConfig(JsonDocument& jsonDoc) { - JsonObject device = jsonDoc.createNestedObject("device"); - if (name) device["name"] = name; - if (model) device["model"] = model; - if (manufacturer) device["manufacturer"] = manufacturer; - if (area) device["suggested_area"] = area; - if (parent) device["via_device"] = parent->id; - JsonArray identifiers = device.createNestedArray("identifiers"); - identifiers.add(id); - } - - DeviceConfig& withName(const char* value) { - name = value; - return *this; - } - - DeviceConfig& withModel(const char* value) { - model = value; - return *this; - } - - DeviceConfig& withManufacturer(const char* value) { - manufacturer = value; - return *this; - } - - DeviceConfig& withArea(const char* value) { - area = value; - return *this; - } - - DeviceConfig& withParent(const DeviceConfig* deviceConfig) { - parent = deviceConfig; - return *this; - } - - protected: - DeviceConfig(const char* id) : id(id) {} - }; - - struct Component { - const char* entityCategory = nullptr; - const char* deviceClass = nullptr; - const char* name = nullptr; - char* id = nullptr; - const char* type = nullptr; - char configTopic[TOPIC_SIZE]; - DeviceConfig* mainDevice = nullptr; - static List components; - - Component(const char* name, const char* id, const char* type) : name(name), id((char*)id), type(type) { - components.add(this); - } - - virtual void buildUniqueId(char* uniqueId) { - sprintf(uniqueId, "%s_%s", MAIN_DEVICE_ID, id); - } - - virtual void buildConfigTopic() { - sprintf(configTopic, "homeassistant/%s/%s/%s/config", type, MAIN_DEVICE_ID, id); - } - - virtual void buildConfig(JsonDocument& jsonDoc) { - if (mainDevice) mainDevice->buildConfig(jsonDoc); - if (entityCategory) jsonDoc["entity_category"] = entityCategory; - if (deviceClass) jsonDoc["device_class"] = deviceClass; - jsonDoc["name"] = name; - 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, ""); - } - }; - - struct AbstractBuilder { - static List builders; - - AbstractBuilder() { - builders.add(this); - } - - static void deleteAll() { - builders.forEach([](AbstractBuilder* builder) { delete builder; }); - builders.empty(); - } - }; - - 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& asSecondary(Component* c) { - c->mainDevice = &DeviceConfig::create(cmp->id); - return *this; - } - - Builder& asDiagnostic(Component* c) { - c->entityCategory = "diagnostic"; - return asSecondary(c); - } - - 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 { - Component::buildConfig(jsonDoc); - jsonDoc["command_topic"] = commandTopic; - } - - }; - - struct Button : Command { - - Button(const char* name, const char* id, onMessage f = nullptr) : Command(name, id, "button", f) {} - - }; - - template - struct StateConfig { - char stateTopic[TOPIC_SIZE]; - - T* withStateTopic() { - sprintf(stateTopic, "homeassistant/%s/%s/%s/state", ((T*)this)->type, MAIN_DEVICE_ID, ((T*)this)->id); - return static_cast(this); - } - }; - - struct Switch : Command, StateConfig { - - Switch(const char* name, const char* id, onMessage f = nullptr) : Command(name, id, "switch", f) {} - - void buildConfig(JsonDocument& jsonDoc) override { - Command::buildConfig(jsonDoc); - jsonDoc["name"] = nullptr; - // jsonDoc["retain"] = true; - if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; - } - - void updateState(bool state) { - publisher(stateTopic, state ? "ON" : "OFF"); - } - }; - - struct Sensor : Component, StateConfig { - const char* unitMeasure = nullptr; - const char* valueTemplate = nullptr; - static unordered_map mapSensors; - - Sensor(const char* name, const char* id) : Component(name, id, "sensor") { - withStateTopic(); - mapSensors.insert({ id, this }); - } - - void buildUniqueId(char* uniqueId) override { - if (deviceClass) { - sprintf(uniqueId, "%s_%s_%s", MAIN_DEVICE_ID, deviceClass, id); - } else { - Component::buildUniqueId(uniqueId); - } - } - - void buildConfigTopic() override { - if (deviceClass) { - sprintf(configTopic, "homeassistant/%s/%s/%s_%s/config", type, MAIN_DEVICE_ID, deviceClass, id); - } else { - Component::buildConfigTopic(); - } - } - - void buildConfig(JsonDocument& jsonDoc) override { - Component::buildConfig(jsonDoc); - if (unitMeasure) jsonDoc["unit_of_measurement"] = unitMeasure; - if (valueTemplate) jsonDoc["value_template"] = valueTemplate; - jsonDoc["state_topic"] = stateTopic; - jsonDoc["suggested_display_precision"] = 2; - } - - void updateState(const char* message) { - publisher(stateTopic, message); - } - }; - - struct TemperatureSensor : Sensor { - TemperatureSensor(const char* id, const char* name = "Temperature") : Sensor(name, id) { - deviceClass = "temperature"; - unitMeasure = "°C"; - } - }; - - struct VoltageSensor : Sensor { - VoltageSensor(const char* id, const char* name, const char* valueTemplate) : Sensor(name, id) { - this->valueTemplate = valueTemplate; - deviceClass = "voltage"; - unitMeasure = "V"; - } - }; - - struct BatterySensor : Sensor { - BatterySensor(const char* id, const char* name, const char* valueTemplate) : Sensor(name, id) { - this->valueTemplate = valueTemplate; - deviceClass = "battery"; - unitMeasure = "%"; - } - }; - - struct HumiditySensor : Sensor { - HumiditySensor(const char* id, const char* name = "Humidity") : Sensor(name, id) { - deviceClass = "humidity"; - unitMeasure = "%"; - // valueTemplate = "{{ value_json.sensor.humidity }}"; - } - }; - - struct PressureSensor : Sensor { - PressureSensor(const char* id, const char* name = "Pressure") : Sensor(name, id) { - deviceClass = "pressure"; - unitMeasure = "hPa"; - // valueTemplate = "{{ value_json.sensor.pressure }}"; - } - }; - - List Component::components; - List AbstractBuilder::builders; - unordered_map Command::mapCommands; - unordered_map Sensor::mapSensors; -} diff --git a/gateway/include/huzzah.h b/gateway/include/huzzah.h index 69c82aa..5f9c3d9 100644 --- a/gateway/include/huzzah.h +++ b/gateway/include/huzzah.h @@ -1,13 +1,15 @@ #include +#define MQTT_HOST IPAddress(192, 168, 5, 11) +#define MQTT_PORT 1883 + using namespace std; Scheduler ts; -void turnLed(uint8_t led, bool on = true) { - on ? digitalWrite(led, LOW) : digitalWrite(led, HIGH); -} - +#include "devices.h" +#include "mqtt.h" +#include "ota.h" #include "wifi.h" namespace Board { @@ -19,6 +21,10 @@ namespace Board { } }, &ts); + void turnLed(uint8_t led, bool on = true) { + on ? digitalWrite(led, LOW) : digitalWrite(led, HIGH); + } + void setup() { // Serial.begin(9600, SERIAL_8N1, SERIAL_TX_ONLY); @@ -27,9 +33,12 @@ namespace Board { turnLed(RED_LED, false); turnLed(BLUE_LED); + Mqtt::setup(&ts, + [] {turnLed(BLUE_LED, false);}, + [] {turnLed(BLUE_LED);} + ); Wifi::setup(); Ota::setup(); - Mqtt::setup(); tReadCommand.enable(); } diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h deleted file mode 100644 index 23ee529..0000000 --- a/gateway/include/mqtt.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include -#include -#include "devices.h" - - -#define MQTT_HOST IPAddress(192, 168, 5, 11) -#define MQTT_PORT 1883 - -namespace Mqtt { - - AsyncMqttClient client; - - Task tReConnect(5 * TASK_MINUTE, TASK_FOREVER, []{ - Serial.println("Connecting to MQTT..."); - client.connect(); - }, &ts); - void publishInit(); - 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); - } - - void publishInit() { - Ha::publisher = publish; - Component::components.forEach([](Component* c) { c->publishConfig(); }); - AbstractBuilder::deleteAll(); - ts.deleteTask(tPublishInit); - } - - void publishCleanupConfig() { - Component::components.forEach([](Component* c) { c->publishCleanupConfig(); }); - } - - 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; - Command* cmd = Command::mapCommands[string{ topic }]; - if (cmd) cmd->onCommand(msg); - } - - void setup() { - client.onConnect([](bool sessionPresent) { - tPublishInit.enable(); - client.subscribe(mainTopic, 0); - tReConnect.disable(); - Serial.println("Connected to MQTT"); - turnLed(BLUE_LED, false); - }); - client.onDisconnect([](AsyncMqttClientDisconnectReason reason) { - tReConnect.enableDelayed(); - Serial.println("Disconnected from MQTT"); - turnLed(BLUE_LED); - }); - client.onMessage(onMessage); - client.setServer(MQTT_HOST, MQTT_PORT); - } -} diff --git a/gateway/include/utils.h b/gateway/include/utils.h deleted file mode 100644 index 4f63959..0000000 --- a/gateway/include/utils.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -template -struct List { - struct Container { - T* t; - Container* next = nullptr; - Container(T* t) : t(t) {} - }; - - Container* first = nullptr; - Container* last = nullptr; - - void add(T* t) { - Container* c = new Container{t}; - first == nullptr ? first = c : last->next = c; - last = c; - } - - void forEach(void(*f)(T*)) { - for (Container *c = first; c; c = c->next) { - f(c->t); - } - } - - void empty() { - Container *c = first; - while (c) { - auto n = c->next; - delete c; - c = n; - } - } - -}; diff --git a/gateway/include/wifi.h b/gateway/include/wifi.h index 2f0c877..d09a1cf 100644 --- a/gateway/include/wifi.h +++ b/gateway/include/wifi.h @@ -1,8 +1,6 @@ #include #include #include -#include "mqtt.h" -#include "ota.h" #include "credentials.h" namespace Wifi { @@ -30,12 +28,11 @@ namespace Wifi { printStatus(); tReconnect.cancel(); Ota::tLoop.enable(); - Mqtt::tReConnect.restart(); + Mqtt::tReConnect.enable(); }); stationDisconnectedHandler = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected& e) { Serial.println("Disconnected from network."); - tReconnect.restartDelayed(); }); diff --git a/gateway/platformio.ini b/gateway/platformio.ini index 8f659c4..de2bedf 100644 --- a/gateway/platformio.ini +++ b/gateway/platformio.ini @@ -9,12 +9,9 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = pro-mini +default_envs = huzzah -[env:huzzah] -platform = espressif8266 -board = huzzah -framework = arduino +[env] lib_extra_dirs = ../libraries lib_deps = @@ -22,65 +19,37 @@ lib_deps = bblanchon/ArduinoJson@6.21.5 adafruit/Adafruit Unified Sensor@^1.1.4 adafruit/DHT sensor library@1.3.2 - arkhipenko/TaskScheduler@^3.7.0 - marvinroger/AsyncMqttClient@^0.9.0 https://git.hodos.ro/arduino/lib_serial-reader.git@^1.0.0 build_flags = -D DHT_SENSOR=0 -D DEBUG_RAW=0 check_tool = cppcheck check_flags = --enable=all check_skip_packages = yes check_severity = medium, high + +[env:huzzah] +platform = espressif8266 +board = huzzah +framework = arduino +lib_deps = + ${env.lib_deps} + https://git.hodos.ro/arduino/ha-mqtt.git@^1.0.0 upload_port = 192.168.6.161 upload_protocol = espota upload_flags = --host_port=10000 +[env:huzzah_dev] +extends = env:huzzah +build_flags = ${env.build_flags} -D MQTT_CLEANUP=1 + [env:pro-mini] platform = atmelavr board = pro16MHzatmega328 framework = arduino -lib_extra_dirs = - ../libraries -lib_deps = - sui77/rc-switch@^2.6.3 - bblanchon/ArduinoJson@6.21.5 - adafruit/Adafruit Unified Sensor@^1.1.4 - adafruit/DHT sensor library@1.3.2 - https://git.hodos.ro/arduino/lib_serial-reader.git@^1.0.0 -build_flags = -D DHT_SENSOR=0 -D DEBUG_RAW=0 -upload_port = /dev/ttyUSB0 -check_tool = cppcheck -check_flags = --enable=all -check_skip_packages = yes -check_severity = medium, high [env:native] platform = native test_filter = native/* -lib_extra_dirs = - ../libraries lib_deps = - bblanchon/ArduinoJson@6.19.4 -build_flags = -std=c++11 - -[env:embedded] -platform = atmelavr -framework = arduino -board = pro16MHzatmega328 -lib_extra_dirs = - ../libraries -lib_deps = - sui77/rc-switch@^2.6.3 - bblanchon/ArduinoJson@6.19.4 - -platform_packages = - platformio/tool-simavr -test_speed = 9600 -test_testing_command = - ${platformio.packages_dir}/tool-simavr/bin/simavr - -m - atmega328p - -f - 16000000L - ${platformio.build_dir}/${this.__env__}/firmware.elf -test_filter = embedded/* + bblanchon/ArduinoJson@6.21.5 +debug_build_flags = -std=c++11 diff --git a/gateway/test/embedded/test_dummy/dummy.cpp b/gateway/test/embedded/test_dummy/dummy.cpp deleted file mode 100644 index 352dc1e..0000000 --- a/gateway/test/embedded/test_dummy/dummy.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -void setUp(void) { - // set stuff up here -} - -void tearDown(void) { - // clean stuff up here -} - -void setup() { - UNITY_BEGIN(); - UNITY_END(); -} - -void loop() { -}