diff --git a/examples/availability/README.md b/examples/availability/README.md new file mode 100644 index 0000000..f7542ea --- /dev/null +++ b/examples/availability/README.md @@ -0,0 +1,31 @@ +# Home Assistant availability example + +This example shows how to use "availability" feature of the Home Assistant. +It's supported only by following device types: +* Binary sensor +* Sensor +* Switch + +## Initialization and usage + +Availability feature is turned off by default. In order to turn it on for the specific +sensor/switch you need to set initial state of the unit by calling `setAvailability` method. + +Example: +```cpp +... +HABinarySensor sensor("input", "door", true, mqtt); + +void setup() { + // ... + + // method must be called before `mqtt.begin(...)` + sensor.setAvailability(false); // offline + + mqtt.begin(BROKER_ADDR); +} +... +``` + +After availability is set for the very first time you can call `setAvailability` in any time. +Each time the method is called, the device will publish MQTT message to the broker. diff --git a/examples/availability/availability.ino b/examples/availability/availability.ino new file mode 100644 index 0000000..2bcc061 --- /dev/null +++ b/examples/availability/availability.ino @@ -0,0 +1,56 @@ +#include +#include + +#define INPUT_PIN 9 +#define BROKER_ADDR IPAddress(192,168,0,17) + +byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A}; +unsigned long lastReadAt = millis(); +unsigned long lastAvailabilityToggleAt = millis(); +bool lastInputState = false; + +EthernetClient client; +HADevice device(mac, sizeof(mac)); +HAMqtt mqtt(client, device); + +// "input" may be anything you want to be displayed in HA panel +// "door" is device class (based on the class HA displays different icons in the panel) +// "true" is initial state of the sensor. In this example it's "true" as we use pullup resistor +HABinarySensor sensor("input", "door", true, mqtt); + +void setup() { + pinMode(INPUT_PIN, INPUT_PULLUP); + lastInputState = digitalRead(INPUT_PIN); + + // you don't need to verify return status + Ethernet.begin(mac); + + // turn on "availability" feature + sensor.setAvailability(false); + + lastReadAt = millis(); + lastAvailabilityToggleAt = millis(); + + // set device's details (optional) + device.setName("Arduino"); + device.setSoftwareVersion("1.0.0"); + + mqtt.begin(BROKER_ADDR); +} + +void loop() { + Ethernet.maintain(); + mqtt.loop(); + + if ((millis() - lastReadAt) > 30) { // read in 30ms interval + // library produces MQTT message if a new state is different than the previous one + sensor.setState(digitalRead(INPUT_PIN)); + lastInputState = sensor.getState(); + lastReadAt = millis(); + } + + if ((millis() - lastAvailabilityToggleAt) > 5000) { + sensor.setAvailability(!sensor.isOnline()); + lastAvailabilityToggleAt = millis(); + } +} diff --git a/examples/binary-sensor/binary-sensor.ino b/examples/binary-sensor/binary-sensor.ino index 2e8f668..967b883 100644 --- a/examples/binary-sensor/binary-sensor.ino +++ b/examples/binary-sensor/binary-sensor.ino @@ -14,7 +14,8 @@ HAMqtt mqtt(client, device); // "input" may be anything you want to be displayed in HA panel // "door" is device class (based on the class HA displays different icons in the panel) -HABinarySensor sensor("input", "door", false, mqtt); +// "true" is initial state of the sensor. In this example it's "true" as we use pullup resistor +HABinarySensor sensor("input", "door", true, mqtt); void setup() { pinMode(INPUT_PIN, INPUT_PULLUP); diff --git a/examples/nodemcu/nodemcu.ino b/examples/nodemcu/nodemcu.ino index 16528b3..8732886 100644 --- a/examples/nodemcu/nodemcu.ino +++ b/examples/nodemcu/nodemcu.ino @@ -6,9 +6,8 @@ #define WIFI_SSID "MyNetwork" #define WIFI_PASSWORD "MyPassword" -byte mac[6]; WiFiClient client; -HADevice device(mac, sizeof(mac)); +HADevice device; HAMqtt mqtt(client, device); HASwitch led("led", false, mqtt); // you can use custom name in place of "led" @@ -21,7 +20,11 @@ void setup() { Serial.begin(9600); Serial.println("Starting..."); + // Unique ID must be set! + byte mac[WL_MAC_ADDR_LENGTH]; WiFi.macAddress(mac); + device.setUniqueId(mac, sizeof(mac)); + pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); diff --git a/examples/sensor/sensor.ino b/examples/sensor/sensor.ino index 84abe7a..421c055 100644 --- a/examples/sensor/sensor.ino +++ b/examples/sensor/sensor.ino @@ -22,7 +22,9 @@ HAMqtt mqtt(client, device); * - double * - float */ -HASensor temp("temp", 0, mqtt); // you can use custom name in place of "temp" +// you can use custom name in place of "temp" +// "0" is initial value of the sensor +HASensor temp("temp", 0, mqtt); void setup() { // you don't need to verify return status diff --git a/library.properties b/library.properties index fbaeed9..aa8dbde 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=home-assistant-integration -version=1.0.1 +version=1.1.0 author=Dawid Chyrzynski maintainer=Dawid Chyrzynski sentence=Home Assistant MQTT integration for Arduino diff --git a/src/HADevice.cpp b/src/HADevice.cpp index 263d515..c22d9b2 100644 --- a/src/HADevice.cpp +++ b/src/HADevice.cpp @@ -9,6 +9,13 @@ _name(nullptr), \ _softwareVersion(nullptr) +HADevice::HADevice() : + _uniqueId(nullptr), + HADEVICE_INIT +{ + +} + HADevice::HADevice(const char* uniqueId) : _uniqueId(uniqueId), HADEVICE_INIT @@ -23,6 +30,16 @@ HADevice::HADevice(const byte* uniqueId, const uint16_t& length) : } +bool HADevice::setUniqueId(const byte* uniqueId, const uint16_t& length) +{ + if (_uniqueId != nullptr) { + return false; + } + + _uniqueId = HAUtils::byteArrayToStr(uniqueId, length); + return true; +} + uint16_t HADevice::calculateSerializedLength() const { uint16_t size = diff --git a/src/HADevice.h b/src/HADevice.h index bcb236d..06e738b 100644 --- a/src/HADevice.h +++ b/src/HADevice.h @@ -6,6 +6,7 @@ class HADevice { public: + HADevice(); HADevice(const char* uniqueId); HADevice(const byte* uniqueId, const uint16_t& length); @@ -24,6 +25,7 @@ public: inline void setSoftwareVersion(const char* softwareVersion) { _softwareVersion = softwareVersion; } + bool setUniqueId(const byte* uniqueId, const uint16_t& length); uint16_t calculateSerializedLength() const; uint16_t serialize(char* dst) const; diff --git a/src/HAMqtt.cpp b/src/HAMqtt.cpp index 5e2031d..9c9857c 100644 --- a/src/HAMqtt.cpp +++ b/src/HAMqtt.cpp @@ -34,14 +34,12 @@ void onMessageReceived(char* topic, uint8_t* payload, uint16_t length) } HAMqtt::HAMqtt(Client& netClient, HADevice& device) : - _clientId(device.getUniqueId()), HAMQTT_INIT { instance = this; } HAMqtt::HAMqtt(const char* clientId, Client& netClient, HADevice& device) : - _clientId(clientId), HAMQTT_INIT { instance = this; @@ -63,6 +61,14 @@ bool HAMqtt::begin( Serial.println(); #endif + if (_device.getUniqueId() == nullptr) { +#if defined(ARDUINOHA_DEBUG) + Serial.println(F("Failed to initialize ArduinoHA. Missing device's unique ID.")); +#endif + + return false; + } + if (_initialized) { #if defined(ARDUINOHA_DEBUG) Serial.println(F("ArduinoHA is already initialized")); @@ -213,14 +219,14 @@ void HAMqtt::connectToServer() #if defined(ARDUINOHA_DEBUG) Serial.print(F("Connecting to the MQTT broker... Client ID: ")); - Serial.print(_clientId); + Serial.print(_device.getUniqueId()); Serial.println(); #endif if (_username == nullptr || _password == nullptr) { - _mqtt->connect(_clientId); + _mqtt->connect(_device.getUniqueId()); } else { - _mqtt->connect(_clientId, _username, _password); + _mqtt->connect(_device.getUniqueId(), _username, _password); } if (isConnected()) { diff --git a/src/HAMqtt.h b/src/HAMqtt.h index 90bdeae..5225ddf 100644 --- a/src/HAMqtt.h +++ b/src/HAMqtt.h @@ -54,6 +54,16 @@ public: const char* password = nullptr ); + /** + * Sets parameters of the connection to the MQTT broker on default port. + * The library will try to connect to the broker in first loop cycle. + * Please note that the library automatically reconnects to the broker if connection is lost. + * + * @param serverIp IP address of the MQTT broker. + * @param serverPort Port of the MQTT broker. + * @param username Username for authentication. + * @param password Password for authentication. + */ bool begin( const IPAddress& serverIp, const char* username, @@ -128,7 +138,6 @@ private: */ void onConnected(); - const char* _clientId; Client& _netClient; HADevice& _device; bool _hasDevice; diff --git a/src/device-types/BaseDeviceType.cpp b/src/device-types/BaseDeviceType.cpp index 5aadb1f..15aad6c 100644 --- a/src/device-types/BaseDeviceType.cpp +++ b/src/device-types/BaseDeviceType.cpp @@ -2,11 +2,15 @@ #include "../HAMqtt.h" #include "../HADevice.h" -const char* BaseDeviceType::ConfigTopic = "config"; // todo: move to progmem -const char* BaseDeviceType::EventTopic = "event"; - -BaseDeviceType::BaseDeviceType(HAMqtt& mqtt) : - _mqtt(mqtt) +BaseDeviceType::BaseDeviceType( + HAMqtt& mqtt, + const char* componentName, + const char* name +) : + _mqtt(mqtt), + _componentName(componentName), + _name(name), + _availability(AvailabilityDefault) { _mqtt.addDeviceType(this); } @@ -16,63 +20,51 @@ BaseDeviceType::~BaseDeviceType() } -uint16_t BaseDeviceType::calculateTopicLength( - const char* component, - const char* objectId, - const char* suffix, - bool includeNullTerminator -) const +void BaseDeviceType::setAvailability(bool online) { - // [discovery prefix]/[namespace]/[device id - optional]/subtype_type/[suffix] - const char* prefix = _mqtt.getDiscoveryPrefix(); - if (prefix == nullptr) { - return 0; - } - - uint16_t size = - strlen(prefix) + 1 + // with slash - strlen(component) + 1 + // with slash - strlen(suffix); // with null terminator - - if (objectId != nullptr) { - size += strlen(objectId) + 1; // with slash - } else { - size += 1; // slash - } - - if (_mqtt.getDevice() != nullptr) { - size += strlen(_mqtt.getDevice()->getUniqueId()) + 1; // with slash - } - - if (includeNullTerminator) { - size += 1; - } - - return size; + _availability = (online ? AvailabilityOnline : AvailabilityOffline); + publishAvailability(); } -uint16_t BaseDeviceType::generateTopic( - char* output, - const char* component, - const char* objectId, - const char* suffix -) const +void BaseDeviceType::publishAvailability() { - static const char Slash[] PROGMEM = {"/"}; - - const char* prefix = _mqtt.getDiscoveryPrefix(); - strcpy(output, prefix); - strcat_P(output, Slash); - strcat(output, component); - strcat_P(output, Slash); - - if (_mqtt.getDevice() != nullptr) { - strcat(output, _mqtt.getDevice()->getUniqueId()); - strcat_P(output, Slash); + if (_availability == AvailabilityDefault || + !_mqtt.isConnected() || + strlen(_name) == 0 || + strlen(_componentName) == 0) { + return; } - strcat(output, objectId); - strcat_P(output, Slash); - strcat(output, suffix); - return strlen(output) + 1; // size with null terminator + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + _componentName, + _name, + DeviceTypeSerializer::AvailabilityTopic + ); + if (topicSize == 0) { + return; + } + + char topic[topicSize]; + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + _componentName, + _name, + DeviceTypeSerializer::AvailabilityTopic + ); + + if (strlen(topic) == 0) { + return; + } + + mqtt()->publish( + topic, + ( + _availability == AvailabilityOnline ? + DeviceTypeSerializer::Online : + DeviceTypeSerializer::Offline + ), + true + ); } diff --git a/src/device-types/BaseDeviceType.h b/src/device-types/BaseDeviceType.h index 3a56d32..3a72f28 100644 --- a/src/device-types/BaseDeviceType.h +++ b/src/device-types/BaseDeviceType.h @@ -3,17 +3,38 @@ #include +#include "DeviceTypeSerializer.h" + class HAMqtt; class BaseDeviceType { public: - static const char* ConfigTopic; - static const char* EventTopic; - - BaseDeviceType(HAMqtt& mqtt); + BaseDeviceType( + HAMqtt& mqtt, + const char* componentName, + const char* name + ); virtual ~BaseDeviceType(); + inline bool isOnline() const + { return (_availability == AvailabilityOnline); } + + virtual void setAvailability(bool online); + +protected: + inline HAMqtt* mqtt() const + { return &_mqtt; } + + inline const char* name() const + { return _name; } + + inline const char* componentName() const + { return _componentName; } + + inline bool isAvailabilityConfigured() const + { return (_availability != AvailabilityDefault); } + virtual void onMqttConnected() = 0; virtual void onMqttMessage( const char* topic, @@ -21,26 +42,22 @@ public: const uint16_t& length ) { }; -protected: - inline HAMqtt* mqtt() const - { return &_mqtt; } + virtual void publishAvailability(); - virtual uint16_t calculateTopicLength( - const char* component, - const char* objectId, - const char* suffix, - bool includeNullTerminator = true - ) const final; - - virtual uint16_t generateTopic( - char* output, - const char* component, - const char* objectId, - const char* suffix - ) const final; + const char* const _componentName; + const char* const _name; private: + enum Availability { + AvailabilityDefault = 0, + AvailabilityOnline, + AvailabilityOffline + }; + HAMqtt& _mqtt; + Availability _availability; + + friend class HAMqtt; }; #endif diff --git a/src/device-types/DeviceTypeSerializer.cpp b/src/device-types/DeviceTypeSerializer.cpp new file mode 100644 index 0000000..02e921f --- /dev/null +++ b/src/device-types/DeviceTypeSerializer.cpp @@ -0,0 +1,266 @@ +#include + +#include "DeviceTypeSerializer.h" +#include "../HAMqtt.h" +#include "../HADevice.h" + +static const char CharSlash[] PROGMEM = {"/"}; +static const char CharUnderscore[] PROGMEM = {"_"}; +static const char CharQuotation[] PROGMEM = {"\""}; + +const char* DeviceTypeSerializer::ConfigTopic = "config"; +const char* DeviceTypeSerializer::EventTopic = "event"; +const char* DeviceTypeSerializer::AvailabilityTopic = "avail"; +const char* DeviceTypeSerializer::StateTopic = "state"; +const char* DeviceTypeSerializer::CommandTopic = "cmd"; +const char* DeviceTypeSerializer::Online = "online"; +const char* DeviceTypeSerializer::Offline = "offline"; +const char* DeviceTypeSerializer::StateOn = "ON"; +const char* DeviceTypeSerializer::StateOff = "OFF"; + +uint16_t DeviceTypeSerializer::calculateTopicLength( + const HAMqtt* mqtt, + const char* component, + const char* objectId, + const char* suffix, + bool includeNullTerminator +) +{ + const char* prefix = mqtt->getDiscoveryPrefix(); + if (prefix == nullptr) { + return 0; + } + + uint16_t size = + strlen(prefix) + 1 + // with slash + strlen(component) + 1 + // with slash + strlen(suffix); + + if (objectId != nullptr) { + size += strlen(objectId) + 1; // with slash + } else { + size += 1; // slash + } + + if (mqtt->getDevice() != nullptr) { + size += strlen(mqtt->getDevice()->getUniqueId()) + 1; // with slash + } + + if (includeNullTerminator) { + size += 1; + } + + return size; +} + +uint16_t DeviceTypeSerializer::generateTopic( + const HAMqtt* mqtt, + char* output, + const char* component, + const char* objectId, + const char* suffix +) +{ + const char* prefix = mqtt->getDiscoveryPrefix(); + strcpy(output, prefix); + strcat_P(output, CharSlash); + strcat(output, component); + strcat_P(output, CharSlash); + + if (mqtt->getDevice() != nullptr) { + strcat(output, mqtt->getDevice()->getUniqueId()); + strcat_P(output, CharSlash); + } + + strcat(output, objectId); + strcat_P(output, CharSlash); + strcat(output, suffix); + return strlen(output) + 1; // size with null terminator +} + +uint16_t DeviceTypeSerializer::calculateBaseJsonDataSize() +{ + return 2; // opening and closing brackets of the JSON data +} + +uint16_t DeviceTypeSerializer::calculateNameFieldSize(const char* name) +{ + if (name == nullptr) { + return 0; + } + + // Field format: ,"name":"[NAME]" + return strlen(name) + 10; // 10 - length of the JSON decorators for this field +} + +uint16_t DeviceTypeSerializer::calculateUniqueIdFieldSize( + const HADevice* device, + const char* name +) +{ + if (device == nullptr || name == nullptr) { + return 0; + } + + // Field format: ,"uniq_id":"[DEVICE ID]_[NAME]" + return ( + strlen(device->getUniqueId()) + + strlen(name) + + 14 // 14 - length of the JSON decorators for this field + ); +} + +uint16_t DeviceTypeSerializer::calculateAvailabilityFieldSize( + const HAMqtt* mqtt, + const char* componentName, + const char* name +) +{ + if (mqtt == nullptr || componentName == nullptr || name == nullptr) { + return 0; + } + + const uint16_t& availabilityTopicLength = calculateTopicLength( + mqtt, + componentName, + name, + AvailabilityTopic, + false + ); + + if (availabilityTopicLength == 0) { + return 0; + } + + // Field format: ,"avty_t":"[TOPIC]" + return availabilityTopicLength + 12; // 12 - length of the JSON decorators for this field +} + +uint16_t DeviceTypeSerializer::calculateDeviceFieldSize( + const char* serializedDevice +) +{ + if (serializedDevice == nullptr) { + return 0; + } + + // Field format: ,"dev":[DEVICE] + return strlen(serializedDevice) + 7; // 7 - length of the JSON decorators for this field +} + +void DeviceTypeSerializer::mqttWriteBeginningJson(HAMqtt* mqtt) +{ + if (mqtt == nullptr) { + return; + } + + static const char Data[] PROGMEM = {"{"}; + mqtt->writePayload_P(Data); +} + +void DeviceTypeSerializer::mqttWriteEndJson(HAMqtt* mqtt) +{ + if (mqtt == nullptr) { + return; + } + + static const char Data[] PROGMEM = {"}"}; + mqtt->writePayload_P(Data); +} + +void DeviceTypeSerializer::mqttWriteConstCharField( + HAMqtt* mqtt, + const char* prefix, + const char* value +) +{ + if (mqtt == nullptr || prefix == nullptr || value == nullptr) { + return; + } + + mqtt->writePayload_P(prefix); + mqtt->writePayload(value, strlen(value)); + mqtt->writePayload_P(CharQuotation); +} + +void DeviceTypeSerializer::mqttWriteNameField(HAMqtt* mqtt, const char* name) +{ + if (mqtt == nullptr || name == nullptr) { + return; + } + + static const char Prefix[] PROGMEM = {",\"name\":\""}; + mqttWriteConstCharField(mqtt, Prefix, name); +} + +void DeviceTypeSerializer::mqttWriteUniqueIdField( + HAMqtt* mqtt, + const char* name +) +{ + if (mqtt == nullptr || name == nullptr) { + return; + } + + static const char Prefix[] PROGMEM = {",\"uniq_id\":\""}; + + uint8_t uniqueIdLength = strlen(name) + strlen(mqtt->getDevice()->getUniqueId()) + 2; // underscore and null temrinator + char uniqueId[uniqueIdLength]; + strcpy(uniqueId, name); + strcat_P(uniqueId, CharUnderscore); + strcat(uniqueId, mqtt->getDevice()->getUniqueId()); + + mqttWriteConstCharField(mqtt, Prefix, uniqueId); +} + +void DeviceTypeSerializer::mqttWriteAvailabilityField( + HAMqtt* mqtt, + const char* componentName, + const char* name +) +{ + if (mqtt == nullptr || componentName == nullptr || name == nullptr) { + return; + } + + const uint16_t& topicSize = calculateTopicLength( + mqtt, + componentName, + name, + AvailabilityTopic + ); + if (topicSize == 0) { + return; + } + + char availabilityTopic[topicSize]; + generateTopic( + mqtt, + availabilityTopic, + componentName, + name, + AvailabilityTopic + ); + + if (strlen(availabilityTopic) == 0) { + return; + } + + static const char Prefix[] PROGMEM = {",\"avty_t\":\""}; + mqttWriteConstCharField(mqtt, Prefix, availabilityTopic); +} + +void DeviceTypeSerializer::mqttWriteDeviceField( + HAMqtt* mqtt, + const char* serializedDevice +) +{ + if (mqtt == nullptr || serializedDevice == nullptr) { + return; + } + + static const char Data[] PROGMEM = {",\"dev\":"}; + + mqtt->writePayload_P(Data); + mqtt->writePayload(serializedDevice, strlen(serializedDevice)); +} diff --git a/src/device-types/DeviceTypeSerializer.h b/src/device-types/DeviceTypeSerializer.h new file mode 100644 index 0000000..a82ebad --- /dev/null +++ b/src/device-types/DeviceTypeSerializer.h @@ -0,0 +1,98 @@ +#ifndef AHA_DEVICETYPESERIALIZER_H +#define AHA_DEVICETYPESERIALIZER_H + +#include + +class HAMqtt; +class HADevice; + +class DeviceTypeSerializer +{ +public: + static const char* ConfigTopic; + static const char* EventTopic; + static const char* AvailabilityTopic; + static const char* StateTopic; + static const char* CommandTopic; + static const char* Online; + static const char* Offline; + static const char* StateOn; + static const char* StateOff; + + /** + * Calculates length of the topic with given parameters. + * Topic format: [discovery prefix]/[component]/[objectId]/[suffix] + * + * @param component + * @param objectId + * @param suffix + * @param includeNullTerminator + */ + static uint16_t calculateTopicLength( + const HAMqtt* mqtt, + const char* component, + const char* objectId, + const char* suffix, + bool includeNullTerminator = true + ); + + /** + * Generates topic and saves it to the given buffer. + * Please note that size of the buffer must be calculated by `calculateTopicLength` method first. + * Topic format: [discovery prefix]/[component]/[objectId]/[suffix] + * + * @param output + * @param component + * @param objectId + * @param suffix + * @param includeNullTerminator + */ + static uint16_t generateTopic( + const HAMqtt* mqtt, + char* output, + const char* component, + const char* objectId, + const char* suffix + ); + + static uint16_t calculateBaseJsonDataSize(); + static uint16_t calculateNameFieldSize( + const char* name + ); + static uint16_t calculateUniqueIdFieldSize( + const HADevice* device, + const char* name + ); + static uint16_t calculateAvailabilityFieldSize( + const HAMqtt* mqtt, + const char* componentName, + const char* name + ); + static uint16_t calculateDeviceFieldSize( + const char* serializedDevice + ); + + static void mqttWriteBeginningJson(HAMqtt* mqtt); + static void mqttWriteEndJson(HAMqtt* mqtt); + static void mqttWriteConstCharField( + HAMqtt* mqtt, + const char* prefix, + const char* value + ); + static void mqttWriteNameField(HAMqtt* mqtt, const char* name); + static void mqttWriteUniqueIdField( + HAMqtt* mqtt, + const char* name + ); + static void mqttWriteAvailabilityField( + HAMqtt* mqtt, + const char* componentName, + const char* name + ); + static void mqttWriteDeviceField( + HAMqtt* mqtt, + const char* serializedDevice + ); +}; + +#endif diff --git a/src/device-types/HABinarySensor.cpp b/src/device-types/HABinarySensor.cpp index 7d809af..997e2c9 100644 --- a/src/device-types/HABinarySensor.cpp +++ b/src/device-types/HABinarySensor.cpp @@ -4,19 +4,12 @@ #include "../HADevice.h" #include "../HAUtils.h" -// todo: move all variables to progmem -static const char* HAComponentName = "binary_sensor"; -static const char* StateTopic = "state"; -static const char* StateOn = "ON"; -static const char* StateOff = "OFF"; - HABinarySensor::HABinarySensor( const char* name, bool initialState, HAMqtt& mqtt ) : - BaseDeviceType(mqtt), - _name(name), + BaseDeviceType(mqtt, "binary_sensor", name), _class(nullptr), _currentState(initialState) { @@ -29,27 +22,22 @@ HABinarySensor::HABinarySensor( bool initialState, HAMqtt& mqtt ) : - BaseDeviceType(mqtt), - _name(name), + BaseDeviceType(mqtt, "binary_sensor", name), _class(deviceClass), _currentState(initialState) { } -HABinarySensor::~HABinarySensor() -{ - -} - void HABinarySensor::onMqttConnected() { - if (strlen(_name) == 0) { + if (strlen(name()) == 0) { return; } publishConfig(); publishState(_currentState); + publishAvailability(); } bool HABinarySensor::setState(bool state) @@ -58,7 +46,7 @@ bool HABinarySensor::setState(bool state) return true; } - if (strlen(_name) == 0) { + if (strlen(name()) == 0) { return false; } @@ -87,7 +75,12 @@ void HABinarySensor::publishConfig() return; } - const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic); + const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::ConfigTopic + ); const uint16_t& dataLength = calculateSerializedLength(serializedDevice); if (topicLength == 0 || dataLength == 0) { @@ -95,46 +88,62 @@ void HABinarySensor::publishConfig() } char topic[topicLength]; - generateTopic(topic, HAComponentName, _name, ConfigTopic); + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + componentName(), + name(), + DeviceTypeSerializer::ConfigTopic + ); if (strlen(topic) == 0) { return; } if (mqtt()->beginPublish(topic, dataLength, true)) { - writeSerializedTrigger(serializedDevice); + writeSerializedData(serializedDevice); mqtt()->endPublish(); } } bool HABinarySensor::publishState(bool state) { - if (strlen(_name) == 0) { + if (strlen(name()) == 0) { return false; } - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - StateTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (topicSize == 0) { return false; } char topic[topicSize]; - generateTopic( + DeviceTypeSerializer::generateTopic( + mqtt(), topic, - HAComponentName, - _name, - StateTopic + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (strlen(topic) == 0) { return false; } - return mqtt()->publish(topic, (state ? StateOn : StateOff), true); + return mqtt()->publish( + topic, + ( + state ? + DeviceTypeSerializer::StateOn : + DeviceTypeSerializer::StateOff + ), + true + ); } uint16_t HABinarySensor::calculateSerializedLength( @@ -145,122 +154,108 @@ uint16_t HABinarySensor::calculateSerializedLength( return 0; } - const uint16_t& stateTopicLength = calculateTopicLength( - HAComponentName, - _name, - StateTopic, - false - ); - - if (stateTopicLength == 0) { + const HADevice* device = mqtt()->getDevice(); + if (device == nullptr) { return 0; } - uint16_t size = - 2 + // opening and closing bracket (without null terminator) - stateTopicLength + 11 + // 11 - length of the JSON data for this field - strlen(_name) + 10; // 10 - length of the JSON data for this field + uint16_t size = 0; + size += DeviceTypeSerializer::calculateBaseJsonDataSize(); + size += DeviceTypeSerializer::calculateNameFieldSize(name()); + size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name()); + size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice); - // unique ID - size += strlen(mqtt()->getDevice()->getUniqueId()); - size += strlen(_name) + 14; // 14 - length of the JSON data for this field + if (isAvailabilityConfigured()) { + size += DeviceTypeSerializer::calculateAvailabilityFieldSize( + mqtt(), + componentName(), + name() + ); + } + + // state topic + { + const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic, + false + ); + + if (topicLength == 0) { + return 0; + } + + // Field format: "stat_t":"[TOPIC]" + size += topicLength + 11; // 11 - length of the JSON decorators for this field + } // device class if (_class != nullptr) { - size += strlen(_class) + 13; // 13 - length of the JSON data for this field + // Field format: ,"dev_cla":"[CLASS]" + size += strlen(_class) + 13; // 13 - length of the JSON decorators for this field } - // device - size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field - - return size; + return size; // exludes null terminator } -bool HABinarySensor::writeSerializedTrigger(const char* serializedDevice) const +bool HABinarySensor::writeSerializedData(const char* serializedDevice) const { if (serializedDevice == nullptr) { return false; } - static const char QuotationSign[] PROGMEM = {"\""}; + DeviceTypeSerializer::mqttWriteBeginningJson(mqtt()); // state topic { - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - StateTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (topicSize == 0) { return false; } - char stateTopic[topicSize]; - generateTopic( - stateTopic, - HAComponentName, - _name, - StateTopic + char topic[topicSize]; + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); - if (strlen(stateTopic) == 0) { + if (strlen(topic) == 0) { return false; } - static const char DataBefore[] PROGMEM = {"{\"stat_t\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(stateTopic, strlen(stateTopic)); - mqtt()->writePayload_P(QuotationSign); - } - - // name - { - static const char DataBefore[] PROGMEM = {",\"name\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(_name, strlen(_name)); - mqtt()->writePayload_P(QuotationSign); - } - - // unique ID - { - static const char Underscore[] PROGMEM = {"_"}; - static const char DataBefore[] PROGMEM = {",\"uniq_id\":\""}; - - const HADevice* device = mqtt()->getDevice(); - uint8_t uniqueIdLength = strlen(_name) + strlen(device->getUniqueId()) + 2; // underscore and null temrinator - char uniqueId[uniqueIdLength]; - strcpy(uniqueId, _name); - strcat_P(uniqueId, Underscore); - strcat(uniqueId, device->getUniqueId()); - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(uniqueId, strlen(uniqueId)); - mqtt()->writePayload_P(QuotationSign); - } - - // device - { - static const char Data[] PROGMEM = {",\"dev\":"}; - - mqtt()->writePayload_P(Data); - mqtt()->writePayload(serializedDevice, strlen(serializedDevice)); + static const char Prefix[] PROGMEM = {"\"stat_t\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic); } // device class if (_class != nullptr) { - static const char DataBefore[] PROGMEM = {",\"dev_cla\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(_class, strlen(_class)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {",\"dev_cla\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _class); } - { - static const char Data[] PROGMEM = {"}"}; - mqtt()->writePayload_P(Data); + DeviceTypeSerializer::mqttWriteNameField(mqtt(), name()); + DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name()); + + if (isAvailabilityConfigured()) { + DeviceTypeSerializer::mqttWriteAvailabilityField( + mqtt(), + componentName(), + name() + ); } + DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice); + DeviceTypeSerializer::mqttWriteEndJson(mqtt()); + return true; } diff --git a/src/device-types/HABinarySensor.h b/src/device-types/HABinarySensor.h index 5eac0f1..1973450 100644 --- a/src/device-types/HABinarySensor.h +++ b/src/device-types/HABinarySensor.h @@ -35,8 +35,6 @@ public: HAMqtt& mqtt ); - virtual ~HABinarySensor(); - /** * Publishes configuration of the sensor to the MQTT. */ @@ -63,9 +61,8 @@ private: void publishConfig(); bool publishState(bool state); uint16_t calculateSerializedLength(const char* serializedDevice) const; - bool writeSerializedTrigger(const char* serializedDevice) const; + bool writeSerializedData(const char* serializedDevice) const; - const char* _name; const char* _class; bool _currentState; }; diff --git a/src/device-types/HASensor.cpp b/src/device-types/HASensor.cpp index 2226416..641c2c6 100644 --- a/src/device-types/HASensor.cpp +++ b/src/device-types/HASensor.cpp @@ -4,18 +4,13 @@ #include "../HADevice.h" #include "../HAUtils.h" -// todo: move all variables to progmem -static const char* HAComponentName = "sensor"; -static const char* ValueTopic = "value"; - template HASensor::HASensor( const char* name, T initialValue, HAMqtt& mqtt ) : - BaseDeviceType(mqtt), - _name(name), + BaseDeviceType(mqtt, "sensor", name), _class(nullptr), _units(nullptr), _valueType(HAUtils::determineValueType()), @@ -31,8 +26,7 @@ HASensor::HASensor( T initialValue, HAMqtt& mqtt ) : - BaseDeviceType(mqtt), - _name(name), + BaseDeviceType(mqtt, "sensor", name), _class(deviceClass), _units(nullptr), _valueType(HAUtils::determineValueType()), @@ -41,27 +35,23 @@ HASensor::HASensor( } -template -HASensor::~HASensor() -{ - -} - template void HASensor::onMqttConnected() { - if (strlen(_name) == 0 || _valueType == HAUtils::ValueTypeUnknown) { + if (strlen(name()) == 0 || + _valueType == HAUtils::ValueTypeUnknown) { return; } publishConfig(); publishValue(_currentValue); + publishAvailability(); } template bool HASensor::setValue(T value) { - if (strlen(_name) == 0 || + if (strlen(name()) == 0 || _valueType == HAUtils::ValueTypeUnknown) { return false; } @@ -100,7 +90,12 @@ void HASensor::publishConfig() return; } - const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic); + const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::ConfigTopic + ); const uint16_t& dataLength = calculateSerializedLength(serializedDevice); if (topicLength == 0 || dataLength == 0) { @@ -108,14 +103,20 @@ void HASensor::publishConfig() } char topic[topicLength]; - generateTopic(topic, HAComponentName, _name, ConfigTopic); + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + componentName(), + name(), + DeviceTypeSerializer::ConfigTopic + ); if (strlen(topic) == 0) { return; } if (mqtt()->beginPublish(topic, dataLength, true)) { - writeSerializedTrigger(serializedDevice); + writeSerializedData(serializedDevice); mqtt()->endPublish(); } } @@ -123,26 +124,28 @@ void HASensor::publishConfig() template bool HASensor::publishValue(T value) { - if (strlen(_name) == 0 || + if (strlen(name()) == 0 || _valueType == HAUtils::ValueTypeUnknown) { return false; } - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - ValueTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (topicSize == 0) { return false; } char topic[topicSize]; - generateTopic( + DeviceTypeSerializer::generateTopic( + mqtt(), topic, - HAComponentName, - _name, - ValueTopic + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (strlen(topic) == 0) { @@ -172,139 +175,122 @@ uint16_t HASensor::calculateSerializedLength( return 0; } - const uint16_t& valueTopicLength = calculateTopicLength( - HAComponentName, - _name, - ValueTopic, - false - ); - - if (valueTopicLength == 0) { + const HADevice* device = mqtt()->getDevice(); + if (device == nullptr) { return 0; } - uint16_t size = - 2 + // opening and closing bracket (without null terminator) - valueTopicLength + 11 + // 11 - length of the JSON data for this field - strlen(_name) + 10; // 10 - length of the JSON data for this field + uint16_t size = 0; + size += DeviceTypeSerializer::calculateBaseJsonDataSize(); + size += DeviceTypeSerializer::calculateNameFieldSize(name()); + size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name()); + size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice); - // unique ID - size += strlen(mqtt()->getDevice()->getUniqueId()); - size += strlen(_name) + 14; // 14 - length of the JSON data for this field + if (isAvailabilityConfigured()) { + size += DeviceTypeSerializer::calculateAvailabilityFieldSize( + mqtt(), + componentName(), + name() + ); + } + + { + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic, + false + ); + + if (topicSize == 0) { + return 0; + } + + // Format: "stat_t":"[TOPIC]" + size += topicSize + 11; // 11 - length of the JSON decorators for this field + } // device class if (_class != nullptr) { - size += strlen(_class) + 13; // 13 - length of the JSON data for this field + // Field format: ,"dev_cla":"[CLASS]" + size += strlen(_class) + 13; // 13 - length of the JSON decorators for this field } // units of measurement if (_units != nullptr) { - size += strlen(_units) + 18; + // Format: ,"unit_of_meas":"[UNITS]" + size += strlen(_units) + 18; // 18 - length of the JSON decorators for this field } - // device - size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field - - return size; + return size; // exludes null terminator } template -bool HASensor::writeSerializedTrigger(const char* serializedDevice) const +bool HASensor::writeSerializedData(const char* serializedDevice) const { if (serializedDevice == nullptr || _valueType == HAUtils::ValueTypeUnknown) { return false; } - static const char QuotationSign[] PROGMEM = {"\""}; + DeviceTypeSerializer::mqttWriteBeginningJson(mqtt()); // state topic { - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - ValueTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (topicSize == 0) { return false; } - char stateTopic[topicSize]; - generateTopic( - stateTopic, - HAComponentName, - _name, - ValueTopic + char topic[topicSize]; + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); - if (strlen(stateTopic) == 0) { + if (strlen(topic) == 0) { return false; } - static const char DataBefore[] PROGMEM = {"{\"stat_t\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(stateTopic, strlen(stateTopic)); - mqtt()->writePayload_P(QuotationSign); - } - - // name - { - static const char DataBefore[] PROGMEM = {",\"name\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(_name, strlen(_name)); - mqtt()->writePayload_P(QuotationSign); - } - - // unique ID - { - static const char Underscore[] PROGMEM = {"_"}; - static const char DataBefore[] PROGMEM = {",\"uniq_id\":\""}; - - const HADevice* device = mqtt()->getDevice(); - uint8_t uniqueIdLength = strlen(_name) + strlen(device->getUniqueId()) + 2; // underscore and null temrinator - char uniqueId[uniqueIdLength]; - strcpy(uniqueId, _name); - strcat_P(uniqueId, Underscore); - strcat(uniqueId, device->getUniqueId()); - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(uniqueId, strlen(uniqueId)); - mqtt()->writePayload_P(QuotationSign); - } - - // device - { - static const char Data[] PROGMEM = {",\"dev\":"}; - - mqtt()->writePayload_P(Data); - mqtt()->writePayload(serializedDevice, strlen(serializedDevice)); + static const char Prefix[] PROGMEM = {"\"stat_t\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic); } // device class if (_class != nullptr) { - static const char DataBefore[] PROGMEM = {",\"dev_cla\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(_class, strlen(_class)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {",\"dev_cla\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _class); } // units of measurement if (_units != nullptr) { - static const char DataBefore[] PROGMEM = {",\"unit_of_meas\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(_units, strlen(_units)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {",\"unit_of_meas\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _units); } - { - static const char Data[] PROGMEM = {"}"}; - mqtt()->writePayload_P(Data); + DeviceTypeSerializer::mqttWriteNameField(mqtt(), name()); + DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name()); + + if (isAvailabilityConfigured()) { + DeviceTypeSerializer::mqttWriteAvailabilityField( + mqtt(), + componentName(), + name() + ); } + DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice); + DeviceTypeSerializer::mqttWriteEndJson(mqtt()); + return true; } diff --git a/src/device-types/HASensor.h b/src/device-types/HASensor.h index 6c449d9..8d17800 100644 --- a/src/device-types/HASensor.h +++ b/src/device-types/HASensor.h @@ -37,8 +37,6 @@ public: HAMqtt& mqtt ); - virtual ~HASensor(); - /** * Publishes configuration of the sensor to the MQTT. */ @@ -73,11 +71,10 @@ private: void publishConfig(); bool publishValue(T value); uint16_t calculateSerializedLength(const char* serializedDevice) const; - bool writeSerializedTrigger(const char* serializedDevice) const; + bool writeSerializedData(const char* serializedDevice) const; uint16_t calculateValueLength() const; bool valueToStr(char* dst, T value) const; - const char* _name; const char* _class; const char* _units; HAUtils::ValueType _valueType; diff --git a/src/device-types/HASwitch.cpp b/src/device-types/HASwitch.cpp index 0cf9ca7..b581814 100644 --- a/src/device-types/HASwitch.cpp +++ b/src/device-types/HASwitch.cpp @@ -4,36 +4,24 @@ #include "../HADevice.h" #include "../HAUtils.h" -// todo: move all variables to progmem -static const char* HAComponentName = "switch"; -static const char* CommandTopic = "cmd"; -static const char* StateTopic = "state"; -static const char* StateOn = "ON"; -static const char* StateOff = "OFF"; - HASwitch::HASwitch(const char* name, bool initialState, HAMqtt& mqtt) : - BaseDeviceType(mqtt), - _name(name), + BaseDeviceType(mqtt, "switch", name), _stateCallback(nullptr), _currentState(initialState) { } -HASwitch::~HASwitch() -{ - -} - void HASwitch::onMqttConnected() { - if (strlen(_name) == 0) { + if (strlen(name()) == 0) { return; } publishConfig(); publishState(_currentState); subscribeCommandTopic(); + publishAvailability(); } void HASwitch::onMqttMessage( @@ -42,21 +30,22 @@ void HASwitch::onMqttMessage( const uint16_t& length ) { - if (strlen(_name) == 0) { + if (strlen(name()) == 0) { return; } static const char Slash[] PROGMEM = {"/"}; - uint8_t suffixLength = strlen(_name) + strlen(CommandTopic) + 3; // two slashes + null terminator + // name + cmd topic + two slashes + null terminator + uint8_t suffixLength = strlen(name()) + strlen(DeviceTypeSerializer::CommandTopic) + 3; char suffix[suffixLength]; strcpy_P(suffix, Slash); - strcat(suffix, _name); + strcat(suffix, name()); strcat_P(suffix, Slash); - strcat(suffix, CommandTopic); + strcat(suffix, DeviceTypeSerializer::CommandTopic); if (HAUtils::endsWith(topic, suffix)) { - bool onState = (length == strlen(StateOn)); + bool onState = (length == strlen(DeviceTypeSerializer::StateOn)); setState(onState); } } @@ -67,7 +56,7 @@ bool HASwitch::setState(bool state) return true; } - if (strlen(_name) == 0) { + if (strlen(name()) == 0) { return false; } @@ -80,11 +69,6 @@ bool HASwitch::setState(bool state) return false; } -void HASwitch::onStateChanged(HASWITCH_CALLBACK) -{ - _stateCallback = callback; -} - void HASwitch::triggerCallback(bool state) { if (_stateCallback == nullptr) { @@ -111,7 +95,12 @@ void HASwitch::publishConfig() return; } - const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic); + const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::ConfigTopic + ); const uint16_t& dataLength = calculateSerializedLength(serializedDevice); if (topicLength == 0 || dataLength == 0) { @@ -119,65 +108,83 @@ void HASwitch::publishConfig() } char topic[topicLength]; - generateTopic(topic, HAComponentName, _name, ConfigTopic); + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + componentName(), + name(), + DeviceTypeSerializer::ConfigTopic + ); if (strlen(topic) == 0) { return; } if (mqtt()->beginPublish(topic, dataLength, true)) { - writeSerializedTrigger(serializedDevice); + writeSerializedData(serializedDevice); mqtt()->endPublish(); } } bool HASwitch::publishState(bool state) { - if (strlen(_name) == 0) { + if (strlen(name()) == 0) { return false; } - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - StateTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (topicSize == 0) { return false; } char topic[topicSize]; - generateTopic( + DeviceTypeSerializer::generateTopic( + mqtt(), topic, - HAComponentName, - _name, - StateTopic + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (strlen(topic) == 0) { return false; } - return mqtt()->publish(topic, (state ? StateOn : StateOff), true); + return mqtt()->publish( + topic, + ( + state ? + DeviceTypeSerializer::StateOn : + DeviceTypeSerializer::StateOff + ), + true + ); } void HASwitch::subscribeCommandTopic() { - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - CommandTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::CommandTopic ); if (topicSize == 0) { return; } char topic[topicSize]; - generateTopic( + DeviceTypeSerializer::generateTopic( + mqtt(), topic, - HAComponentName, - _name, - CommandTopic + componentName(), + name(), + DeviceTypeSerializer::CommandTopic ); if (strlen(topic) == 0) { @@ -193,145 +200,143 @@ uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const return 0; } - const uint16_t& cmdTopicLength = calculateTopicLength( - HAComponentName, - _name, - CommandTopic, - false - ); - const uint16_t& stateTopicLength = calculateTopicLength( - HAComponentName, - _name, - StateTopic, - false - ); - - if (cmdTopicLength == 0 || stateTopicLength == 0) { + const HADevice* device = mqtt()->getDevice(); + if (device == nullptr) { return 0; } - uint16_t size = - 2 + // opening and closing bracket (without null terminator) - cmdTopicLength + 10 + // 10 - length of the JSON data for this field - stateTopicLength + 12 + // 12 - length of the JSON data for this field - strlen(_name) + 10; // 10 - length of the JSON data for this field + uint16_t size = 0; + size += DeviceTypeSerializer::calculateBaseJsonDataSize(); + size += DeviceTypeSerializer::calculateNameFieldSize(name()); + size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name()); + size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice); - // unique ID - size += strlen(mqtt()->getDevice()->getUniqueId()); - size += strlen(_name) + 14; // 14 - length of the JSON data for this field + if (isAvailabilityConfigured()) { + size += DeviceTypeSerializer::calculateAvailabilityFieldSize( + mqtt(), + componentName(), + name() + ); + } - // device - size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field + // cmd topic + { + const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::CommandTopic, + false + ); - return size; + if (topicLength == 0) { + return 0; + } + + // Field format: "cmd_t":"[TOPIC]" + size += topicLength + 10; // 10 - length of the JSON decorators for this field + } + + // state topic + { + const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic, + false + ); + + if (topicLength == 0) { + return 0; + } + + // Field format: ,"stat_t":"[TOPIC]" + size += topicLength + 12; // 12 - length of the JSON decorators for this field + } + + return size; // exludes null terminator } -bool HASwitch::writeSerializedTrigger(const char* serializedDevice) const +bool HASwitch::writeSerializedData(const char* serializedDevice) const { if (serializedDevice == nullptr) { return false; } - static const char QuotationSign[] PROGMEM = {"\""}; + DeviceTypeSerializer::mqttWriteBeginningJson(mqtt()); // command topic { - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - CommandTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::CommandTopic ); if (topicSize == 0) { return false; } - char cmdTopic[topicSize]; - generateTopic( - cmdTopic, - HAComponentName, - _name, - CommandTopic + char topic[topicSize]; + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + componentName(), + name(), + DeviceTypeSerializer::CommandTopic ); - if (strlen(cmdTopic) == 0) { + if (strlen(topic) == 0) { return false; } - static const char DataBefore[] PROGMEM = {"{\"cmd_t\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(cmdTopic, strlen(cmdTopic)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {"\"cmd_t\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic); } // state topic { - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - StateTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); if (topicSize == 0) { return false; } - char stateTopic[topicSize]; - generateTopic( - stateTopic, - HAComponentName, - _name, - StateTopic + char topic[topicSize]; + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + componentName(), + name(), + DeviceTypeSerializer::StateTopic ); - if (strlen(stateTopic) == 0) { + if (strlen(topic) == 0) { return false; } - static const char DataBefore[] PROGMEM = {",\"stat_t\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(stateTopic, strlen(stateTopic)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {",\"stat_t\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic); } - // name - { - static const char DataBefore[] PROGMEM = {",\"name\":\""}; + DeviceTypeSerializer::mqttWriteNameField(mqtt(), name()); + DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name()); - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(_name, strlen(_name)); - mqtt()->writePayload_P(QuotationSign); + if (isAvailabilityConfigured()) { + DeviceTypeSerializer::mqttWriteAvailabilityField( + mqtt(), + componentName(), + name() + ); } - // unique ID - { - static const char Underscore[] PROGMEM = {"_"}; - static const char DataBefore[] PROGMEM = {",\"uniq_id\":\""}; - - const HADevice* device = mqtt()->getDevice(); - uint8_t uniqueIdLength = strlen(_name) + strlen(device->getUniqueId()) + 2; // underscore and null temrinator - char uniqueId[uniqueIdLength]; - strcpy(uniqueId, _name); - strcat_P(uniqueId, Underscore); - strcat(uniqueId, device->getUniqueId()); - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(uniqueId, strlen(uniqueId)); - mqtt()->writePayload_P(QuotationSign); - } - - // device - { - static const char Data[] PROGMEM = {",\"dev\":"}; - - mqtt()->writePayload_P(Data); - mqtt()->writePayload(serializedDevice, strlen(serializedDevice)); - } - - { - static const char Data[] PROGMEM = {"}"}; - mqtt()->writePayload_P(Data); - } + DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice); + DeviceTypeSerializer::mqttWriteEndJson(mqtt()); return true; } diff --git a/src/device-types/HASwitch.h b/src/device-types/HASwitch.h index 56b20f7..9558189 100644 --- a/src/device-types/HASwitch.h +++ b/src/device-types/HASwitch.h @@ -20,7 +20,6 @@ public: bool initialState, HAMqtt& mqtt ); - virtual ~HASwitch(); /** * Publishes configuration of the sensor to the MQTT. @@ -78,7 +77,8 @@ public: * * @param callback */ - void onStateChanged(HASWITCH_CALLBACK); + inline void onStateChanged(HASWITCH_CALLBACK) + { _stateCallback = callback; } private: void triggerCallback(bool state); @@ -86,9 +86,8 @@ private: bool publishState(bool state); void subscribeCommandTopic(); uint16_t calculateSerializedLength(const char* serializedDevice) const; - bool writeSerializedTrigger(const char* serializedDevice) const; + bool writeSerializedData(const char* serializedDevice) const; - const char* _name; void (*_stateCallback)(bool, HASwitch*); bool _currentState; }; diff --git a/src/device-types/HATagScanner.cpp b/src/device-types/HATagScanner.cpp index 77f7b0e..adec525 100644 --- a/src/device-types/HATagScanner.cpp +++ b/src/device-types/HATagScanner.cpp @@ -4,24 +4,15 @@ #include "../HADevice.h" #include "../HAUtils.h" -// todo: move to progmem -static const char* HAComponentName = "tag"; - HATagScanner::HATagScanner(const char* name, HAMqtt& mqtt) : - BaseDeviceType(mqtt), - _name(name) -{ - -} - -HATagScanner::~HATagScanner() + BaseDeviceType(mqtt, "tag", name) { } void HATagScanner::onMqttConnected() { - if (strlen(_name) == 0) { + if (strlen(name()) == 0) { return; } @@ -34,21 +25,23 @@ bool HATagScanner::tagScanned(const char* tag) return false; } - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - EventTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::EventTopic ); if (topicSize == 0) { return false; } char topic[topicSize]; - generateTopic( + DeviceTypeSerializer::generateTopic( + mqtt(), topic, - HAComponentName, - _name, - EventTopic + componentName(), + name(), + DeviceTypeSerializer::EventTopic ); if (strlen(topic) == 0) { @@ -75,7 +68,12 @@ void HATagScanner::publishConfig() return; } - const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic); + const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::ConfigTopic + ); const uint16_t& dataLength = calculateSerializedLength(serializedDevice); if (topicLength == 0 || dataLength == 0) { @@ -83,14 +81,20 @@ void HATagScanner::publishConfig() } char topic[topicLength]; - generateTopic(topic, HAComponentName, _name, ConfigTopic); + DeviceTypeSerializer::generateTopic( + mqtt(), + topic, + componentName(), + name(), + DeviceTypeSerializer::ConfigTopic + ); if (strlen(topic) == 0) { return; } if (mqtt()->beginPublish(topic, dataLength, true)) { - writeSerializedTrigger(serializedDevice); + writeSerializedData(serializedDevice); mqtt()->endPublish(); } } @@ -103,63 +107,58 @@ uint16_t HATagScanner::calculateSerializedLength( return 0; } - const uint16_t& eventTopicLength = calculateTopicLength( - HAComponentName, - _name, - EventTopic, - false - ); + uint16_t size = 0; + size += DeviceTypeSerializer::calculateBaseJsonDataSize(); + size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice); - uint16_t size = - 2 + // opening and closing bracket (without null terminator) - eventTopicLength + 6 + // 6 - length of the JSON data for this field - strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field + // event topic + { + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::EventTopic, + false + ); - return size; + // Format: "t":"[TOPIC]" + size += topicSize + 6; // 6 - length of the JSON decorators for this field + } + + return size; // exludes null terminator } -bool HATagScanner::writeSerializedTrigger(const char* serializedDevice) const +bool HATagScanner::writeSerializedData(const char* serializedDevice) const { if (serializedDevice == nullptr) { return false; } - static const char QuotationSign[] PROGMEM = {"\""}; + DeviceTypeSerializer::mqttWriteBeginningJson(mqtt()); // topic { - const uint16_t& topicSize = calculateTopicLength( - HAComponentName, - _name, - EventTopic + const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength( + mqtt(), + componentName(), + name(), + DeviceTypeSerializer::EventTopic ); char topic[topicSize]; - generateTopic( + DeviceTypeSerializer::generateTopic( + mqtt(), topic, - HAComponentName, - _name, - EventTopic + componentName(), + name(), + DeviceTypeSerializer::EventTopic ); - static const char DataBefore[] PROGMEM = {"{\"t\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(topic, strlen(topic)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {"\"t\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic); } - // device - { - static const char Data[] PROGMEM = {",\"dev\":"}; - - mqtt()->writePayload_P(Data); - mqtt()->writePayload(serializedDevice, strlen(serializedDevice)); - } - - { - static const char Data[] PROGMEM = {"}"}; - mqtt()->writePayload_P(Data); - } + DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice); + DeviceTypeSerializer::mqttWriteEndJson(mqtt()); return true; } diff --git a/src/device-types/HATagScanner.h b/src/device-types/HATagScanner.h index 625b5b2..3e885a7 100644 --- a/src/device-types/HATagScanner.h +++ b/src/device-types/HATagScanner.h @@ -12,13 +12,17 @@ public: * @param name Name of the scanner. Recommendes characters: [a-z0-9\-_] */ HATagScanner(const char* name, HAMqtt& mqtt); - virtual ~HATagScanner(); /** * Publishes configuration of the sensor to the MQTT. */ virtual void onMqttConnected() override; + /** + * Tag scanner doesn't support availability. Nothing to do here. + */ + virtual void setAvailability(bool online) override { } + /** * Sends "tag scanned" event to the MQTT (Home Assistant). * Based on this event HA may perform user-defined automation. @@ -30,9 +34,7 @@ public: private: void publishConfig(); uint16_t calculateSerializedLength(const char* serializedDevice) const; - bool writeSerializedTrigger(const char* serializedDevice) const; - - const char* _name; + bool writeSerializedData(const char* serializedDevice) const; }; #endif diff --git a/src/device-types/HATriggers.cpp b/src/device-types/HATriggers.cpp index 7c1a3c8..b51b93e 100644 --- a/src/device-types/HATriggers.cpp +++ b/src/device-types/HATriggers.cpp @@ -3,10 +3,8 @@ #include "../HAMqtt.h" #include "../HADevice.h" -static const char* HAComponentName = "device_automation"; // todo: move to progmem - HATriggers::HATriggers(HAMqtt& mqtt) : - BaseDeviceType(mqtt), + BaseDeviceType(mqtt, "device_automation", nullptr), _triggers(nullptr), _triggersNb(0) { @@ -68,9 +66,9 @@ bool HATriggers::trigger(const char* type, const char* subtype) } const uint16_t& topicSize = calculateTopicLength( - HAComponentName, + componentName(), trigger, - EventTopic + DeviceTypeSerializer::EventTopic ); if (topicSize == 0) { @@ -78,7 +76,12 @@ bool HATriggers::trigger(const char* type, const char* subtype) } char topic[topicSize]; - generateTopic(topic, HAComponentName, trigger, EventTopic); + generateTopic( + topic, + componentName(), + trigger, + DeviceTypeSerializer::EventTopic + ); if (strlen(topic) == 0) { return false; @@ -110,15 +113,27 @@ void HATriggers::publishConfig() continue; } - const uint16_t& topicLength = calculateTopicLength(HAComponentName, trigger, ConfigTopic); - const uint16_t& dataLength = calculateSerializedLength(trigger, serializedDevice); + const uint16_t& topicLength = calculateTopicLength( + componentName(), + trigger, + DeviceTypeSerializer::ConfigTopic + ); + const uint16_t& dataLength = calculateSerializedLength( + trigger, + serializedDevice + ); if (topicLength == 0 || dataLength == 0) { continue; } char topic[topicLength]; - generateTopic(topic, HAComponentName, trigger, ConfigTopic); + generateTopic( + topic, + componentName(), + trigger, + DeviceTypeSerializer::ConfigTopic + ); if (strlen(topic) == 0) { continue; @@ -139,7 +154,8 @@ uint16_t HATriggers::calculateTopicLength( ) const { uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 1; // + underscore - return BaseDeviceType::calculateTopicLength( + return DeviceTypeSerializer::calculateTopicLength( + mqtt(), component, nullptr, suffix, @@ -162,7 +178,8 @@ uint16_t HATriggers::generateTopic( strcat_P(objectId, Underscore); strcat(objectId, trigger->type); - return BaseDeviceType::generateTopic( + return DeviceTypeSerializer::generateTopic( + mqtt(), output, component, objectId, @@ -179,28 +196,45 @@ uint16_t HATriggers::calculateSerializedLength( return 0; } - const uint16_t& topicLength = calculateTopicLength( - HAComponentName, - trigger, - EventTopic, - false - ); - if (topicLength == 0) { - return 0; + uint16_t size = 0; + size += DeviceTypeSerializer::calculateBaseJsonDataSize(); + size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice); + + // automation type + { + // Format: "atype":"trigger" + size += 17; } - uint16_t size = - 2 + // opening and closing bracket (without null terminator) - 17 + // automation type - topicLength + 7 + // 7 - length of the JSON data for this field - strlen(trigger->type) + 10 + // 10 - length of the JSON data for this field - strlen(trigger->subtype) + 11; // 11 - length of the JSON data for this field + // topic + { + const uint16_t& topicSize = calculateTopicLength( + componentName(), + trigger, + DeviceTypeSerializer::EventTopic, + false + ); + if (topicSize == 0) { + return 0; + } - if (serializedDevice != nullptr) { - size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field + // Format: ,"t":"[TOPIC]" + size += topicSize + 7; // 7 - length of the JSON decorators for this field } - return size; + // type + { + // Format: ,"type":"[TYPE]" + size += strlen(trigger->type) + 10; // 10 - length of the JSON decorators for this field + } + + // subtype + { + // Format: ,"stype":"[SUBTYPE]" + size += strlen(trigger->subtype) + 11; // 11 - length of the JSON decorators for this field; + } + + return size; // exludes null terminator } bool HATriggers::writeSerializedTrigger( @@ -212,74 +246,55 @@ bool HATriggers::writeSerializedTrigger( return false; } - static const char QuotationSign[] PROGMEM = {"\""}; + DeviceTypeSerializer::mqttWriteBeginningJson(mqtt()); // automation type { - static const char Data[] PROGMEM = {"{\"atype\":\"trigger\""}; + static const char Data[] PROGMEM = {"\"atype\":\"trigger\""}; mqtt()->writePayload_P(Data); } // topic { const uint16_t& topicSize = calculateTopicLength( - HAComponentName, + componentName(), trigger, - EventTopic + DeviceTypeSerializer::EventTopic ); if (topicSize == 0) { return false; } - char eventTopic[topicSize]; + char topic[topicSize]; generateTopic( - eventTopic, - HAComponentName, + topic, + componentName(), trigger, - EventTopic + DeviceTypeSerializer::EventTopic ); - if (strlen(eventTopic) == 0) { + if (strlen(topic) == 0) { return false; } - static const char DataBefore[] PROGMEM = {",\"t\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(eventTopic, strlen(eventTopic)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {",\"t\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic); } // type { - static const char DataBefore[] PROGMEM = {",\"type\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(trigger->type, strlen(trigger->type)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {",\"type\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, trigger->type); } // subtype { - static const char DataBefore[] PROGMEM = {",\"stype\":\""}; - - mqtt()->writePayload_P(DataBefore); - mqtt()->writePayload(trigger->subtype, strlen(trigger->subtype)); - mqtt()->writePayload_P(QuotationSign); + static const char Prefix[] PROGMEM = {",\"stype\":\""}; + DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, trigger->subtype); } - // device - { - static const char Data[] PROGMEM = {",\"dev\":"}; - - mqtt()->writePayload_P(Data); - mqtt()->writePayload(serializedDevice, strlen(serializedDevice)); - } - - { - static const char Data[] PROGMEM = {"}"}; - mqtt()->writePayload_P(Data); - } + DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice); + DeviceTypeSerializer::mqttWriteEndJson(mqtt()); return true; } diff --git a/src/device-types/HATriggers.h b/src/device-types/HATriggers.h index 8600f63..90ca8c1 100644 --- a/src/device-types/HATriggers.h +++ b/src/device-types/HATriggers.h @@ -16,6 +16,11 @@ public: virtual void onMqttConnected() override; + /** + * Triggers dont't support availability. Nothing to do here. + */ + virtual void setAvailability(bool online) override { } + bool add(const char* type, const char* subtype); bool trigger(const char* type, const char* subtype);