mirror of
https://github.com/mrcory/arduino-home-assistant.git
synced 2026-08-02 15:24:26 -04:00
add support for sensor
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user