Import ESP_UniClock (upstream 1189810)

Vendored from https://github.com/gpeter62/ESP_UniClock: the sketch
source, the web UI and the libraries the ESP8266 build needs, unpacked
from the bundled zips. Adds a PlatformIO project for the ESP-12. The
source is unchanged; local changes follow as separate commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-25 20:48:04 +01:00
1 parent 8efa810e4e
commit b347f1c63f
843 files changed
+122061

No files matched your search

@@ -0,0 +1,16 @@
#ifndef AHA_ARDUINOHA_H
#define AHA_ARDUINOHA_H
#include "HADevice.h"
#include "HAMqtt.h"
#include "HAUtils.h"
#include "device-types/HABinarySensor.h"
#include "device-types/HACover.h"
#include "device-types/HAFan.h"
#include "device-types/HAHVAC.h"
#include "device-types/HASensor.h"
#include "device-types/HASwitch.h"
#include "device-types/HATagScanner.h"
#include "device-types/HATriggers.h"
#endif
@@ -0,0 +1,23 @@
// Turns on debug information of the ArduinoHA core.
// Please note that you need to initialize serial interface manually
// by calling Serial.begin([baudRate]) before initializing ArduinoHA.
// #define ARDUINOHA_DEBUG
// You can reduce Flash size of the compiled library by commenting unused components below
#define ARDUINOHA_BINARY_SENSOR
#define ARDUINOHA_COVER
#define ARDUINOHA_FAN
#define ARDUINOHA_HVAC
#define ARDUINOHA_SENSOR
#define ARDUINOHA_SWITCH
#define ARDUINOHA_TAG_SCANNER
#define ARDUINOHA_TRIGGERS
#ifdef __GNUC__
#define AHA_DEPRECATED(func) func __attribute__ ((deprecated))
#elif defined(_MSC_VER)
#define AHA_DEPRECATED(func) __declspec(deprecated) func
#else
#warning "Arduino Home Assistant: You may miss deprecation warnings."
#define AHA_DEPRECATED(func) func
#endif
@@ -0,0 +1,198 @@
#include <Arduino.h>
#include "ArduinoHADefines.h"
#include "HADevice.h"
#include "HAUtils.h"
#include "HAMqtt.h"
#include "device-types/DeviceTypeSerializer.h"
#define HADEVICE_INIT \
_manufacturer(nullptr), \
_model(nullptr), \
_name(nullptr), \
_softwareVersion(nullptr), \
_sharedAvailability(false), \
_availabilityTopic(nullptr), \
_available(true) // device will be available by default
HADevice::HADevice() :
_uniqueId(nullptr),
HADEVICE_INIT
{
}
HADevice::HADevice(const char* uniqueId) :
_uniqueId(uniqueId),
HADEVICE_INIT
{
}
HADevice::HADevice(const byte* uniqueId, const uint16_t& length) :
_uniqueId(HAUtils::byteArrayToStr(uniqueId, length)),
HADEVICE_INIT
{
}
void HADevice::setAvailability(bool online)
{
_available = online;
publishAvailability();
}
bool HADevice::enableSharedAvailability()
{
if (_sharedAvailability) {
return false;
}
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Enabling shared availability in the device"));
#endif
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
nullptr,
nullptr,
DeviceTypeSerializer::AvailabilityTopic
);
if (topicSize == 0) {
return false;
}
_availabilityTopic = (char*)malloc(topicSize);
_sharedAvailability = true;
DeviceTypeSerializer::generateTopic(
_availabilityTopic,
nullptr,
nullptr,
DeviceTypeSerializer::AvailabilityTopic
);
return true;
}
bool HADevice::enableLastWill()
{
HAMqtt* mqtt = HAMqtt::instance();
if (mqtt == nullptr || _availabilityTopic == nullptr) {
return false;
}
mqtt->setLastWill(
_availabilityTopic,
DeviceTypeSerializer::Offline,
false
);
return true;
}
bool HADevice::setUniqueId(const byte* uniqueId, const uint16_t& length)
{
if (_uniqueId != nullptr) {
return false;
}
_uniqueId = HAUtils::byteArrayToStr(uniqueId, length);
return true;
}
void HADevice::publishAvailability()
{
if (!_sharedAvailability) {
return;
}
HAMqtt* mqtt = HAMqtt::instance();
if (mqtt == nullptr) {
return;
}
mqtt->publish(
_availabilityTopic,
(
_available ?
DeviceTypeSerializer::Online :
DeviceTypeSerializer::Offline
),
true
);
}
uint16_t HADevice::calculateSerializedLength() const
{
uint16_t size =
3 + // opening and closing bracket + null terminator
strlen(_uniqueId) + 8; // 8 - length of the JSON data for this field
if (_manufacturer != nullptr) {
size += strlen(_manufacturer) + 8; // 8 - length of the JSON data for this field
}
if (_model != nullptr) {
size += strlen(_model) + 9; // 9 - length of the JSON data for this field
}
if (_name != nullptr) {
size += strlen(_name) + 10; // 10 - length of the JSON data for this field
}
if (_softwareVersion != nullptr) {
size += strlen(_softwareVersion) + 8; // 8 - length of the JSON data for this field
}
return size;
}
uint16_t HADevice::serialize(char* dst) const
{
static const char QuotationSign[] PROGMEM = {"\""};
{
static const char DataBefore[] PROGMEM = {"{\"ids\":\""};
strcpy_P(dst, DataBefore);
strcat(dst, _uniqueId);
strcat_P(dst, QuotationSign);
}
if (_manufacturer != nullptr) {
static const char DataBefore[] PROGMEM = {",\"mf\":\""};
strcat_P(dst, DataBefore);
strcat(dst, _manufacturer);
strcat_P(dst, QuotationSign);
}
if (_model != nullptr) {
static const char DataBefore[] PROGMEM = {",\"mdl\":\""};
strcat_P(dst, DataBefore);
strcat(dst, _model);
strcat_P(dst, QuotationSign);
}
if (_name != nullptr) {
static const char DataBefore[] PROGMEM = {",\"name\":\""};
strcat_P(dst, DataBefore);
strcat(dst, _name);
strcat_P(dst, QuotationSign);
}
if (_softwareVersion != nullptr) {
static const char DataBefore[] PROGMEM = {",\"sw\":\""};
strcat_P(dst, DataBefore);
strcat(dst, _softwareVersion);
strcat_P(dst, QuotationSign);
}
{
static const char Data[] PROGMEM = {"}"};
strcat_P(dst, Data);
}
return strlen(dst) + 1; // size with null terminator
}
@@ -0,0 +1,53 @@
#ifndef AHA_HADEVICE_H
#define AHA_HADEVICE_H
#include <Arduino.h>
class HADevice
{
public:
HADevice();
HADevice(const char* uniqueId);
HADevice(const byte* uniqueId, const uint16_t& length);
inline const char* getUniqueId() const
{ return _uniqueId; }
inline void setManufacturer(const char* manufacturer)
{ _manufacturer = manufacturer; }
inline void setModel(const char* model)
{ _model = model; }
inline void setName(const char* name)
{ _name = name; }
inline void setSoftwareVersion(const char* softwareVersion)
{ _softwareVersion = softwareVersion; }
inline bool isSharedAvailabilityEnabled() const
{ return _sharedAvailability; }
inline bool isOnline() const
{ return _available; }
void setAvailability(bool online);
bool enableSharedAvailability();
bool enableLastWill();
bool setUniqueId(const byte* uniqueId, const uint16_t& length);
void publishAvailability();
uint16_t calculateSerializedLength() const;
uint16_t serialize(char* dst) const;
private:
const char* _uniqueId;
const char* _manufacturer;
const char* _model;
const char* _name;
const char* _softwareVersion;
bool _sharedAvailability;
char* _availabilityTopic;
bool _available;
};
#endif
@@ -0,0 +1,331 @@
#include <PubSubClient.h>
#include "HAMqtt.h"
#include "HADevice.h"
#include "ArduinoHADefines.h"
#include "device-types/BaseDeviceType.h"
static const char* DefaultDiscoveryPrefix = "esp";
HAMqtt* HAMqtt::_instance = nullptr;
void onMessageReceived(char* topic, uint8_t* payload, unsigned int length)
{
if (HAMqtt::instance() == nullptr || length > UINT16_MAX) {
return;
}
HAMqtt::instance()->processMessage(topic, payload, static_cast<uint16_t>(length));
}
HAMqtt::HAMqtt(Client& netClient, HADevice& device) :
_netClient(netClient),
_device(device),
_messageCallback(nullptr),
_connectedCallback(nullptr),
_connectionFailedCallback(nullptr),
_initialized(false),
_discoveryPrefix(DefaultDiscoveryPrefix),
_dataPrefix(nullptr),
_mqtt(new PubSubClient(netClient)),
_username(nullptr),
_password(nullptr),
_lastConnectionAttemptAt(0),
_devicesTypesNb(0),
_devicesTypes(nullptr),
_lastWillTopic(nullptr),
_lastWillMessage(nullptr),
_lastWillRetain(false)
{
_instance = this;
}
bool HAMqtt::begin(
const IPAddress& serverIp,
const uint16_t& serverPort,
const char* username,
const char* password
)
{
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Initializing ArduinoHA"));
Serial.print(F("Server address: "));
Serial.print(serverIp);
Serial.print(F(":"));
Serial.print(serverPort);
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"));
#endif
return false;
}
_username = username;
_password = password;
_initialized = true;
_mqtt->setServer(serverIp, serverPort);
_mqtt->setCallback(onMessageReceived);
return true;
}
bool HAMqtt::begin(
const IPAddress& serverIp,
const char* username,
const char* password
)
{
return begin(serverIp, HAMQTT_DEFAULT_PORT, username, password);
}
bool HAMqtt::begin(
const char* hostname,
const uint16_t& serverPort,
const char* username,
const char* password
)
{
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Initializing ArduinoHA"));
Serial.print(F("Server address: "));
Serial.print(hostname);
Serial.print(F(":"));
Serial.print(serverPort);
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"));
#endif
return false;
}
_username = username;
_password = password;
_initialized = true;
_mqtt->setServer(hostname, serverPort);
_mqtt->setCallback(onMessageReceived);
return true;
}
bool HAMqtt::begin(
const char* hostname,
const char* username,
const char* password
)
{
return begin(hostname, HAMQTT_DEFAULT_PORT, username, password);
}
bool HAMqtt::disconnect(bool sendLastWill)
{
if (!_initialized) {
return false;
}
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Closing connection with MQTT broker"));
#endif
if (sendLastWill &&
_lastWillTopic != nullptr &&
_lastWillMessage != nullptr) {
publish(_lastWillTopic, _lastWillMessage, _lastWillRetain);
}
_initialized = false;
_lastConnectionAttemptAt = 0;
_mqtt->disconnect();
return true;
}
void HAMqtt::loop()
{
if (_initialized && !_mqtt->loop()) {
connectToServer();
}
}
bool HAMqtt::isConnected()
{
return _mqtt->connected();
}
void HAMqtt::addDeviceType(BaseDeviceType* deviceType)
{
BaseDeviceType** data = (BaseDeviceType**)realloc(
_devicesTypes,
sizeof(BaseDeviceType*) * (_devicesTypesNb + 1)
);
if (data != nullptr) {
_devicesTypes = data;
_devicesTypes[_devicesTypesNb] = deviceType;
_devicesTypesNb++;
}
}
bool HAMqtt::publish(const char* topic, const char* payload, bool retained)
{
if (!isConnected()) {
return false;
}
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Publishing: "));
Serial.print(topic);
Serial.print(F(", len: "));
Serial.print(strlen(payload));
Serial.println();
#endif
_mqtt->beginPublish(topic, strlen(payload), retained);
_mqtt->write((const uint8_t*)(payload), strlen(payload));
return _mqtt->endPublish();
}
bool HAMqtt::beginPublish(
const char* topic,
uint16_t payloadLength,
bool retained
)
{
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Publishing: "));
Serial.print(topic);
Serial.print(F(", len: "));
Serial.print(payloadLength);
Serial.println();
#endif
return _mqtt->beginPublish(topic, payloadLength, retained);
}
bool HAMqtt::writePayload(const char* data, uint16_t length)
{
return (_mqtt->write((const uint8_t*)(data), length) > 0);
}
bool HAMqtt::writePayload_P(const char* src)
{
char data[strlen_P(src) + 1];
strcpy_P(data, src);
return _mqtt->write((const uint8_t*)(data), strlen(data));
}
bool HAMqtt::endPublish()
{
return _mqtt->endPublish();
}
bool HAMqtt::subscribe(const char* topic)
{
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Subscribing topic: "));
Serial.print(topic);
Serial.println();
#endif
return _mqtt->subscribe(topic);
}
void HAMqtt::processMessage(char* topic, uint8_t* payload, uint16_t length)
{
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Received message on topic: "));
Serial.print(topic);
Serial.print(F(", payload length: "));
Serial.print(length);
Serial.println();
#endif
if (_messageCallback) {
_messageCallback(topic, payload, length);
}
for (uint8_t i = 0; i < _devicesTypesNb; i++) {
_devicesTypes[i]->onMqttMessage(topic, payload, length);
}
}
void HAMqtt::connectToServer()
{
if (_lastConnectionAttemptAt > 0 &&
(millis() - _lastConnectionAttemptAt) < ReconnectInterval) {
return;
}
_lastConnectionAttemptAt = millis();
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Connecting to the MQTT broker... Client ID: "));
Serial.print(_device.getUniqueId());
Serial.println();
#endif
_mqtt->connect(
_device.getUniqueId(),
_username,
_password,
_lastWillTopic,
0,
_lastWillRetain,
_lastWillMessage,
true
);
if (isConnected()) {
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Connected to the broker"));
#endif
onConnectedLogic();
} else {
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Failed to connect to the broker"));
if (_connectionFailedCallback) {
_connectionFailedCallback();
}
#endif
}
}
void HAMqtt::onConnectedLogic()
{
if (_connectedCallback) {
_connectedCallback();
}
_device.publishAvailability();
for (uint8_t i = 0; i < _devicesTypesNb; i++) {
_devicesTypes[i]->onMqttConnected();
}
}
@@ -0,0 +1,243 @@
#ifndef AHA_HAMQTT_H
#define AHA_HAMQTT_H
#include <Client.h>
#include <IPAddress.h>
#define HAMQTT_CALLBACK(name) void (*name)()
#define HAMQTT_MESSAGE_CALLBACK(name) void (*name)(const char* topic, const uint8_t* payload, uint16_t length)
#define HAMQTT_DEFAULT_PORT 1883
class PubSubClient;
class HADevice;
class BaseDeviceType;
class HAMqtt
{
public:
static const uint16_t ReconnectInterval = 5000; // ms
inline static HAMqtt* instance()
{ return _instance; }
HAMqtt(Client& netClient, HADevice& device);
/**
* Sets prefix for Home Assistant discovery.
* It needs to match prefix set in the HA admin panel.
* The default prefix is "homeassistant".
*
* @param prefix
*/
inline void setDiscoveryPrefix(const char* prefix)
{ _discoveryPrefix = prefix; }
/**
* Returns discovery prefix.
*/
inline const char* getDiscoveryPrefix() const
{ return _discoveryPrefix; }
/**
* Sets prefix that will be used for topics different than discovery.
* It may be useful if you want to pass MQTT trafic through bridge.
*
* @param prefix
*/
inline void setDataPrefix(const char* prefix)
{ _dataPrefix = prefix; }
/**
* Returns data prefix.
*/
inline const char* getDataPrefix() const
{ return _dataPrefix; }
/**
* Returns instance of the device assigned to the HAMqtt class.
*/
inline HADevice const* getDevice() const
{ return &_device; }
/**
* Given callback will be called for each received message from the broker.
*
* @param callback
*/
inline void onMessage(HAMQTT_MESSAGE_CALLBACK(callback))
{ _messageCallback = callback; }
/**
* Given callback will be called each time the connection with broker is acquired.
*
* @param callback
*/
inline void onConnected(HAMQTT_CALLBACK(callback))
{ _connectedCallback = callback; }
/**
* Given callback will be called each time the library fails to connect to the broker.
*
* @param callback
*/
inline void onConnectionFailed(HAMQTT_CALLBACK(callback))
{ _connectionFailedCallback = callback; }
/**
* Sets parameters of the connection to the MQTT broker.
* 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 uint16_t& serverPort = HAMQTT_DEFAULT_PORT,
const char* username = nullptr,
const char* password = nullptr
);
bool begin(
const IPAddress& serverIp,
const char* username,
const char* password
);
/**
* Connects to the MQTT broker using hostname.
*
* @param hostname Hostname of the MQTT broker.
* @param serverPort Port of the MQTT broker.
* @param username Username for authentication.
* @param password Password for authentication.
*/
bool begin(
const char* hostname,
const uint16_t& serverPort = HAMQTT_DEFAULT_PORT,
const char* username = nullptr,
const char* password = nullptr
);
bool begin(
const char* hostname,
const char* username,
const char* password
);
/**
* Closes connection with the MQTT broker.
*
* @param sendLastWill Set to true if you want to publish device unavailability before closing the connection.
*/
bool disconnect(bool sendLastWill = true);
/**
* ArduinoHA's ticker.
*/
void loop();
/**
* Returns true if connection to the MQTT broker is established.
*/
bool isConnected();
/**
* Adds a new device's type to the MQTT.
* Each time the connection with MQTT broker is acquired, the HAMqtt class
* calls "onMqttConnected" method in all devices' types instances.
*
* @param deviceType Instance of the device's type (eg. HATriggers).
*/
void addDeviceType(BaseDeviceType* deviceType);
/**
* Publishes MQTT message with given topic and payload.
* Message won't be published if connection with MQTT broker is not established.
* In this case method returns false.
*
* @param topic Topic to publish.
* @param payload Payload to publish (it may be empty const char).
* @param retained Determines whether message should be retained.
*/
bool publish(const char* topic, const char* payload, bool retained = false);
bool beginPublish(const char* topic, uint16_t payloadLength, bool retained = false);
bool writePayload(const char* data, uint16_t length);
bool writePayload_P(const char* src);
bool endPublish();
/**
* Subscribes to the given topic.
* Whenever a new message is received the onMqttMessage callback in all
* devices types is called.
*
* Please note that you need to subscribe topic each time the connection
* with the broker is acquired.
*
* @param topic Topic to subscribe
*/
bool subscribe(const char* topic);
/**
* Enables last will message that will be produced when device disconnects from the broker.
* If you want to change availability of the device in Home Assistant panel
* please use enableLastWill() method in the HADevice object instead.
*
* @param lastWillTopic
* @param lastWillMessage
* @param lastWillRetain
*/
inline void setLastWill(
const char* lastWillTopic,
const char* lastWillMessage,
bool lastWillRetain
) {
_lastWillTopic = lastWillTopic;
_lastWillMessage = lastWillMessage;
_lastWillRetain = lastWillRetain;
}
/**
* Processes MQTT message received from the broker (subscription).
*
* @param topic Topic of the message.
* @param payload Content of the message.
* @param length Length of the message.
*/
void processMessage(char* topic, uint8_t* payload, uint16_t length);
private:
static HAMqtt* _instance;
/**
* Attempts to connect to the MQTT broker.
* The method uses properties passed to the "begin" method.
*/
void connectToServer();
/**
* This method is called each time the connection with MQTT broker is acquired.
*/
void onConnectedLogic();
Client& _netClient;
HADevice& _device;
HAMQTT_MESSAGE_CALLBACK(_messageCallback);
HAMQTT_CALLBACK(_connectedCallback);
HAMQTT_CALLBACK(_connectionFailedCallback);
bool _initialized;
const char* _discoveryPrefix;
const char* _dataPrefix;
PubSubClient* _mqtt;
const char* _username;
const char* _password;
uint32_t _lastConnectionAttemptAt;
uint8_t _devicesTypesNb;
BaseDeviceType** _devicesTypes;
const char* _lastWillTopic;
const char* _lastWillMessage;
bool _lastWillRetain;
};
#endif
@@ -0,0 +1,67 @@
#ifdef ARDUINO_ARCH_SAMD
#include <avr/dtostrf.h>
#endif
#include <Arduino.h>
#include "HAUtils.h"
bool HAUtils::endsWith(const char* str, const char* suffix)
{
if (str == nullptr || suffix == nullptr) {
return false;
}
const uint16_t& lenstr = strlen(str);
const uint16_t& lensuffix = strlen(suffix);
if (lensuffix > lenstr) {
return false;
}
return (strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0);
}
void HAUtils::byteArrayToStr(
char* dst,
const byte* src,
const uint16_t& length
)
{
const uint16_t& finalLength = (length * 2);
static const char map[] PROGMEM = {"0123456789abcdef"};
for (uint8_t i = 0; i < length; i++) {
dst[i*2] = pgm_read_byte(&map[((char)src[i] & 0XF0) >> 4]);
dst[i*2+1] = pgm_read_byte(&map[((char)src[i] & 0x0F)]);
}
dst[finalLength] = '\0';
}
char* HAUtils::byteArrayToStr(
const byte* src,
const uint16_t& length
)
{
char* dst = (char*)malloc((length * 2) + 1); // include null terminator
byteArrayToStr(dst, src, length);
return dst;
}
void HAUtils::tempToStr(
char* dst,
const double& temp
)
{
memset(dst, 0, sizeof(AHA_SERIALIZED_TEMP_SIZE));
dtostrf(temp, 0, 2, dst);
}
double HAUtils::strToTemp(
const char* src
)
{
return atof(src);
}
@@ -0,0 +1,33 @@
#ifndef AHA_HAUTILS_H
#define AHA_HAUTILS_H
#include <Arduino.h>
#define AHA_SERIALIZED_TEMP_SIZE 8
class HAUtils
{
public:
static bool endsWith(
const char* str,
const char* suffix
);
static void byteArrayToStr(
char* dst,
const byte* src,
const uint16_t& length
);
static char* byteArrayToStr(
const byte* src,
const uint16_t& length
);
static void tempToStr(
char* dst,
const double& temp
);
static double strToTemp(
const char* src
);
};
#endif
@@ -0,0 +1,163 @@
#include "BaseDeviceType.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
#include "../HAUtils.h"
BaseDeviceType::BaseDeviceType(
const char* componentName,
const char* uniqueId
) :
_componentName(componentName),
_uniqueId(uniqueId),
_availability(AvailabilityDefault),
_name(nullptr)
{
mqtt()->addDeviceType(this);
}
BaseDeviceType::~BaseDeviceType()
{
}
void BaseDeviceType::setAvailability(bool online)
{
_availability = (online ? AvailabilityOnline : AvailabilityOffline);
publishAvailability();
}
HAMqtt* BaseDeviceType::mqtt() const
{
return HAMqtt::instance();
}
void BaseDeviceType::onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
)
{
(void)topic;
(void)payload;
(void)length;
}
void BaseDeviceType::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 = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::ConfigTopic,
true,
true
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
return;
}
char topic[topicLength];
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
uniqueId(),
DeviceTypeSerializer::ConfigTopic,
true
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
void BaseDeviceType::publishAvailability()
{
const HADevice* device = HAMqtt::instance()->getDevice();
if (device == nullptr) {
return;
}
if (_availability == AvailabilityDefault ||
!mqtt()->isConnected() ||
strlen(uniqueId()) == 0 ||
strlen(componentName()) == 0 ||
device->isSharedAvailabilityEnabled()) {
return;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::AvailabilityTopic
);
if (topicSize == 0) {
return;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
uniqueId(),
DeviceTypeSerializer::AvailabilityTopic
);
if (strlen(topic) == 0) {
return;
}
mqtt()->publish(
topic,
(
_availability == AvailabilityOnline ?
DeviceTypeSerializer::Online :
DeviceTypeSerializer::Offline
),
true
);
}
bool BaseDeviceType::compareTopics(const char* topic, const char* expectedTopic)
{
if (topic == nullptr || expectedTopic == nullptr) {
return false;
}
if (strlen(uniqueId()) == 0) {
return false;
}
static const char Slash[] PROGMEM = {"/"};
// unique ID + cmd topic + two slashes + null terminator
uint8_t suffixLength = strlen(uniqueId()) + strlen(expectedTopic) + 3;
char suffix[suffixLength];
strcpy_P(suffix, Slash);
strcat(suffix, uniqueId());
strcat_P(suffix, Slash);
strcat(suffix, expectedTopic);
return HAUtils::endsWith(topic, suffix);
}
@@ -0,0 +1,72 @@
#ifndef AHA_BASEDEVICETYPE_H
#define AHA_BASEDEVICETYPE_H
#include <stdint.h>
#include "../ArduinoHADefines.h"
#include "DeviceTypeSerializer.h"
class HAMqtt;
class BaseDeviceType
{
public:
BaseDeviceType(
const char* componentName,
const char* uniqueId
);
virtual ~BaseDeviceType();
inline const char* uniqueId() const
{ return _uniqueId; }
inline const char* componentName() const
{ return _componentName; }
inline bool isAvailabilityConfigured() const
{ return (_availability != AvailabilityDefault); }
inline bool isOnline() const
{ return (_availability == AvailabilityOnline); }
inline void setName(const char* name)
{ _name = name; }
inline const char* getName() const
{ return _name; }
virtual void setAvailability(bool online);
protected:
HAMqtt* mqtt() const;
virtual void onMqttConnected() = 0;
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
);
virtual void publishConfig();
virtual void publishAvailability();
virtual bool compareTopics(const char* topic, const char* expectedTopic);
virtual uint16_t calculateSerializedLength(const char* serializedDevice) const = 0;
virtual bool writeSerializedData(const char* serializedDevice) const = 0;
const char* const _componentName;
const char* const _uniqueId;
private:
enum Availability {
AvailabilityDefault = 0,
AvailabilityOnline,
AvailabilityOffline
};
Availability _availability;
const char* _name;
friend class HAMqtt;
};
#endif
@@ -0,0 +1,421 @@
#include <Arduino.h>
#include "DeviceTypeSerializer.h"
#include "BaseDeviceType.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";
const char* DeviceTypeSerializer::getTopicPrefix(bool isDiscoveryTopic)
{
if (!isDiscoveryTopic) {
const char* dataPrefix = HAMqtt::instance()->getDataPrefix();
if (dataPrefix != nullptr) {
return dataPrefix;
}
}
return HAMqtt::instance()->getDiscoveryPrefix();
}
uint16_t DeviceTypeSerializer::calculateTopicLength(
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator,
bool isDiscoveryTopic
)
{
const char* prefix = getTopicPrefix(isDiscoveryTopic);
if (prefix == nullptr) {
return 0;
}
uint16_t size =
strlen(prefix) + 1 + // prefix with slash
strlen(suffix);
if (component != nullptr) {
size += strlen(component) + 1; // component with slash
}
if (HAMqtt::instance()->getDevice() != nullptr) {
size += strlen(HAMqtt::instance()->getDevice()->getUniqueId()) + 1; // device ID with slash
}
if (objectId != nullptr) {
size += strlen(objectId) + 1; // with slash
}
if (includeNullTerminator) {
size += 1;
}
return size;
}
uint16_t DeviceTypeSerializer::generateTopic(
char* output,
const char* component,
const char* objectId,
const char* suffix,
bool isDiscoveryTopic
)
{
const char* prefix = getTopicPrefix(isDiscoveryTopic);
if (prefix == nullptr) {
return 0;
}
strcpy(output, prefix);
strcat_P(output, CharSlash);
if (component != nullptr) {
strcat(output, component);
strcat_P(output, CharSlash);
}
if (HAMqtt::instance()->getDevice() != nullptr) {
strcat(output, HAMqtt::instance()->getDevice()->getUniqueId());
strcat_P(output, CharSlash);
}
if (objectId != nullptr) {
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 char* uniqueId
)
{
HADevice const* device = HAMqtt::instance()->getDevice();
if (device == nullptr || uniqueId == nullptr) {
return 0;
}
// Field format: ,"uniq_id":"[DEVICE ID]_[UNIQUE ID]"
return (
strlen(device->getUniqueId()) +
strlen(uniqueId) +
14 // 14 - length of the JSON decorators for this field
);
}
uint16_t DeviceTypeSerializer::calculateAvailabilityFieldSize(
const BaseDeviceType* const dt
)
{
const HADevice* device = HAMqtt::instance()->getDevice();
if (device == nullptr) {
return 0;
}
const bool& sharedAvailability = device->isSharedAvailabilityEnabled();
if (!sharedAvailability && !dt->isAvailabilityConfigured()) {
return 0;
}
const uint16_t& availabilityTopicLength = calculateTopicLength(
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->uniqueId()),
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::calculateRetainFieldSize(bool retain)
{
if (!retain) {
return 0;
}
// Field format: ,"ret":true
return 11;
}
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()
{
static const char Data[] PROGMEM = {"{"};
HAMqtt::instance()->writePayload_P(Data);
}
void DeviceTypeSerializer::mqttWriteEndJson()
{
static const char Data[] PROGMEM = {"}"};
HAMqtt::instance()->writePayload_P(Data);
}
void DeviceTypeSerializer::mqttWriteConstCharField(
const char* prefix,
const char* value,
bool quoteSuffix
)
{
if (prefix == nullptr || value == nullptr) {
return;
}
HAMqtt::instance()->writePayload_P(prefix);
HAMqtt::instance()->writePayload(value, strlen(value));
if (quoteSuffix) {
HAMqtt::instance()->writePayload_P(CharQuotation);
}
}
void DeviceTypeSerializer::mqttWriteNameField(const char* name)
{
if (name == nullptr) {
return;
}
static const char Prefix[] PROGMEM = {",\"name\":\""};
mqttWriteConstCharField(Prefix, name);
}
void DeviceTypeSerializer::mqttWriteUniqueIdField(
const char* uniqueId
)
{
if (uniqueId == nullptr) {
return;
}
HADevice const* device = HAMqtt::instance()->getDevice();
if (device == nullptr) {
return;
}
static const char Prefix[] PROGMEM = {",\"uniq_id\":\""};
uint8_t uniqueIdLength = strlen(uniqueId) + strlen(device->getUniqueId()) + 2; // underscore + null temrinator
char finalUniqueId[uniqueIdLength];
strcpy(finalUniqueId, uniqueId);
strcat_P(finalUniqueId, CharUnderscore);
strcat(finalUniqueId, device->getUniqueId());
mqttWriteConstCharField(Prefix, finalUniqueId);
}
void DeviceTypeSerializer::mqttWriteAvailabilityField(
const BaseDeviceType* const dt
)
{
const HADevice* device = HAMqtt::instance()->getDevice();
if (device == nullptr) {
return;
}
const bool& sharedAvailability = device->isSharedAvailabilityEnabled();
if (!sharedAvailability && !dt->isAvailabilityConfigured()) {
return;
}
const uint16_t& topicSize = calculateTopicLength(
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->uniqueId()),
AvailabilityTopic
);
if (topicSize == 0) {
return;
}
char availabilityTopic[topicSize];
generateTopic(
availabilityTopic,
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->uniqueId()),
AvailabilityTopic
);
if (strlen(availabilityTopic) == 0) {
return;
}
static const char Prefix[] PROGMEM = {",\"avty_t\":\""};
mqttWriteConstCharField(Prefix, availabilityTopic);
}
void DeviceTypeSerializer::mqttWriteRetainField(
bool retain
)
{
if (!retain) {
return;
}
static const char Prefix[] PROGMEM = {",\"ret\":"};
mqttWriteConstCharField(
Prefix,
"true",
false
);
}
void DeviceTypeSerializer::mqttWriteDeviceField(
const char* serializedDevice
)
{
if (serializedDevice == nullptr) {
return;
}
static const char Data[] PROGMEM = {",\"dev\":"};
HAMqtt::instance()->writePayload_P(Data);
HAMqtt::instance()->writePayload(serializedDevice, strlen(serializedDevice));
}
bool DeviceTypeSerializer::mqttWriteTopicField(
const BaseDeviceType* const dt,
const char* jsonPrefix,
const char* topicSuffix
)
{
if (jsonPrefix == nullptr || topicSuffix == nullptr) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
dt->componentName(),
dt->uniqueId() ,
topicSuffix
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
topic,
dt->componentName(),
dt->uniqueId(),
topicSuffix
);
if (strlen(topic) == 0) {
return false;
}
DeviceTypeSerializer::mqttWriteConstCharField(jsonPrefix, topic);
return true;
}
bool DeviceTypeSerializer::mqttPublishMessage(
const BaseDeviceType* const dt,
const char* topicSuffix,
const char* data
)
{
if (topicSuffix == nullptr || data == nullptr) {
return false;
}
const uint16_t& topicSize = calculateTopicLength(
dt->componentName(),
dt->uniqueId(),
topicSuffix
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
topic,
dt->componentName(),
dt->uniqueId(),
topicSuffix
);
if (strlen(topic) == 0) {
return false;
}
return HAMqtt::instance()->publish(
topic,
data,
true
);
}
bool DeviceTypeSerializer::mqttSubscribeTopic(
const BaseDeviceType* const dt,
const char* topicSuffix
)
{
const uint16_t& topicSize = calculateTopicLength(
dt->componentName(),
dt->uniqueId(),
topicSuffix
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
topic,
dt->componentName(),
dt->uniqueId(),
topicSuffix
);
if (strlen(topic) == 0) {
return false;
}
return HAMqtt::instance()->subscribe(topic);
}
@@ -0,0 +1,117 @@
#ifndef AHA_DEVICETYPESERIALIZER_H
#define AHA_DEVICETYPESERIALIZER_H
#include <stdint.h>
class HADevice;
class BaseDeviceType;
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;
static const char* getTopicPrefix(bool isDiscoveryTopic);
/**
* Calculates length of the topic with given parameters.
* Topic format: [prefix]/[component]/[deviceId]/[objectId]/[suffix]
*
* @param component
* @param objectId
* @param suffix
* @param includeNullTerminator
* @param isDiscoveryTopic Determines which prefix will be used for topic.
*/
static uint16_t calculateTopicLength(
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator = true,
bool isDiscoveryTopic = false
);
/**
* 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: [prefix]/[component]/[deviceId]/[objectId]/[suffix]
*
* @param output
* @param component
* @param objectId
* @param suffix
* @param includeNullTerminator
* @param isDiscoveryTopic Determines which prefix will be used for topic.
*/
static uint16_t generateTopic(
char* output,
const char* component,
const char* objectId,
const char* suffix,
bool isDiscoveryTopic = false
);
static uint16_t calculateBaseJsonDataSize();
static uint16_t calculateNameFieldSize(
const char* name
);
static uint16_t calculateUniqueIdFieldSize(
const char* uniqueId
);
static uint16_t calculateAvailabilityFieldSize(
const BaseDeviceType* const dt
);
static uint16_t calculateRetainFieldSize(
bool retain
);
static uint16_t calculateDeviceFieldSize(
const char* serializedDevice
);
static void mqttWriteBeginningJson();
static void mqttWriteEndJson();
static void mqttWriteConstCharField(
const char* prefix,
const char* value,
bool quoteSuffix = true
);
static void mqttWriteNameField(
const char* name
);
static void mqttWriteUniqueIdField(
const char* uniqueId
);
static void mqttWriteAvailabilityField(
const BaseDeviceType* const dt
);
static void mqttWriteRetainField(
bool retain
);
static void mqttWriteDeviceField(
const char* serializedDevice
);
static bool mqttWriteTopicField(
const BaseDeviceType* const dt,
const char* jsonPrefix,
const char* topicSuffix
);
static bool mqttPublishMessage(
const BaseDeviceType* const dt,
const char* topicSuffix,
const char* data
);
static bool mqttSubscribeTopic(
const BaseDeviceType* const dt,
const char* topicSuffix
);
};
#endif
@@ -0,0 +1,194 @@
#include "HABinarySensor.h"
#ifdef ARDUINOHA_BINARY_SENSOR
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
HABinarySensor::HABinarySensor(
const char* uniqueId,
bool initialState
) :
BaseDeviceType("binary_sensor", uniqueId),
_class(nullptr),
_currentState(initialState)
{
}
HABinarySensor::HABinarySensor(
const char* uniqueId,
bool initialState,
HAMqtt& mqtt
) :
HABinarySensor(uniqueId, initialState)
{
(void)mqtt;
}
HABinarySensor::HABinarySensor(
const char* uniqueId,
const char* deviceClass,
bool initialState
) :
BaseDeviceType("binary_sensor", uniqueId),
_class(deviceClass),
_currentState(initialState)
{
}
HABinarySensor::HABinarySensor(
const char* uniqueId,
const char* deviceClass,
bool initialState,
HAMqtt& mqtt
) :
HABinarySensor(uniqueId, deviceClass, initialState)
{
(void)mqtt;
}
void HABinarySensor::onMqttConnected()
{
if (strlen(uniqueId()) == 0) {
return;
}
publishConfig();
publishState(_currentState);
publishAvailability();
}
bool HABinarySensor::setState(bool state)
{
if (state == _currentState) {
return true;
}
if (publishState(state)) {
_currentState = state;
return true;
}
return false;
}
bool HABinarySensor::publishState(bool state)
{
if (strlen(uniqueId()) == 0) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
uniqueId(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
return false;
}
return mqtt()->publish(
topic,
(
state ?
DeviceTypeSerializer::StateOn :
DeviceTypeSerializer::StateOff
),
true
);
}
uint16_t HABinarySensor::calculateSerializedLength(
const char* serializedDevice
) const
{
if (serializedDevice == nullptr) {
return 0;
}
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
// state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
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) {
// Field format: ,"dev_cla":"[CLASS]"
size += strlen(_class) + 13; // 13 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
bool HABinarySensor::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// state topic
{
static const char Prefix[] PROGMEM = {"\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// device class
if (_class != nullptr) {
static const char Prefix[] PROGMEM = {",\"dev_cla\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _class);
}
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
@@ -0,0 +1,81 @@
#ifndef AHA_HABINARYSENSOR_H
#define AHA_HABINARYSENSOR_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_BINARY_SENSOR
class HABinarySensor : public BaseDeviceType
{
public:
/**
* Initializes binary sensor.
*
* @param uniqueId Unique ID of the sensor. Recommended 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* uniqueId,
bool initialState
);
HABinarySensor(
const char* uniqueId,
bool initialState,
HAMqtt& mqtt
); // legacy constructor
/**
* 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 uniqueId Unique ID 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* uniqueId,
const char* deviceClass,
bool initialState
);
HABinarySensor(
const char* uniqueId,
const char* deviceClass,
bool initialState,
HAMqtt& mqtt
);
/**
* 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:
bool publishState(bool state);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
const char* _class;
bool _currentState;
};
#endif
#endif
@@ -0,0 +1,286 @@
#include "HACover.h"
#ifdef ARDUINOHA_COVER
#include "../HAMqtt.h"
#include "../HADevice.h"
static const char ClosedStateStr[] PROGMEM = {"closed"};
static const char ClosingStateStr[] PROGMEM = {"closing"};
static const char OpenStateStr[] PROGMEM = {"open"};
static const char OpeningStateStr[] PROGMEM = {"opening"};
static const char StoppedStateStr[] PROGMEM = {"stopped"};
static const char OpenCommandStr[] PROGMEM = {"OPEN"};
static const char CloseCommandStr[] PROGMEM = {"CLOSE"};
static const char StopCommandStr[] PROGMEM = {"STOP"};
const char* HACover::PositionTopic = "ps";
HACover::HACover(const char* uniqueId) :
BaseDeviceType("cover", uniqueId),
_commandCallback(nullptr),
_currentState(StateUnknown),
_currentPosition(0),
_retain(false)
{
}
HACover::HACover(const char* uniqueId, HAMqtt& mqtt) :
HACover(uniqueId)
{
(void)mqtt;
}
void HACover::onMqttConnected()
{
if (strlen(uniqueId()) == 0) {
return;
}
publishConfig();
publishAvailability();
DeviceTypeSerializer::mqttSubscribeTopic(
this,
DeviceTypeSerializer::CommandTopic
);
if (!_retain) {
publishState(_currentState);
publishPosition(_currentPosition);
}
}
void HACover::onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
)
{
(void)payload;
if (compareTopics(topic, DeviceTypeSerializer::CommandTopic)) {
char cmd[length + 1];
memset(cmd, 0, sizeof(cmd));
memcpy(cmd, payload, length);
handleCommand(cmd);
}
}
bool HACover::setState(CoverState state, bool force)
{
if (!force && _currentState == state) {
return true;
}
if (publishState(state)) {
_currentState = state;
return true;
}
return false;
}
bool HACover::setPosition(int16_t position)
{
if (_currentPosition == position) {
return true;
}
if (publishPosition(position)) {
_currentPosition = position;
return true;
}
return false;
}
bool HACover::publishState(CoverState state)
{
if (strlen(uniqueId()) == 0 || state == StateUnknown) {
return false;
}
char stateStr[8];
switch (state) {
case StateClosed:
strcpy_P(stateStr, ClosedStateStr);
break;
case StateClosing:
strcpy_P(stateStr, ClosingStateStr);
break;
case StateOpen:
strcpy_P(stateStr, OpenStateStr);
break;
case StateOpening:
strcpy_P(stateStr, OpeningStateStr);
break;
case StateStopped:
strcpy_P(stateStr, StoppedStateStr);
break;
default:
return false;
}
return DeviceTypeSerializer::mqttPublishMessage(
this,
DeviceTypeSerializer::StateTopic,
stateStr
);
}
bool HACover::publishPosition(int16_t position)
{
if (strlen(uniqueId()) == 0) {
return false;
}
uint8_t digitsNb = floor(log10(position)) + 1;
char str[digitsNb + 2]; // + null terminator + negative sign
memset(str, 0, sizeof(str));
itoa(position, str, 10);
return DeviceTypeSerializer::mqttPublishMessage(
this,
PositionTopic,
str
);
}
uint16_t HACover::calculateSerializedLength(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
size += DeviceTypeSerializer::calculateRetainFieldSize(_retain);
// command topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::CommandTopic,
false
);
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(
componentName(),
uniqueId(),
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
}
// position topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
PositionTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: ,"pos_t":"[TOPIC]"
size += topicLength + 11; // 11 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
bool HACover::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// command topic
{
static const char Prefix[] PROGMEM = {"\"cmd_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::CommandTopic
);
}
// state topic
{
static const char Prefix[] PROGMEM = {",\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// position topic
{
static const char Prefix[] PROGMEM = {",\"pos_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
PositionTopic
);
}
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
void HACover::handleCommand(const char* cmd)
{
if (!_commandCallback) {
return;
}
if (strcmp_P(cmd, CloseCommandStr) == 0) {
_commandCallback(CommandClose);
} else if (strcmp_P(cmd, OpenCommandStr) == 0) {
_commandCallback(CommandOpen);
} else if (strcmp_P(cmd, StopCommandStr) == 0) {
_commandCallback(CommandStop);
}
}
#endif
@@ -0,0 +1,113 @@
#ifndef AHA_COVER_H
#define AHA_COVER_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_COVER
#define HACOVER_CALLBACK(name) void (*name)(CoverCommand cmd)
class HACover : public BaseDeviceType
{
public:
static const char* PositionTopic;
enum CoverState {
StateUnknown = 0,
StateClosed,
StateClosing,
StateOpen,
StateOpening,
StateStopped
};
enum CoverCommand {
CommandOpen,
CommandClose,
CommandStop
};
HACover(const char* uniqueId);
HACover(const char* uniqueId, HAMqtt& mqtt); // legacy constructor
virtual void onMqttConnected() override;
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
) override;
/**
* Changes state of the cover 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 cover.
* @param force Forces to update state without comparing it to previous known state.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setState(CoverState state, bool force = false);
/**
* Sets current state of the cover without pushing the state to Home Assistant.
* This method may be useful if you want to change state before connection
* with MQTT broker is acquired.
*
* @param state New state of the cover.
*/
inline void setCurrentState(CoverState state)
{ _currentState = state; }
/**
* Changes position of the cover 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 position New position of the cover.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setPosition(int16_t position);
/**
* Sets current position of the cover without pushing the value to Home Assistant.
* This method may be useful if you want to change position before connection
* with MQTT broker is acquired.
*
* @param position New position of the cover.
*/
inline void setCurrentPosition(int16_t position)
{ _currentPosition = position; }
/**
* Sets `retain` flag for commands published by Home Assistant.
* By default it's set to false.
*
* @param retain
*/
inline void setRetain(bool retain)
{ _retain = retain; }
/**
* Registers callback that will be called each time the command from HA is received.
* Please note that it's not possible to register multiple callbacks for the same covers.
*
* @param callback
*/
inline void onCommand(HACOVER_CALLBACK(callback))
{ _commandCallback = callback; }
protected:
bool publishState(CoverState state);
bool publishPosition(int16_t position);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
void handleCommand(const char* cmd);
HACOVER_CALLBACK(_commandCallback);
CoverState _currentState;
int16_t _currentPosition;
bool _retain;
};
#endif
#endif
@@ -0,0 +1,343 @@
#include "HAFan.h"
#ifdef ARDUINOHA_FAN
#include "../HAMqtt.h"
#include "../HADevice.h"
const char* HAFan::PercentageCommandTopic = {"sct"};
const char* HAFan::PercentageStateTopic = {"sst"};
HAFan::HAFan(const char* uniqueId, uint8_t features) :
BaseDeviceType("fan", uniqueId),
_features(features),
_currentState(false),
_stateCallback(nullptr),
_currentSpeed(0),
_speedCallback(nullptr),
_retain(false),
_speedRangeMin(1),
_speedRangeMax(100)
{
}
HAFan::HAFan(const char* uniqueId, uint8_t features, HAMqtt& mqtt) :
HAFan(uniqueId, features)
{
(void)mqtt;
}
void HAFan::onMqttConnected()
{
if (strlen(uniqueId()) == 0) {
return;
}
publishConfig();
publishAvailability();
DeviceTypeSerializer::mqttSubscribeTopic(
this,
DeviceTypeSerializer::CommandTopic
);
if (_features & SpeedsFeature) {
DeviceTypeSerializer::mqttSubscribeTopic(
this,
PercentageCommandTopic
);
}
if (!_retain) {
publishState(_currentState);
publishSpeed(_currentSpeed);
}
}
void HAFan::onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
)
{
(void)payload;
if (compareTopics(topic, DeviceTypeSerializer::CommandTopic)) {
bool state = (length == strlen(DeviceTypeSerializer::StateOn));
setState(state, true);
} else if (compareTopics(topic, PercentageCommandTopic)) {
char speedStr[length + 1];
memset(speedStr, 0, sizeof(speedStr));
memcpy(speedStr, payload, length);
int32_t speed = atoi(speedStr);
if (speed >= 0) {
setSpeed(speed);
}
}
}
bool HAFan::setState(bool state, bool force)
{
if (!force && _currentState == state) {
return true;
}
if (publishState(state)) {
_currentState = state;
if (_stateCallback) {
_stateCallback(state);
}
return true;
}
return false;
}
bool HAFan::setSpeed(uint16_t speed)
{
if (publishSpeed(speed)) {
_currentSpeed = speed;
if (_speedCallback) {
_speedCallback(_currentSpeed);
}
return true;
}
return false;
}
bool HAFan::publishState(bool state)
{
if (strlen(uniqueId()) == 0) {
return false;
}
return DeviceTypeSerializer::mqttPublishMessage(
this,
DeviceTypeSerializer::StateTopic,
(
state ?
DeviceTypeSerializer::StateOn :
DeviceTypeSerializer::StateOff
)
);
}
bool HAFan::publishSpeed(uint16_t speed)
{
if (strlen(uniqueId()) == 0) {
return false;
}
uint8_t digitsNb = floor(log10(speed)) + 1;
char str[digitsNb + 1]; // + null terminator
memset(str, 0, sizeof(str));
itoa(speed, str, 10);
return DeviceTypeSerializer::mqttPublishMessage(
this,
PercentageStateTopic,
str
);
}
uint16_t HAFan::calculateSerializedLength(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
size += DeviceTypeSerializer::calculateRetainFieldSize(_retain);
// command topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::CommandTopic,
false
);
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(
componentName(),
uniqueId(),
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
}
// speeds
if (_features & SpeedsFeature) {
// percentage command topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
PercentageCommandTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: ,"pct_cmd_t":"[TOPIC]"
size += topicLength + 15; // 15 - length of the JSON decorators for this field
}
// percentage state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
PercentageStateTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: ,"pct_stat_t":"[TOPIC]"
size += topicLength + 16; // 16 - length of the JSON decorators for this field
}
// speed range min
if (_speedRangeMin != 1) {
uint8_t digitsNb = floor(log10(_speedRangeMin)) + 1;
// Field format: ,"spd_rng_min":[VALUE]
size += digitsNb + 15; // 15 - length of the JSON decorators for this field
}
// speed range max
if (_speedRangeMax != 100) {
uint8_t digitsNb = floor(log10(_speedRangeMax)) + 1;
// Field format: ,"spd_rng_max":[VALUE]
size += digitsNb + 15; // 15 - length of the JSON decorators for this field
}
}
return size; // excludes null terminator
}
bool HAFan::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// command topic
{
static const char Prefix[] PROGMEM = {"\"cmd_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::CommandTopic
);
}
// state topic
{
static const char Prefix[] PROGMEM = {",\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// speeds
if (_features & SpeedsFeature) {
// percentage command topic
{
static const char Prefix[] PROGMEM = {",\"pct_cmd_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
PercentageCommandTopic
);
}
// percentage state topic
{
static const char Prefix[] PROGMEM = {",\"pct_stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
PercentageStateTopic
);
}
// speed range min
if (_speedRangeMin != 1) {
uint8_t digitsNb = floor(log10(_speedRangeMin)) + 1;
char str[digitsNb + 1]; // + null terminator
memset(str, 0, sizeof(str));
itoa(_speedRangeMin, str, 10);
static const char Prefix[] PROGMEM = {",\"spd_rng_min\":"};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
str,
false
);
}
// speed range max
if (_speedRangeMax != 100) {
uint8_t digitsNb = floor(log10(_speedRangeMax)) + 1;
char str[digitsNb + 1]; // + null terminator
memset(str, 0, sizeof(str));
itoa(_speedRangeMax, str, 10);
static const char Prefix[] PROGMEM = {",\"spd_rng_max\":"};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
str,
false
);
}
}
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
@@ -0,0 +1,156 @@
#ifndef AHA_FAN_H
#define AHA_FAN_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_FAN
#define HAFAN_STATE_CALLBACK_BOOL(name) void (*name)(bool)
#define HAFAN_STATE_CALLBACK_SPEED(name) void (*name)(uint16_t)
#define HAFAN_STATE_CALLBACK_SPEED_DEPRECATED(name) void (*name)(Speed)
class HAFan : public BaseDeviceType
{
public:
static const char* PercentageCommandTopic;
static const char* PercentageStateTopic;
enum Features {
DefaultFeatures = 0,
SpeedsFeature = 1
};
// @deprecated
enum Speed {
UnknownSpeed = 0,
OffSpeed = 1,
LowSpeed = 2,
MediumSpeed = 4,
HighSpeed = 8
};
HAFan(const char* uniqueId, uint8_t features = DefaultFeatures);
HAFan(const char* uniqueId, uint8_t features, HAMqtt& mqtt); // legacy constructor
virtual void onMqttConnected() override;
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
) override;
/**
* Changes state of the fan 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 fan (on - true, off - false).
* @param force Forces to update state without comparing it to previous known state.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setState(bool state, bool force = false);
/**
* Alias for setState(true).
*/
inline bool turnOn()
{ return setState(true); }
/**
* Alias for setState(false).
*/
inline bool turnOff()
{ return setState(false); }
/**
* Returns last known state of the fan.
* 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 state of the fan changes.
* Please note that it's not possible to register multiple callbacks for the same fan.
*
* @param callback
*/
inline void onStateChanged(HAFAN_STATE_CALLBACK_BOOL(callback))
{ _stateCallback = callback; }
/**
* Sets the list of supported fan's speeds.
*
* @param speeds
*/
AHA_DEPRECATED(inline void setSpeeds(uint8_t speeds))
{ (void)speeds; }
/**
* Sets speed of the fan.
*
* @param speed
*/
bool setSpeed(uint16_t speed);
/**
* Returns current speed of the fan.
*/
inline uint16_t getSpeed() const
{ return _currentSpeed; }
/**
* Registers callback that will be called each time the speed of the fan changes.
* Please note that it's not possible to register multiple callbacks for the same fan.
*
* @param callback
*/
inline void onSpeedChanged(HAFAN_STATE_CALLBACK_SPEED(callback))
{ _speedCallback = callback; }
AHA_DEPRECATED(inline void onSpeedChanged(HAFAN_STATE_CALLBACK_SPEED_DEPRECATED(callback)))
{ (void)callback; }
/**
* Sets `retain` flag for commands published by Home Assistant.
* By default it's set to false.
*
* @param retain
*/
inline void setRetain(bool retain)
{ _retain = retain; }
/**
* Sets minimum range for slider in the HA panel.
*
* @param min
*/
inline void setSpeedRangeMin(uint16_t min)
{ _speedRangeMin = min; }
/**
* Sets maximum range for slider in the HA panel.
*
* @param min
*/
inline void setSpeedRangeMax(uint16_t max)
{ _speedRangeMax = max; }
protected:
bool publishState(bool state);
bool publishSpeed(uint16_t speed);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
const uint8_t _features;
bool _currentState;
HAFAN_STATE_CALLBACK_BOOL(_stateCallback);
uint16_t _currentSpeed;
HAFAN_STATE_CALLBACK_SPEED(_speedCallback);
bool _retain;
uint16_t _speedRangeMin;
uint16_t _speedRangeMax;
};
#endif
#endif
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,336 @@
#ifndef AHA_HVAC_H
#define AHA_HVAC_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_HVAC
#define HAHVAC_STATE_CALLBACK_BOOL(name) void (*name)(bool)
#define HAHVAC_STATE_CALLBACK_DOUBLE(name) void (*name)(double)
#define HAHVAC_STATE_CALLBACK_MODE(name) void (*name)(HAHVAC::Mode)
class HAHVAC : public BaseDeviceType
{
public:
static const char* ActionTopic;
static const char* AuxCommandTopic;
static const char* AuxStateTopic;
static const char* AwayCommandTopic;
static const char* AwayStateTopic;
static const char* HoldCommandTopic;
static const char* HoldStateTopic;
static const char* TargetTemperatureCommandTopic;
static const char* TargetTemperatureStateTopic;
static const char* CurrentTemperatureTopic;
static const char* ModeCommandTopic;
static const char* ModeStateTopic;
enum Features {
DefaultFeatures = 0,
ActionFeature = 1,
AuxHeatingFeature = 2,
AwayModeFeature = 4,
HoldFeature = 8
};
enum Action {
UnknownAction = 0,
OffAction,
HeatingAction,
CoolingAction,
DryingAction,
IdleAction,
FanAction
};
enum Mode {
UnknownMode = 0,
OffMode = 1,
AutoMode = 2,
CoolMode = 4,
HeatMode = 8,
DryMode = 16,
FanOnlyMode = 32
};
enum TemperatureUnit {
DefaultUnit = 1,
CelsiusUnit,
FahrenheitUnit
};
/**
* Initializes HVAC.
*
* @param uniqueId Unique ID of the HVAC. Recommendes characters: [a-z0-9\-_]
* @param features The list of additional features (flag). For example: `HAHVAC::AuxHeatingFeature | HAHVAC::AwayModeFeature`
*/
HAHVAC(
const char* uniqueId,
uint8_t features = 0
);
HAHVAC(
const char* uniqueId,
uint8_t features,
HAMqtt& mqtt
); // legacy constructor
virtual void onMqttConnected() override;
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
) override;
/**
* Changes action of the HVAC (it's displayed in the Home Assistant panel).
*
* @param action New action.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setAction(Action action);
/**
* Returns action that was previously sent to MQTT.
*/
inline Action getAction() const
{ return _action; }
/**
* Changes default temperature unit.
* Please note that this method must be called before `mqtt.begin(...)`.
*
* @param unit See TemperatureUnit enum above
*/
inline void setTemperatureUnit(TemperatureUnit unit)
{ _temperatureUnit = unit; }
/**
* Publishes aux heating state.
* Please note that HAHVAC::AuxHeatingFeature must be set in the constructor.
*
* @param state ON (true) / OFF (false)
* @returns Returns true if MQTT message has been published successfully.
*/
bool setAuxHeatingState(bool state);
/**
* Registers callback that will be called each time the aux heating's state changes.
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onAuxHeatingStateChanged(HAHVAC_STATE_CALLBACK_BOOL(callback))
{ _auxHeatingCallback = callback; }
/**
* Returns state of the aux heating.
*
* @returns ON (true) / OFF (false)
*/
inline bool getAuxHeatingState() const
{ return _auxHeatingState; }
/**
* Publishes away state.
* Please note that HAHVAC::AwayModeFeature must be set in the constructor.
*
* @param state ON (true) / OFF (false)
* @returns Returns true if MQTT message has been published successfully.
*/
bool setAwayState(bool state);
/**
* Registers callback that will be called each time the away's state changes.
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onAwayStateChanged(HAHVAC_STATE_CALLBACK_BOOL(callback))
{ _awayCallback = callback; }
/**
* Returns away's state.
*
* @returns ON (true) / OFF (false)
*/
inline bool getAwayState() const
{ return _awayState; }
/**
* Publishes hold state.
* Please note that HAHVAC::HoldFeature must be set in the constructor.
*
* @param state ON (true) / OFF (false)
* @returns Returns true if MQTT message has been published successfully.
*/
bool setHoldState(bool state);
/**
* Registers callback that will be called each time the hold's state changes.
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onHoldStateChanged(HAHVAC_STATE_CALLBACK_BOOL(callback))
{ _holdCallback = callback; }
/**
* Returns hold's state.
*
* @returns ON (true) / OFF (false)
*/
inline bool getHoldState() const
{ return _holdState; }
/**
* Publishes given target temperature.
*
* @param targetTemperature
* @returns Returns true if MQTT message has been published successfully.
*/
bool setTargetTemperature(double targetTemperature);
/**
* Registers callback that will be called each time the target temperature changes
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onTargetTemperatureChanged(HAHVAC_STATE_CALLBACK_DOUBLE(callback))
{ _targetTempCallback = callback; }
/**
* Returns target temperature.
*
* @returns Target temperature or __DBL_MAX__ if temperature is not set.
*/
inline double getTargetTemperature() const
{ return _targetTemperature; }
/**
* Publishes current temperature.
*
* @param temperature
* @returns Returns true if MQTT message has been published successfully.
*/
bool setCurrentTemperature(double temperature);
/**
* Return temperature that was previously set by `setCurrentTemperature` method.
*
* @returns Temperature or __DBL_MAX__ if temperature is not set.
*/
inline double getCurrentTemperature() const
{ return _currentTemperature; }
/**
* Publishes working mode of the HVAC.
*
* @param mode
* @returns Returns true if MQTT message has been published successfully.
*/
bool setMode(Mode mode);
/**
* Same as above but input is the string representation of the mode.
*
* @param mode
* @returns Returns true if MQTT message has been published successfully.
*/
bool setModeFromStr(const char* mode);
/**
* Registers callback that will be called each time the mode changes.
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onModeChanged(HAHVAC_STATE_CALLBACK_MODE(callback))
{ _modeChangedCallback = callback; }
/**
* Returns HVAC's mode.
*
* @returns It may be UnknownMode if it's not set.
*/
inline Mode getMode() const
{ return _currentMode; }
/**
* Sets the list of supported modes. By default the list contains all available modes.
* You can merge multiple modes as following: `setModes(HAHVAC::OffMode | HAHVAC::CoolMode)`
*
* @param modes
*/
inline void setModes(uint8_t modes)
{ _modes = modes; }
/**
* Sets `retain` flag for commands published by Home Assistant.
* By default it's set to false.
*
* @param retain
*/
inline void setRetain(bool retain)
{ _retain = retain; }
/**
* Sets the minimum temperature that user will be able to select in Home Assistant panel.
*
* @param minTemp
*/
bool setMinTemp(double minTemp);
/**
* Sets the maximum temperature that user will be able to select in Home Assistant panel.
*
* @param minTemp
*/
bool setMaxTemp(double maxTemp);
/**
* Sets the step of the temperature's picker in the Home Assistant panel.
*
* @param tempStep
*/
bool setTempStep(double tempStep);
private:
bool publishAction(Action action);
bool publishAuxHeatingState(bool state);
bool publishAwayState(bool state);
bool publishHoldState(bool state);
bool publishCurrentTemperature(double temperature);
bool publishTargetTemperature(double temperature);
bool publishMode(Mode mode);
void subscribeTopics();
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
uint16_t calculateModesLength() const;
bool writeSerializedData(const char* serializedDevice) const override;
const char* _uniqueId;
const uint8_t _features;
TemperatureUnit _temperatureUnit;
Action _action;
HAHVAC_STATE_CALLBACK_BOOL(_auxHeatingCallback);
bool _auxHeatingState;
HAHVAC_STATE_CALLBACK_BOOL(_awayCallback);
bool _awayState;
HAHVAC_STATE_CALLBACK_BOOL(_holdCallback);
bool _holdState;
double _currentTemperature;
double _minTemp;
double _maxTemp;
double _tempStep;
HAHVAC_STATE_CALLBACK_DOUBLE(_targetTempCallback);
double _targetTemperature;
uint8_t _modes;
HAHVAC_STATE_CALLBACK_MODE(_modeChangedCallback);
Mode _currentMode;
bool _retain;
};
#endif
#endif
@@ -0,0 +1,218 @@
#ifdef ARDUINO_ARCH_SAMD
#include <avr/dtostrf.h>
#endif
#include "HASensor.h"
#ifdef ARDUINOHA_SENSOR
#include "../HAMqtt.h"
#include "../HADevice.h"
HASensor::HASensor(const char* uniqueId) :
BaseDeviceType("sensor", uniqueId),
_class(nullptr),
_units(nullptr),
_icon(nullptr)
{
}
HASensor::HASensor(const char* uniqueId, HAMqtt& mqtt) :
HASensor(uniqueId)
{
(void)mqtt;
}
void HASensor::onMqttConnected()
{
if (strlen(uniqueId()) == 0) {
return;
}
publishConfig();
publishAvailability();
}
bool HASensor::setValue(const char* value)
{
return publishValue(value);
}
bool HASensor::setValue(uint32_t value)
{
uint8_t digitsNb = floor(log10(value)) + 1;
char str[digitsNb + 1]; // + null terminator
memset(str, 0, sizeof(str));
itoa(value, str, 10);
return publishValue(str);
}
bool HASensor::setValue(int32_t value)
{
uint8_t digitsNb = floor(log10(value)) + 1;
char str[digitsNb + 2]; // + null terminator and minus sign
memset(str, 0, sizeof(str));
itoa(value, str, 10);
return publishValue(str);
}
bool HASensor::setValue(double value, uint8_t precision)
{
uint8_t digitsNb = floor(log10(floor(value))) + 1;
char str[digitsNb + 3 + precision]; // null terminator, dot, minus sign
dtostrf(value, 0, precision, str);
return publishValue(str);
}
bool HASensor::setValue(float value, uint8_t precision)
{
uint8_t digitsNb = floor(log10(floor(value))) + 1;
char str[digitsNb + 3 + precision]; // null terminator, dot, minus sign
dtostrf(value, 0, precision, str);
return publishValue(str);
}
bool HASensor::publishValue(const char* value)
{
if (strlen(uniqueId()) == 0 || value == nullptr) {
return false;
}
if (!mqtt()->isConnected()) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
uniqueId(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
return false;
}
return mqtt()->publish(topic, value, true);
}
uint16_t HASensor::calculateSerializedLength(
const char* serializedDevice
) const
{
if (serializedDevice == nullptr) {
return 0;
}
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
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) {
// Field format: ,"dev_cla":"[CLASS]"
size += strlen(_class) + 13; // 13 - length of the JSON decorators for this field
}
// units of measurement
if (_units != nullptr) {
// Format: ,"unit_of_meas":"[UNITS]"
size += strlen(_units) + 18; // 18 - length of the JSON decorators for this field
}
// icon
if (_icon != nullptr) {
// Field format: ,"ic":"[ICON]"
size += strlen(_icon) + 8; // 8 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
bool HASensor::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// state topic
{
static const char Prefix[] PROGMEM = {"\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// device class
if (_class != nullptr) {
static const char Prefix[] PROGMEM = {",\"dev_cla\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _class);
}
// units of measurement
if (_units != nullptr) {
static const char Prefix[] PROGMEM = {",\"unit_of_meas\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _units);
}
// icon
if (_icon != nullptr) {
static const char Prefix[] PROGMEM = {",\"ic\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
_icon
);
}
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
@@ -0,0 +1,90 @@
#ifndef AHA_HASENSOR_H
#define AHA_HASENSOR_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_SENSOR
class HASensor : public BaseDeviceType
{
public:
/**
* Initializes binary sensor.
*
* @param uniqueId Unique ID of the sensor. Recommendes characters: [a-z0-9\-_]
*/
HASensor(
const char* uniqueId
);
HASensor(
const char* uniqueId,
HAMqtt& mqtt
); // legacy constructor
/**
* Publishes configuration of the sensor to the MQTT.
*/
virtual void onMqttConnected() override;
/**
* Publishes new value of the sensor.
* Please note that connection to MQTT broker must be acquired.
* Otherwise method will return false.
*
* @param state Value to publish.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setValue(const char* value);
bool setValue(uint32_t value);
bool setValue(int32_t value);
bool setValue(double value, uint8_t precision = 2);
bool setValue(float value, uint8_t precision = 2);
inline bool setValue(uint8_t value)
{ return setValue(static_cast<uint32_t>(value)); }
inline bool setValue(uint16_t value)
{ return setValue(static_cast<uint32_t>(value)); }
inline bool setValue(int8_t value)
{ return setValue(static_cast<int32_t>(value)); }
inline bool setValue(int16_t value)
{ return setValue(static_cast<int32_t>(value)); }
/**
* The type/class of the sensor to set the icon in the frontend.
*
* @param className https://www.home-assistant.io/integrations/sensor/#device-class
*/
inline void setDeviceClass(const char* className)
{ _class = className; }
/**
* Defines the units of measurement of the sensor, if any.
*
* @param units For example: °C, %
*/
inline void setUnitOfMeasurement(const char* units)
{ _units = units; }
/**
* Sets icon of the sensor, e.g. `mdi:home`.
*
* @param icon Material Design Icon name with mdi: prefix.
*/
inline void setIcon(const char* icon)
{ _icon = icon; }
private:
bool publishValue(const char* value);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
const char* _class;
const char* _units;
const char* _icon;
};
#endif
#endif
@@ -0,0 +1,211 @@
#include "HASwitch.h"
#ifdef ARDUINOHA_SWITCH
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
HASwitch::HASwitch(const char* uniqueId, bool initialState) :
BaseDeviceType("switch", uniqueId),
_stateCallback(nullptr),
_beforeStateCallback(nullptr),
_currentState(initialState),
_icon(nullptr),
_retain(false)
{
}
HASwitch::HASwitch(
const char* uniqueId,
bool initialState,
HAMqtt& mqtt
) :
HASwitch(uniqueId, initialState)
{
(void)mqtt;
}
void HASwitch::onMqttConnected()
{
if (strlen(uniqueId()) == 0) {
return;
}
publishConfig();
publishAvailability();
if (!_retain) {
publishState(_currentState);
}
DeviceTypeSerializer::mqttSubscribeTopic(
this,
DeviceTypeSerializer::CommandTopic
);
}
void HASwitch::onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
)
{
(void)payload;
if (compareTopics(topic, DeviceTypeSerializer::CommandTopic)) {
bool state = (length == strlen(DeviceTypeSerializer::StateOn));
setState(state, true);
}
}
bool HASwitch::setState(bool state, bool force)
{
if (!force && _currentState == state) {
return true;
}
if (_beforeStateCallback) {
_beforeStateCallback(state, this);
}
if (publishState(state)) {
_currentState = state;
if (_stateCallback) {
_stateCallback(_currentState, this);
}
return true;
}
return false;
}
bool HASwitch::publishState(bool state)
{
if (strlen(uniqueId()) == 0) {
return false;
}
return DeviceTypeSerializer::mqttPublishMessage(
this,
DeviceTypeSerializer::StateTopic,
(
state ?
DeviceTypeSerializer::StateOn :
DeviceTypeSerializer::StateOff
)
);
}
uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return 0;
}
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
size += DeviceTypeSerializer::calculateRetainFieldSize(_retain);
// cmd topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::CommandTopic,
false
);
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(
componentName(),
uniqueId(),
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
}
// icon
if (_icon != nullptr) {
// Field format: ,"ic":"[ICON]"
size += strlen(_icon) + 8; // 8 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
bool HASwitch::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// command topic
{
static const char Prefix[] PROGMEM = {"\"cmd_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::CommandTopic
);
}
// state topic
{
static const char Prefix[] PROGMEM = {",\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// icon
if (_icon != nullptr) {
static const char Prefix[] PROGMEM = {",\"ic\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
_icon
);
}
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
@@ -0,0 +1,123 @@
#ifndef AHA_HASWITCH_H
#define AHA_HASWITCH_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_SWITCH
#define HASWITCH_CALLBACK(name) void (*name)(bool, HASwitch*)
class HASwitch : public BaseDeviceType
{
public:
/**
* Initializes switch.
*
* @param uniqueId Unique ID of the switch. Recommendes characters: [a-z0-9\-_]
* @param initialState Initial state of the switch.
It will be published right after "config" message in order to update HA state.
*/
HASwitch(
const char* uniqueId,
bool initialState
);
HASwitch(
const char* uniqueId,
bool initialState,
HAMqtt& mqtt
); // legacy constructor
/**
* Publishes configuration of the sensor to the MQTT.
*/
virtual void onMqttConnected() override;
/**
* Processes message received from the MQTT broker.
* The method updates state of the switch (if message matches switch'es topic).
*/
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
) override;
/**
* Changes state of the switch 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 switch.
* @param force Forces to update state without comparing it to previous known state.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setState(bool state, bool force = false);
/**
* Alias for setState(true).
*/
inline bool turnOn()
{ return setState(true); }
/**
* Alias for setState(false).
*/
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 state of the switch changes.
* Please note that it's not possible to register multiple callbacks for the same switch.
*
* @param callback
*/
inline void onStateChanged(HASWITCH_CALLBACK(callback))
{ _stateCallback = callback; }
/**
* Registers callback that will be called before state of the switch changes.
* The state passed to callback is a new state that is going to be set.
*
* @param callback
*/
inline void onBeforeStateChanged(HASWITCH_CALLBACK(callback))
{ _beforeStateCallback = callback; }
/**
* Sets icon of the switch, e.g. `mdi:home`.
*
* @param icon Material Design Icon name with mdi: prefix.
*/
inline void setIcon(const char* icon)
{ _icon = icon; }
/**
* Sets `retain` flag for commands published by Home Assistant.
* By default it's set to false.
*
* @param retain
*/
inline void setRetain(bool retain)
{ _retain = retain; }
private:
bool publishState(bool state);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
HASWITCH_CALLBACK(_stateCallback);
HASWITCH_CALLBACK(_beforeStateCallback);
bool _currentState;
const char* _icon;
bool _retain;
};
#endif
#endif
@@ -0,0 +1,111 @@
#include "HATagScanner.h"
#ifdef ARDUINOHA_TAG_SCANNER
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
HATagScanner::HATagScanner(const char* uniqueId) :
BaseDeviceType("tag", uniqueId)
{
}
HATagScanner::HATagScanner(const char* uniqueId, HAMqtt& mqtt) :
HATagScanner(uniqueId)
{
(void)mqtt;
}
void HATagScanner::onMqttConnected()
{
if (strlen(uniqueId()) == 0) {
return;
}
publishConfig();
}
bool HATagScanner::tagScanned(const char* tag)
{
if (tag == nullptr || strlen(tag) == 0 || strlen(uniqueId()) == 0) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::EventTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
uniqueId(),
DeviceTypeSerializer::EventTopic
);
if (strlen(topic) == 0) {
return false;
}
return mqtt()->publish(topic, tag);
}
uint16_t HATagScanner::calculateSerializedLength(
const char* serializedDevice
) const
{
if (serializedDevice == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
// event topic
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::EventTopic,
false
);
// Format: "t":"[TOPIC]"
size += topicSize + 6; // 6 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
bool HATagScanner::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// topic
{
static const char Prefix[] PROGMEM = {"\"t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::EventTopic
);
}
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
@@ -0,0 +1,43 @@
#ifndef AHA_HATAGSCANNER_H
#define AHA_HATAGSCANNER_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_TAG_SCANNER
class HATagScanner : public BaseDeviceType
{
public:
/**
* Initializes tag scanner with the given name.
*
* @param uniqueId Unique ID of the scanner. Recommendes characters: [a-z0-9\-_]
*/
HATagScanner(const char* uniqueId);
HATagScanner(const char* uniqueId, HAMqtt& mqtt); // legacy constructor
/**
* 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 { (void)online; }
/**
* Sends "tag scanned" event to the MQTT (Home Assistant).
* Based on this event HA may perform user-defined automation.
*
* @param tag Value of the scanned tag.
*/
bool tagScanned(const char* tag);
private:
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
};
#endif
#endif
@@ -0,0 +1,321 @@
#include "HATriggers.h"
#ifdef ARDUINOHA_TRIGGERS
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
HATriggers::HATriggers() :
BaseDeviceType("device_automation", nullptr),
_triggers(nullptr),
_triggersNb(0)
{
}
HATriggers::HATriggers(HAMqtt& mqtt) :
HATriggers()
{
(void)mqtt;
}
HATriggers::~HATriggers()
{
if (_triggers != nullptr) {
free(_triggers);
}
}
void HATriggers::onMqttConnected()
{
publishConfig();
}
bool HATriggers::add(const char* type, const char* subtype)
{
if (mqtt()->getDevice() == nullptr) {
return false;
}
HATrigger* triggers = (HATrigger*)realloc(_triggers, sizeof(HATrigger) * (_triggersNb + 1));
if (triggers == nullptr) {
return false;
}
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Adding HATrigger: "));
Serial.print(type);
Serial.print(F(" "));
Serial.print(subtype);
Serial.println();
#endif
_triggers = triggers;
_triggers[_triggersNb].type = type;
_triggers[_triggersNb].subtype = subtype;
_triggersNb++;
return true;
}
bool HATriggers::trigger(const char* type, const char* subtype)
{
HATrigger* trigger = nullptr;
for (uint8_t i = 0; i < _triggersNb; i++) {
if (strcmp(_triggers[i].type, type) == 0 &&
strcmp(_triggers[i].subtype, subtype) == 0) {
trigger = &_triggers[i];
break;
}
}
if (trigger == nullptr) {
return false;
}
const uint16_t& topicSize = calculateTopicLength(
componentName(),
trigger,
DeviceTypeSerializer::EventTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
topic,
componentName(),
trigger,
DeviceTypeSerializer::EventTopic
);
if (strlen(topic) == 0) {
return false;
}
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Triggering HATrigger: "));
Serial.print(type);
Serial.print(F(" "));
Serial.print(subtype);
Serial.println();
#endif
return mqtt()->publish(topic, "");
}
void HATriggers::publishConfig()
{
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return; // device is required for triggers
}
const uint16_t& deviceLength = device->calculateSerializedLength();
if (deviceLength == 0) {
return;
}
char serializedDevice[deviceLength];
if (device->serialize(serializedDevice) == 0) {
return;
}
for (uint8_t i = 0; i < _triggersNb; i++) {
const HATrigger* trigger = &_triggers[i];
if (trigger == nullptr) {
continue;
}
const uint16_t& topicLength = calculateTopicLength(
componentName(),
trigger,
DeviceTypeSerializer::ConfigTopic,
true,
true
);
const uint16_t& dataLength = calculateSerializedLength(
trigger,
serializedDevice
);
if (topicLength == 0 || dataLength == 0) {
continue;
}
char topic[topicLength];
generateTopic(
topic,
componentName(),
trigger,
DeviceTypeSerializer::ConfigTopic,
true
);
if (strlen(topic) == 0) {
continue;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedTrigger(trigger, serializedDevice);
mqtt()->endPublish();
}
}
}
uint16_t HATriggers::calculateTopicLength(
const char* component,
const HATrigger *trigger,
const char* suffix,
bool includeNullTerminator,
bool isDiscoveryTopic
) const
{
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // underscore and slash
return DeviceTypeSerializer::calculateTopicLength(
component,
nullptr,
suffix,
includeNullTerminator,
isDiscoveryTopic
) + length;
}
uint16_t HATriggers::generateTopic(
char* output,
const char* component,
const HATrigger *trigger,
const char* suffix,
bool isDiscoveryTopic
) const
{
static const char Underscore[] PROGMEM = {"_"};
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // underscore + null terminator
char objectId[length];
strcpy(objectId, trigger->subtype);
strcat_P(objectId, Underscore);
strcat(objectId, trigger->type);
return DeviceTypeSerializer::generateTopic(
output,
component,
objectId,
suffix,
isDiscoveryTopic
);
}
uint16_t HATriggers::calculateSerializedLength(
const HATrigger* trigger,
const char* serializedDevice
) const
{
if (serializedDevice == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
// automation type
{
// Format: "atype":"trigger"
size += 17;
}
// topic
{
const uint16_t& topicSize = calculateTopicLength(
componentName(),
trigger,
DeviceTypeSerializer::EventTopic,
false
);
if (topicSize == 0) {
return 0;
}
// Format: ,"t":"[TOPIC]"
size += topicSize + 7; // 7 - length of the JSON decorators for this field
}
// 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(
const HATrigger* trigger,
const char* serializedDevice
) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// automation type
{
static const char Data[] PROGMEM = {"\"atype\":\"trigger\""};
mqtt()->writePayload_P(Data);
}
// topic
{
const uint16_t& topicSize = calculateTopicLength(
componentName(),
trigger,
DeviceTypeSerializer::EventTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
topic,
componentName(),
trigger,
DeviceTypeSerializer::EventTopic
);
static const char Prefix[] PROGMEM = {",\"t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
topic
);
}
// type
{
static const char Prefix[] PROGMEM = {",\"type\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, trigger->type);
}
// subtype
{
static const char Prefix[] PROGMEM = {",\"stype\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, trigger->subtype);
}
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
@@ -0,0 +1,73 @@
#ifndef AHA_HATRIGGERS_H
#define AHA_HATRIGGERS_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_TRIGGERS
struct HATrigger {
const char* type;
const char* subtype;
} __attribute__((packed));
class HATriggers : public BaseDeviceType
{
public:
HATriggers();
HATriggers(HAMqtt& mqtt); // legacy constructor
virtual ~HATriggers();
virtual void onMqttConnected() override;
/**
* Triggers dont't support availability. Nothing to do here.
*/
virtual void setAvailability(bool online) override { (void)online; }
bool add(const char* type, const char* subtype);
bool trigger(const char* type, const char* subtype);
protected:
void publishConfig() override;
uint16_t calculateSerializedLength(
const char* serializedDevice
) const override { (void)serializedDevice; return 0; }
bool writeSerializedData(
const char* serializedDevice
) const override { (void)serializedDevice; return false; }
uint16_t calculateTopicLength(
const char* component,
const HATrigger *trigger,
const char* suffix,
bool includeNullTerminator = true,
bool isDiscoveryTopic = false
) const;
uint16_t generateTopic(
char* output,
const char* component,
const HATrigger *trigger,
const char* suffix,
bool isDiscoveryTopic = false
) const;
private:
uint16_t calculateSerializedLength(
const HATrigger* trigger,
const char* serializedDevice
) const;
bool writeSerializedTrigger(
const HATrigger* trigger,
const char* serializedDevice
) const;
HATrigger* _triggers;
uint8_t _triggersNb;
};
#endif
#endif