diff --git a/src/ArduinoHA.h b/src/ArduinoHA.h index 4d37d97..3388355 100644 --- a/src/ArduinoHA.h +++ b/src/ArduinoHA.h @@ -1,5 +1,6 @@ #include "HADevice.h" #include "HAMqtt.h" +#include "device-types/HABinarySensor.h" #include "device-types/HASwitch.h" #include "device-types/HATagScanner.h" #include "device-types/HATriggers.h" diff --git a/src/ArduinoHADefines.h b/src/ArduinoHADefines.h index 5c6955e..2bf933c 100644 --- a/src/ArduinoHADefines.h +++ b/src/ArduinoHADefines.h @@ -6,6 +6,7 @@ // You can easily reduce amount of required resources by removing unused components from the library. // Here is the list of available defines: +// #define NO_HA_BINARY_SENSOR // #define NO_HA_TRIGGERS // #define NO_HA_TAG_SCANNER // #define NO_HA_SWITCH diff --git a/src/HAMqtt.h b/src/HAMqtt.h index cc37605..72c3935 100644 --- a/src/HAMqtt.h +++ b/src/HAMqtt.h @@ -12,7 +12,7 @@ class BaseDeviceType; class HAMqtt { public: - static const uint16_t ReconnectInterval = 3000; // ms + static const uint16_t ReconnectInterval = 5000; // ms HAMqtt(const char* clientId, Client& netClient, HADevice& device); diff --git a/src/device-types/HABinarySensor.cpp b/src/device-types/HABinarySensor.cpp new file mode 100644 index 0000000..67a5298 --- /dev/null +++ b/src/device-types/HABinarySensor.cpp @@ -0,0 +1,270 @@ +#ifndef NO_HA_BINARY_SENSOR + +#include "HABinarySensor.h" +#include "../ArduinoHADefines.h" +#include "../HAMqtt.h" +#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), + _class(nullptr), + _currentState(initialState) +{ + +} + +HABinarySensor::HABinarySensor( + const char* name, + const char* deviceClass, + bool initialState, + HAMqtt& mqtt +) : + BaseDeviceType(mqtt), + _name(name), + _class(deviceClass), + _currentState(initialState) +{ + +} + +HABinarySensor::~HABinarySensor() +{ + +} + +void HABinarySensor::onMqttConnected() +{ + if (strlen(_name) == 0) { + return; + } + + publishConfig(); + publishState(_currentState); +} + +bool HABinarySensor::setState(bool state) +{ + if (state == _currentState) { + return true; + } + + if (strlen(_name) == 0) { + return false; + } + + if (publishState(state)) { + _currentState = state; + return true; + } + + return false; +} + +void HABinarySensor::publishConfig() +{ + const HADevice* device = mqtt()->getDevice(); + if (device == nullptr) { + return; + } + + const uint16_t& deviceLength = device->calculateSerializedLength(); + if (deviceLength == 0) { + return; + } + + char serializedDevice[deviceLength]; + if (device->serialize(serializedDevice) == 0) { + return; + } + + const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic); + const uint16_t& dataLength = calculateSerializedLength(serializedDevice); + + if (topicLength == 0 || dataLength == 0) { + return; + } + + char topic[topicLength]; + generateTopic(topic, HAComponentName, _name, ConfigTopic); + + if (strlen(topic) == 0) { + return; + } + + if (mqtt()->beginPublish(topic, dataLength, true)) { + writeSerializedTrigger(serializedDevice); + mqtt()->endPublish(); + } +} + +bool HABinarySensor::publishState(bool state) +{ + if (strlen(_name) == 0) { + return false; + } + + const uint16_t& topicSize = calculateTopicLength( + HAComponentName, + _name, + StateTopic + ); + if (topicSize == 0) { + return false; + } + + char topic[topicSize]; + generateTopic( + topic, + HAComponentName, + _name, + StateTopic + ); + + if (strlen(topic) == 0) { + return false; + } + + return mqtt()->publish(topic, (state ? StateOn : StateOff), true); +} + +uint16_t HABinarySensor::calculateSerializedLength( + const char* serializedDevice +) const +{ + if (serializedDevice == nullptr) { + return 0; + } + + const uint16_t& stateTopicLength = calculateTopicLength( + HAComponentName, + _name, + StateTopic, + false + ); + + if (stateTopicLength == 0) { + 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 + + // unique ID + size += strlen(mqtt()->getDevice()->getUniqueId()); + size += strlen(_name) + 14; // 14 - length of the JSON data for this field + + // device class + if (_class != nullptr) { + size += strlen(_class) + 13; // 13 - length of the JSON data for this field + } + + // device + size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field + + return size; +} + +bool HABinarySensor::writeSerializedTrigger(const char* serializedDevice) const +{ + if (serializedDevice == nullptr) { + return false; + } + + static const char QuotationSign[] PROGMEM = {"\""}; + + // state topic + { + const uint16_t& topicSize = calculateTopicLength( + HAComponentName, + _name, + StateTopic + ); + if (topicSize == 0) { + return false; + } + + char stateTopic[topicSize]; + generateTopic( + stateTopic, + HAComponentName, + _name, + StateTopic + ); + + if (strlen(stateTopic) == 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)); + } + + // 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 Data[] PROGMEM = {"}"}; + mqtt()->writePayload_P(Data); + } + + return true; +} + +#endif diff --git a/src/device-types/HABinarySensor.h b/src/device-types/HABinarySensor.h new file mode 100644 index 0000000..f3d5eb6 --- /dev/null +++ b/src/device-types/HABinarySensor.h @@ -0,0 +1,75 @@ +#ifndef HABINARYSENSOR_H +#ifndef NO_HA_BINARY_SENSOR +#define HABINARYSENSOR_H + +#include "BaseDeviceType.h" + +class HABinarySensor : public BaseDeviceType +{ +public: + /** + * Initializes binary sensor. + * + * @param name Name of the sensor. Recommendes characters: [a-z0-9\-_] + * @param initialState Initial state of the sensor. + It will be published right after "config" message in order to update HA state. + */ + HABinarySensor( + const char* name, + bool initialState, + HAMqtt& mqtt + ); + + /** + * Initializes binary sensor with the specified class. + * You can find list of available values here: https://www.home-assistant.io/integrations/binary_sensor/#device-class + * + * @param name Name of the sensor. Recommendes characters: [a-z0-9\-_] + * @param deviceClass Name of the class (lower case). + * @param initialState Initial state of the sensor. + It will be published right after "config" message in order to update HA state. + */ + HABinarySensor( + const char* name, + const char* deviceClass, + bool initialState, + HAMqtt& mqtt + ); + + virtual ~HABinarySensor(); + + /** + * Publishes configuration of the sensor to the MQTT. + */ + virtual void onMqttConnected() override; + + /** + * Changes state of the sensor and publishes MQTT message. + * Please note that if a new value is the same as previous one, + * the MQTT message won't be published. + * + * @param state New state of the sensor. + * @returns Returns true if MQTT message has been published successfully. + */ + bool setState(bool state); + + /** + * Returns last known state of the sensor. + * If setState method wasn't called the initial value will be returned. + */ + inline bool getState() const + { return _currentState; } + +private: + void publishConfig(); + bool publishState(bool state); + uint16_t calculateSerializedLength(const char* serializedDevice) const; + bool writeSerializedTrigger(const char* serializedDevice) const; + + const char* _name; + const char* _class; + bool _currentState; +}; + +#endif +#endif diff --git a/src/device-types/HASwitch.h b/src/device-types/HASwitch.h index fd683a4..e71355f 100644 --- a/src/device-types/HASwitch.h +++ b/src/device-types/HASwitch.h @@ -66,6 +66,13 @@ public: inline bool turnOff() { return setState(false); } + /** + * Returns last known state of the switch. + * If setState method wasn't called the initial value will be returned. + */ + inline bool getState() const + { return _currentState; } + /** * Registers callback that will be called each time the value of the switch changes. * Please note that it's not possible to register multiple callbacks for the same switch.