add support for sensor

This commit is contained in:
Dawid Chyrzynski
2020-12-05 16:16:44 +01:00
parent 3e6076f4da
commit 051d58a8b8
6 changed files with 510 additions and 10 deletions
+19 -9
View File
@@ -2,16 +2,26 @@
## Examples
## Compatible boards
## Tested boards
## Compatible Arduino Shields
* Arduino Uno
* Arduino Mega
* Controllino Maxi (standard/pure/automation/power)
* Controllino Mega (standard/pure)
## Supported types
## Tested Arduino Shields
- Binary sensors
- Device triggers
- Switches
- Sensors
- Tag scanners
* Arduino Ethernet Shield (W5100)
If you need support for non-listed device's type please open a new issue in the Github repository.
## Supported HA types
* Binary sensors
* Device triggers
* Switches
* Sensors
* Tag scanners
## Unsupported features
The library doesn't support all features of the MQTT integration with Home Assistant.
If you need support for a new feature please open a new issue in the repository.
+3
View File
@@ -1,6 +1,9 @@
#include "HADevice.h"
#include "HAMqtt.h"
#include "HAUtils.h"
#include "device-types/HABinarySensor.h"
#include "device-types/HASensor.h"
#include "device-types/HASensor.cpp"
#include "device-types/HASwitch.h"
#include "device-types/HATagScanner.h"
#include "device-types/HATriggers.h"
+30 -1
View File
@@ -40,8 +40,37 @@ char* HAUtils::byteArrayToStr(
const uint16_t& length
)
{
char* dst = malloc((length * 2) + 1); // include null terminator
char* dst = (char*)malloc((length * 2) + 1); // include null terminator
byteArrayToStr(dst, src, length);
return dst;
}
uint8_t HAUtils::getValueTypeLength(const ValueType& type)
{
switch(type) {
case ValueTypeUint8:
return sizeof(uint8_t);
case ValueTypeUint16:
return sizeof(uint16_t);
case ValueTypeUint32:
return sizeof(uint32_t);
case ValueTypeInt8:
return sizeof(int8_t);
case ValueTypeInt16:
return sizeof(int16_t);
case ValueTypeInt32:
return sizeof(int32_t);
case ValueTypeDouble:
return sizeof(double);
case ValueTypeFloat:
return sizeof(float);
}
}
+36
View File
@@ -1,9 +1,23 @@
#ifndef HAUTILS_H
#define HAUTILS_H
#include <Arduino.h>
class HAUtils
{
public:
enum ValueType {
ValueTypeUnknown = -1,
ValueTypeUint8,
ValueTypeUint16,
ValueTypeUint32,
ValueTypeInt8,
ValueTypeInt16,
ValueTypeInt32,
ValueTypeDouble,
ValueTypeFloat
};
static bool endsWith(
const char* str,
const char* suffi
@@ -17,6 +31,28 @@ public:
const byte* src,
const uint16_t& length
);
template <typename A, typename B >
static bool compareType(A a, B b) { return false; }
template <typename A, typename B >
static bool compareType(A a, A b) { return true; }
template <typename T>
static ValueType determineValueType() {
if (compareType<T, uint8_t>(0, (uint8_t)0)) return ValueTypeUint8;
else if (compareType<T, uint16_t>(0, (uint16_t)0)) return ValueTypeUint16;
else if (compareType<T, uint32_t>(0, (uint32_t)0)) return ValueTypeUint32;
else if (compareType<T, int8_t>(0, (int8_t)0)) return ValueTypeInt8;
else if (compareType<T, int16_t>(0, (int16_t)0)) return ValueTypeInt16;
else if (compareType<T, int32_t>(0, (int32_t)0)) return ValueTypeInt32;
else if (compareType<T, double>(0, (double)0)) return ValueTypeDouble;
else if (compareType<T, float>(0, (float)0)) return ValueTypeFloat;
return ValueTypeUnknown;
}
static uint8_t getValueTypeLength(const ValueType& type);
};
#endif
+342
View File
@@ -0,0 +1,342 @@
#ifndef NO_HA_SENSOR
#include "HASensor.h"
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
#include "../HAUtils.h"
// todo: move all variables to progmem
static const char* HAComponentName = "sensor";
static const char* ValueTopic = "value";
template <typename T>
HASensor<T>::HASensor(
const char* name,
T initialValue,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt),
_name(name),
_class(nullptr),
_valueType(HAUtils::determineValueType<T>()),
_currentValue(initialValue)
{
}
template <typename T>
HASensor<T>::HASensor(
const char* name,
const char* deviceClass,
T initialValue,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt),
_name(name),
_class(deviceClass),
_valueType(HAUtils::determineValueType<T>()),
_currentValue(initialValue)
{
}
template <typename T>
HASensor<T>::~HASensor()
{
}
template <typename T>
void HASensor<T>::onMqttConnected()
{
if (strlen(_name) == 0 || _valueType == HAUtils::ValueTypeUnknown) {
return;
}
publishConfig();
publishValue(_currentValue);
}
template <typename T>
bool HASensor<T>::setValue(T value)
{
if (strlen(_name) == 0 ||
_valueType == HAUtils::ValueTypeUnknown) {
return false;
}
if (publishValue(value)) {
_currentValue = value;
return true;
}
return false;
}
template <typename T>
void HASensor<T>::publishConfig()
{
if (_valueType == HAUtils::ValueTypeUnknown) {
return;
}
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();
}
}
template <typename T>
bool HASensor<T>::publishValue(T value)
{
if (strlen(_name) == 0 ||
_valueType == HAUtils::ValueTypeUnknown) {
return false;
}
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
ValueTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
topic,
HAComponentName,
_name,
ValueTopic
);
if (strlen(topic) == 0) {
return false;
}
const uint16_t& valueLength = calculateValueLength();
if (valueLength == 0) {
return false;
}
char valueStr[valueLength];
if (!valueToStr(valueStr, value)) {
return false;
}
return mqtt()->publish(topic, valueStr, true);
}
template <typename T>
uint16_t HASensor<T>::calculateSerializedLength(
const char* serializedDevice
) const
{
if (serializedDevice == nullptr ||
_valueType == HAUtils::ValueTypeUnknown) {
return 0;
}
const uint16_t& valueTopicLength = calculateTopicLength(
HAComponentName,
_name,
ValueTopic,
false
);
if (valueTopicLength == 0) {
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
// 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;
}
template <typename T>
bool HASensor<T>::writeSerializedTrigger(const char* serializedDevice) const
{
if (serializedDevice == nullptr ||
_valueType == HAUtils::ValueTypeUnknown) {
return false;
}
static const char QuotationSign[] PROGMEM = {"\""};
// state topic
{
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
ValueTopic
);
if (topicSize == 0) {
return false;
}
char stateTopic[topicSize];
generateTopic(
stateTopic,
HAComponentName,
_name,
ValueTopic
);
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;
}
template <typename T>
uint16_t HASensor<T>::calculateValueLength() const
{
if (_valueType == HAUtils::ValueTypeUnknown) {
return 0;
}
switch (_valueType) {
case HAUtils::ValueTypeUint8:
case HAUtils::ValueTypeUint16:
case HAUtils::ValueTypeUint32:
case HAUtils::ValueTypeInt8:
case HAUtils::ValueTypeInt16:
case HAUtils::ValueTypeInt32:
return (HAUtils::getValueTypeLength(_valueType) * 3) + 1; // 3 digits per byte + null terminator
case HAUtils::ValueTypeDouble:
case HAUtils::ValueTypeFloat:
return (HAUtils::getValueTypeLength(_valueType) * 3) + 4; // 3 digits per byte + dot separator + 2 precision digits + null terminator
}
return 0;
}
template <typename T>
bool HASensor<T>::valueToStr(char* dst, T value) const
{
if (_valueType == HAUtils::ValueTypeUnknown) {
return false;
}
Serial.println(value);
switch (_valueType) {
case HAUtils::ValueTypeUint8:
case HAUtils::ValueTypeUint16:
case HAUtils::ValueTypeUint32:
case HAUtils::ValueTypeInt8:
case HAUtils::ValueTypeInt16:
case HAUtils::ValueTypeInt32:
sprintf(dst, "%d", value);
break;
case HAUtils::ValueTypeDouble:
dtostrf(value, 0, 2, dst);
break;
}
return true;
}
#endif
+80
View File
@@ -0,0 +1,80 @@
#ifndef HASENSOR_H
#ifndef NO_HA_SENSOR
#define HASENSOR_H
#include "BaseDeviceType.h"
#include "../HAUtils.h"
template <typename T>
class HASensor : public BaseDeviceType
{
public:
/**
* Initializes binary sensor.
*
* @param name Name of the sensor. Recommendes characters: [a-z0-9\-_]
* @param initialValue Initial value of the sensor.
It will be published right after "config" message in order to update HA state.
*/
HASensor(
const char* name,
T initialValue,
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 initialValue Initial value of the sensor.
It will be published right after "config" message in order to update HA state.
*/
HASensor(
const char* name,
const char* deviceClass,
T initialValue,
HAMqtt& mqtt
);
virtual ~HASensor();
/**
* 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 setValue(T value);
/**
* Returns last known state of the sensor.
* If setState method wasn't called the initial value will be returned.
*/
inline T getValue() const
{ return _currentValue; }
private:
void publishConfig();
bool publishValue(T value);
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedTrigger(const char* serializedDevice) const;
uint16_t calculateValueLength() const;
bool valueToStr(char* dst, T value) const;
const char* _name;
const char* _class;
HAUtils::ValueType _valueType;
T _currentValue;
};
#endif
#endif