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:
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
|
||||
+163
@@ -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
|
||||
+421
@@ -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);
|
||||
}
|
||||
+117
@@ -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
|
||||
+194
@@ -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
|
||||
Reference in new issue
Block a user