release
This commit is contained in:
33
lib/home-assistant-integration/src/ArduinoHA.h
Normal file
33
lib/home-assistant-integration/src/ArduinoHA.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef AHA_ARDUINOHA_H
|
||||
#define AHA_ARDUINOHA_H
|
||||
|
||||
#include "HADevice.h"
|
||||
#include "HAMqtt.h"
|
||||
#include "device-types/HABinarySensor.h"
|
||||
#include "device-types/HAButton.h"
|
||||
//#include "device-types/HACamera.h"
|
||||
//#include "device-types/HACover.h"
|
||||
//#include "device-types/HADeviceTracker.h"
|
||||
//#include "device-types/HADeviceTrigger.h"
|
||||
//#include "device-types/HAFan.h"
|
||||
//#include "device-types/HAHVAC.h"
|
||||
#include "device-types/HALight.h"
|
||||
//#include "device-types/HALock.h"
|
||||
#include "device-types/HANumber.h"
|
||||
//#include "device-types/HAScene.h"
|
||||
#include "device-types/HASelect.h"
|
||||
#include "device-types/HASensor.h"
|
||||
#include "device-types/HASensorNumber.h"
|
||||
#include "device-types/HASwitch.h"
|
||||
//#include "device-types/HATagScanner.h"
|
||||
#include "utils/HAUtils.h"
|
||||
#include "utils/HANumeric.h"
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
#include "mocks/AUnitHelpers.h"
|
||||
#include "mocks/PubSubClientMock.h"
|
||||
#include "utils/HADictionary.h"
|
||||
#include "utils/HASerializer.h"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
35
lib/home-assistant-integration/src/ArduinoHADefines.h
Normal file
35
lib/home-assistant-integration/src/ArduinoHADefines.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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
|
||||
|
||||
// These macros allow to exclude some parts of the library to save more resources.
|
||||
#define EX_ARDUINOHA_BINARY_SENSOR
|
||||
//#define EX_ARDUINOHA_BUTTON
|
||||
#define EX_ARDUINOHA_CAMERA
|
||||
#define EX_ARDUINOHA_COVER
|
||||
#define EX_ARDUINOHA_DEVICE_TRACKER
|
||||
#define EX_ARDUINOHA_DEVICE_TRIGGER
|
||||
#define EX_ARDUINOHA_FAN
|
||||
#define EX_ARDUINOHA_HVAC
|
||||
//#define EX_ARDUINOHA_LIGHT
|
||||
#define EX_ARDUINOHA_LOCK
|
||||
#define EX_ARDUINOHA_NUMBER
|
||||
#define EX_ARDUINOHA_SCENE
|
||||
//#define EX_ARDUINOHA_SELECT
|
||||
// #define EX_ARDUINOHA_SENSOR
|
||||
// #define EX_ARDUINOHA_SWITCH
|
||||
#define EX_ARDUINOHA_TAG_SCANNER
|
||||
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
#include <Arduino.h>
|
||||
#define ARDUINOHA_DEBUG_PRINTLN(x) Serial.println(x);
|
||||
#define ARDUINOHA_DEBUG_PRINT(x) Serial.print(x);
|
||||
#else
|
||||
#define ARDUINOHA_DEBUG_INIT()
|
||||
#define ARDUINOHA_DEBUG_PRINTLN(x)
|
||||
#define ARDUINOHA_DEBUG_PRINT(x)
|
||||
#endif
|
||||
|
||||
#define AHATOFSTR(x) reinterpret_cast<const __FlashStringHelper*>(x)
|
||||
#define AHAFROMFSTR(x) reinterpret_cast<const char*>(x)
|
||||
146
lib/home-assistant-integration/src/HADevice.cpp
Normal file
146
lib/home-assistant-integration/src/HADevice.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
#include "ArduinoHADefines.h"
|
||||
#include "HADevice.h"
|
||||
#include "HAMqtt.h"
|
||||
#include "utils/HAUtils.h"
|
||||
#include "utils/HASerializer.h"
|
||||
|
||||
#define HADEVICE_INIT \
|
||||
_ownsUniqueId(false), \
|
||||
_serializer(new HASerializer(nullptr, 5)), \
|
||||
_availabilityTopic(nullptr), \
|
||||
_sharedAvailability(false), \
|
||||
_available(true) // device will be available by default
|
||||
|
||||
HADevice::HADevice() :
|
||||
_uniqueId(nullptr),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HADevice::HADevice(const char* uniqueId) :
|
||||
_uniqueId(uniqueId),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
_serializer->set(AHATOFSTR(HADeviceIdentifiersProperty), _uniqueId);
|
||||
}
|
||||
|
||||
HADevice::HADevice(const byte* uniqueId, const uint16_t length) :
|
||||
_uniqueId(HAUtils::byteArrayToStr(uniqueId, length)),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
_ownsUniqueId = true;
|
||||
_serializer->set(AHATOFSTR(HADeviceIdentifiersProperty), _uniqueId);
|
||||
}
|
||||
|
||||
HADevice::~HADevice()
|
||||
{
|
||||
delete _serializer;
|
||||
|
||||
if (_availabilityTopic) {
|
||||
delete _availabilityTopic;
|
||||
}
|
||||
|
||||
if (_ownsUniqueId) {
|
||||
delete[] _uniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
bool HADevice::setUniqueId(const byte* uniqueId, const uint16_t length)
|
||||
{
|
||||
if (_uniqueId) {
|
||||
return false; // unique ID cannot be changed at runtime once it's set
|
||||
}
|
||||
|
||||
_uniqueId = HAUtils::byteArrayToStr(uniqueId, length);
|
||||
_ownsUniqueId = true;
|
||||
_serializer->set(AHATOFSTR(HADeviceIdentifiersProperty), _uniqueId);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HADevice::setManufacturer(const char* manufacturer)
|
||||
{
|
||||
_serializer->set(AHATOFSTR(HADeviceManufacturerProperty), manufacturer);
|
||||
}
|
||||
|
||||
void HADevice::setModel(const char* model)
|
||||
{
|
||||
_serializer->set(AHATOFSTR(HADeviceModelProperty), model);
|
||||
}
|
||||
|
||||
void HADevice::setName(const char* name)
|
||||
{
|
||||
_serializer->set(AHATOFSTR(HANameProperty), name);
|
||||
}
|
||||
|
||||
void HADevice::setSoftwareVersion(const char* softwareVersion)
|
||||
{
|
||||
_serializer->set(
|
||||
AHATOFSTR(HADeviceSoftwareVersionProperty),
|
||||
softwareVersion
|
||||
);
|
||||
}
|
||||
|
||||
void HADevice::setAvailability(bool online)
|
||||
{
|
||||
_available = online;
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
bool HADevice::enableSharedAvailability()
|
||||
{
|
||||
if (_sharedAvailability) {
|
||||
return true; // already enabled
|
||||
}
|
||||
|
||||
const uint16_t topicLength = HASerializer::calculateDataTopicLength(
|
||||
nullptr,
|
||||
AHATOFSTR(HAAvailabilityTopic)
|
||||
);
|
||||
if (topicLength == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_availabilityTopic = new char[topicLength];
|
||||
|
||||
if (HASerializer::generateDataTopic(
|
||||
_availabilityTopic,
|
||||
nullptr,
|
||||
AHATOFSTR(HAAvailabilityTopic)
|
||||
) > 0) {
|
||||
_sharedAvailability = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HADevice::enableLastWill()
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (!mqtt || !_availabilityTopic) {
|
||||
return;
|
||||
}
|
||||
|
||||
mqtt->setLastWill(
|
||||
_availabilityTopic,
|
||||
"offline",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
void HADevice::publishAvailability() const
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (!_availabilityTopic || !mqtt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* payload = _available ? HAOnline : HAOffline;
|
||||
const uint16_t length = strlen_P(payload);
|
||||
|
||||
if (mqtt->beginPublish(_availabilityTopic, length, true)) {
|
||||
mqtt->writePayload(AHATOFSTR(payload));
|
||||
mqtt->endPublish();
|
||||
}
|
||||
}
|
||||
160
lib/home-assistant-integration/src/HADevice.h
Normal file
160
lib/home-assistant-integration/src/HADevice.h
Normal file
@@ -0,0 +1,160 @@
|
||||
#ifndef AHA_HADEVICE_H
|
||||
#define AHA_HADEVICE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class HASerializer;
|
||||
|
||||
/**
|
||||
* This class represents your device that's going to be registered in the Home Assistant devices registry.
|
||||
* Each entity (HABinarySensor, HASensor, etc.) that you use will be owned by this device.
|
||||
*/
|
||||
class HADevice
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructs HADevice without the unique ID.
|
||||
*
|
||||
* @note You will need to set the ID using HADevice::setUniqueId method. Otherwise none of the entities will work.
|
||||
*/
|
||||
HADevice();
|
||||
|
||||
/**
|
||||
* Constructs HADevice with the given unique ID (string).
|
||||
* Keep the unique ID short to save the memory.
|
||||
*
|
||||
* @param uniqueId String with the null terminator.
|
||||
*/
|
||||
HADevice(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Constructs HADevice using the given byte array as the unique ID.
|
||||
* It works in the same way as HADevice::setUniqueId method.
|
||||
*
|
||||
* @param uniqueId Bytes array that's going to be converted into the string.
|
||||
* @param length Number of bytes in the array.
|
||||
*/
|
||||
HADevice(const byte* uniqueId, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Deletes HASerializer and the availability topic if the shared availability was enabled.
|
||||
*/
|
||||
~HADevice();
|
||||
|
||||
/**
|
||||
* Returns pointer to the unique ID. It can be nullptr if the device has no ID assigned.
|
||||
*/
|
||||
inline const char* getUniqueId() const
|
||||
{ return _uniqueId; }
|
||||
|
||||
/**
|
||||
* Returns the instance of the HASerializer used by the device.
|
||||
* This method is used by all entities to serialize device's representation.
|
||||
*/
|
||||
inline const HASerializer* getSerializer() const
|
||||
{ return _serializer; }
|
||||
|
||||
/**
|
||||
* Returns true if the shared availability is enabled for the device.
|
||||
*/
|
||||
inline bool isSharedAvailabilityEnabled() const
|
||||
{ return _sharedAvailability; }
|
||||
|
||||
/**
|
||||
* Returns availability topic generated by the HADevice::enableSharedAvailability method.
|
||||
* It can be nullptr if the shared availability is not enabled.
|
||||
*/
|
||||
inline const char* getAvailabilityTopic() const
|
||||
{ return _availabilityTopic; }
|
||||
|
||||
/**
|
||||
* Returns online/offline state of the device.
|
||||
*/
|
||||
inline bool isAvailable() const
|
||||
{ return _available; }
|
||||
|
||||
/**
|
||||
* Sets unique ID of the device based on the given byte array.
|
||||
* Each byte is converted into a hex string representation, so the final length of the unique ID will be twice as given.
|
||||
*
|
||||
* @param uniqueId Bytes array that's going to be converted into the string.
|
||||
* @param length Number of bytes in the array.
|
||||
* @note The unique ID can be set only once (via constructor or using this method).
|
||||
*/
|
||||
bool setUniqueId(const byte* uniqueId, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Sets the "manufacturer" property that's going to be displayed in the Home Assistant.
|
||||
*
|
||||
* @param manufacturer Any string. Keep it short to save the memory.
|
||||
*/
|
||||
void setManufacturer(const char* manufacturer);
|
||||
|
||||
/**
|
||||
* Sets the "model" property that's going to be displayed in the Home Assistant.
|
||||
*
|
||||
* @param model Any string. Keep it short to save the memory.
|
||||
*/
|
||||
void setModel(const char* model);
|
||||
|
||||
/**
|
||||
* Sets the "name" property that's going to be displayed in the Home Assistant.
|
||||
*
|
||||
* @param name Any string. Keep it short to save the memory.
|
||||
*/
|
||||
void setName(const char* name);
|
||||
|
||||
/**
|
||||
* Sets the "software version" property that's going to be displayed in the Home Assistant.
|
||||
*
|
||||
* @param softwareVersion Any string. Keep it short to save the memory.
|
||||
*/
|
||||
void setSoftwareVersion(const char* softwareVersion);
|
||||
|
||||
/**
|
||||
* Sets device's availability and publishes MQTT message on the availability topic.
|
||||
* If the device is not connected to an MQTT broker or the shared availability is not enabled then nothing happens.
|
||||
*
|
||||
* @param online Set to true if the device should be displayed as available in the HA panel.
|
||||
*/
|
||||
void setAvailability(bool online);
|
||||
|
||||
/**
|
||||
* Enables the shared availability feature.
|
||||
*/
|
||||
bool enableSharedAvailability();
|
||||
|
||||
/**
|
||||
* Enables MQTT LWT feature.
|
||||
* Please note that the shared availability needs to be enabled first.
|
||||
*/
|
||||
void enableLastWill();
|
||||
|
||||
/**
|
||||
* Publishes current availability of the device on the availability topic.
|
||||
* If the device is not connected to an MQTT broker or the shared availability is not enabled then nothing happens.
|
||||
* This method is called by the HAMqtt when the connection to an MQTT broker is acquired.
|
||||
*/
|
||||
void publishAvailability() const;
|
||||
|
||||
private:
|
||||
/// The unique ID of the device. It can be a memory allocated by HADevice::setUniqueId method.
|
||||
const char* _uniqueId;
|
||||
|
||||
/// Specifies whether HADevice class owns the _uniqueId pointer.
|
||||
bool _ownsUniqueId;
|
||||
|
||||
/// JSON serializer of the HADevice class. It's allocated in the constructor.
|
||||
HASerializer* _serializer;
|
||||
|
||||
/// The availability topic allocated by HADevice::enableSharedAvailability method.
|
||||
char* _availabilityTopic;
|
||||
|
||||
/// Specifies whether the shared availability is enabled.
|
||||
bool _sharedAvailability;
|
||||
|
||||
/// Specifies whether the device is available (online / offline).
|
||||
bool _available;
|
||||
};
|
||||
|
||||
#endif
|
||||
318
lib/home-assistant-integration/src/HAMqtt.cpp
Normal file
318
lib/home-assistant-integration/src/HAMqtt.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
#include "HAMqtt.h"
|
||||
|
||||
#ifndef ARDUINOHA_TEST
|
||||
#include <PubSubClient.h>
|
||||
#endif
|
||||
|
||||
#include "HADevice.h"
|
||||
#include "device-types/HABaseDeviceType.h"
|
||||
#include "mocks/PubSubClientMock.h"
|
||||
|
||||
#define HAMQTT_INIT \
|
||||
_device(device), \
|
||||
_messageCallback(nullptr), \
|
||||
_connectedCallback(nullptr), \
|
||||
_initialized(false), \
|
||||
_discoveryPrefix(DefaultDiscoveryPrefix), \
|
||||
_dataPrefix(DefaultDataPrefix), \
|
||||
_username(nullptr), \
|
||||
_password(nullptr), \
|
||||
_lastConnectionAttemptAt(0), \
|
||||
_devicesTypesNb(0), \
|
||||
_maxDevicesTypesNb(maxDevicesTypesNb), \
|
||||
_devicesTypes(new HABaseDeviceType *[maxDevicesTypesNb]), \
|
||||
_lastWillTopic(nullptr), \
|
||||
_lastWillMessage(nullptr), \
|
||||
_lastWillRetain(false)
|
||||
|
||||
static const char *DefaultDiscoveryPrefix = "homeassistant";
|
||||
static const char *DefaultDataPrefix = "SHA";
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
HAMqtt::HAMqtt(
|
||||
PubSubClientMock *pubSub,
|
||||
HADevice &device,
|
||||
uint8_t maxDevicesTypesNb) : _mqtt(pubSub),
|
||||
HAMQTT_INIT
|
||||
{
|
||||
_instance = this;
|
||||
}
|
||||
#else
|
||||
HAMqtt::HAMqtt(
|
||||
Client &netClient,
|
||||
HADevice &device,
|
||||
uint8_t maxDevicesTypesNb) : _mqtt(new PubSubClient(netClient)),
|
||||
HAMQTT_INIT
|
||||
{
|
||||
_instance = this;
|
||||
}
|
||||
#endif
|
||||
|
||||
HAMqtt::~HAMqtt()
|
||||
{
|
||||
delete[] _devicesTypes;
|
||||
|
||||
if (_mqtt)
|
||||
{
|
||||
delete _mqtt;
|
||||
}
|
||||
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const IPAddress serverIp,
|
||||
const uint16_t serverPort,
|
||||
const char *username,
|
||||
const char *password,
|
||||
const char *clientID)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: init server "))
|
||||
ARDUINOHA_DEBUG_PRINT(serverIp)
|
||||
ARDUINOHA_DEBUG_PRINT(F(":"))
|
||||
ARDUINOHA_DEBUG_PRINTLN(serverPort)
|
||||
|
||||
if (_initialized)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: already initialized"))
|
||||
return false;
|
||||
}
|
||||
|
||||
_username = username;
|
||||
_password = password;
|
||||
_initialized = true;
|
||||
_clientID = clientID;
|
||||
_mqtt->setServer(serverIp, serverPort);
|
||||
_mqtt->setCallback(onMessageReceived);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const IPAddress serverIp,
|
||||
const char *username,
|
||||
const char *password,
|
||||
const char *userid)
|
||||
{
|
||||
return begin(serverIp, HAMQTT_DEFAULT_PORT, username, password, userid);
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const char *serverHostname,
|
||||
const uint16_t serverPort,
|
||||
const char *username,
|
||||
const char *password,
|
||||
const char *clientID)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: init server "))
|
||||
ARDUINOHA_DEBUG_PRINT(serverHostname)
|
||||
ARDUINOHA_DEBUG_PRINT(F(":"))
|
||||
ARDUINOHA_DEBUG_PRINTLN(serverPort)
|
||||
|
||||
if (_initialized)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: already initialized"))
|
||||
return false;
|
||||
}
|
||||
|
||||
_username = username;
|
||||
_password = password;
|
||||
_initialized = true;
|
||||
_clientID = clientID;
|
||||
|
||||
_mqtt->setServer(serverHostname, serverPort);
|
||||
_mqtt->setCallback(onMessageReceived);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const char *serverHostname,
|
||||
const char *username,
|
||||
const char *password,
|
||||
const char *userid)
|
||||
{
|
||||
return begin(serverHostname, HAMQTT_DEFAULT_PORT, username, password, userid);
|
||||
}
|
||||
|
||||
bool HAMqtt::disconnect()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: disconnecting"))
|
||||
|
||||
_initialized = false;
|
||||
_lastConnectionAttemptAt = 0;
|
||||
_mqtt->disconnect();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HAMqtt::disableHA()
|
||||
{
|
||||
noHA = true;
|
||||
}
|
||||
|
||||
void HAMqtt::loop()
|
||||
{
|
||||
if (_initialized && !_mqtt->loop())
|
||||
{
|
||||
connectToServer();
|
||||
}
|
||||
}
|
||||
|
||||
bool HAMqtt::isConnected() const
|
||||
{
|
||||
return _mqtt->connected();
|
||||
}
|
||||
|
||||
void HAMqtt::addDeviceType(HABaseDeviceType *deviceType)
|
||||
{
|
||||
if (_devicesTypesNb + 1 >= _maxDevicesTypesNb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_devicesTypes[_devicesTypesNb++] = deviceType;
|
||||
}
|
||||
|
||||
bool HAMqtt::publish(const char *topic, const char *payload, bool retained)
|
||||
{
|
||||
if (!isConnected())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: publishing "))
|
||||
ARDUINOHA_DEBUG_PRINT(topic)
|
||||
ARDUINOHA_DEBUG_PRINT(F(", len: "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(strlen(payload))
|
||||
|
||||
_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)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: begin publish "))
|
||||
ARDUINOHA_DEBUG_PRINT(topic)
|
||||
ARDUINOHA_DEBUG_PRINT(F(", len: "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(payloadLength)
|
||||
|
||||
return _mqtt->beginPublish(topic, payloadLength, retained);
|
||||
}
|
||||
|
||||
void HAMqtt::writePayload(const char *data, const uint16_t length)
|
||||
{
|
||||
writePayload(reinterpret_cast<const uint8_t *>(data), length);
|
||||
}
|
||||
|
||||
void HAMqtt::writePayload(const uint8_t *data, const uint16_t length)
|
||||
{
|
||||
_mqtt->write(data, length);
|
||||
}
|
||||
|
||||
void HAMqtt::writePayload(const __FlashStringHelper *src)
|
||||
{
|
||||
_mqtt->print(src);
|
||||
}
|
||||
|
||||
bool HAMqtt::endPublish()
|
||||
{
|
||||
return _mqtt->endPublish();
|
||||
}
|
||||
|
||||
bool HAMqtt::subscribe(const char *topic)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: subscribing "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(topic)
|
||||
|
||||
return _mqtt->subscribe(topic);
|
||||
}
|
||||
|
||||
void HAMqtt::processMessage(const char *topic, const uint8_t *payload, uint16_t length)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: received call "))
|
||||
ARDUINOHA_DEBUG_PRINT(topic)
|
||||
ARDUINOHA_DEBUG_PRINT(F(", len: "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(length)
|
||||
|
||||
if (_messageCallback)
|
||||
{
|
||||
_messageCallback(topic, payload, length);
|
||||
}
|
||||
|
||||
if (!noHA)
|
||||
{
|
||||
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();
|
||||
|
||||
_mqtt->connect(
|
||||
_clientID,
|
||||
_username,
|
||||
_password,
|
||||
_lastWillTopic,
|
||||
0,
|
||||
_lastWillRetain,
|
||||
_lastWillMessage,
|
||||
true);
|
||||
|
||||
if (isConnected())
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: connected"))
|
||||
if (_connectedCallback)
|
||||
{
|
||||
_connectedCallback();
|
||||
}
|
||||
if (!noHA)
|
||||
onConnectedLogic();
|
||||
}
|
||||
else
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: failed to connect"))
|
||||
}
|
||||
}
|
||||
|
||||
void HAMqtt::onConnectedLogic()
|
||||
{
|
||||
|
||||
_device.publishAvailability();
|
||||
|
||||
for (uint8_t i = 0; i < _devicesTypesNb; i++)
|
||||
{
|
||||
_devicesTypes[i]->onMqttConnected();
|
||||
}
|
||||
}
|
||||
413
lib/home-assistant-integration/src/HAMqtt.h
Normal file
413
lib/home-assistant-integration/src/HAMqtt.h
Normal file
@@ -0,0 +1,413 @@
|
||||
#ifndef AHA_HAMQTT_H
|
||||
#define AHA_HAMQTT_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Client.h>
|
||||
#include <IPAddress.h>
|
||||
#include "ArduinoHADefines.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
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
class PubSubClientMock;
|
||||
#else
|
||||
class PubSubClient;
|
||||
#endif
|
||||
|
||||
class HADevice;
|
||||
class HABaseDeviceType;
|
||||
|
||||
#if defined(ARDUINO_API_VERSION)
|
||||
using namespace arduino;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This class is a wrapper for the PubSub API.
|
||||
* It's a central point of the library where instances of all device types are stored.
|
||||
*/
|
||||
class HAMqtt
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Returns existing instance (singleton) of the HAMqtt class.
|
||||
* It may be a null pointer if the HAMqtt object was never constructed or it was destroyed.
|
||||
*/
|
||||
inline static HAMqtt *instance()
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
explicit HAMqtt(
|
||||
PubSubClientMock *pubSub,
|
||||
HADevice &device,
|
||||
const uint8_t maxDevicesTypesNb = 6);
|
||||
#else
|
||||
/**
|
||||
* Creates a new instance of the HAMqtt class.
|
||||
* Please note that only one instance of the class can be initialized at the same time.
|
||||
*
|
||||
* @param netClient The EthernetClient or WiFiClient that's going to be used for the network communication.
|
||||
* @param device An instance of the HADevice class representing your device.
|
||||
* @param maxDevicesTypesNb The maximum number of device types (sensors, switches, etc.) that you're going to implement.
|
||||
*/
|
||||
explicit HAMqtt(
|
||||
Client &netClient,
|
||||
HADevice &device,
|
||||
const uint8_t maxDevicesTypesNb = 6);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Removes singleton of the HAMqtt class.
|
||||
*/
|
||||
~HAMqtt();
|
||||
|
||||
/**
|
||||
* Sets the prefix of the Home Assistant discovery topics.
|
||||
* It needs to match the prefix set in the HA admin panel.
|
||||
* The default prefix is "homeassistant".
|
||||
*
|
||||
* @param prefix The discovery topics' prefix.
|
||||
*/
|
||||
inline void setDiscoveryPrefix(const char *prefix)
|
||||
{
|
||||
_discoveryPrefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the discovery topics' prefix.
|
||||
*/
|
||||
inline const char *getDiscoveryPrefix() const
|
||||
{
|
||||
return _discoveryPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets prefix of the data topics.
|
||||
* It may be useful if you want to pass MQTT traffic through a bridge.
|
||||
* The default prefix is "aha".
|
||||
*
|
||||
* @param prefix The data topics' prefix.
|
||||
*/
|
||||
inline void setDataPrefix(const char *prefix)
|
||||
{
|
||||
_dataPrefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data topics' prefix.
|
||||
*/
|
||||
inline const char *getDataPrefix() const
|
||||
{
|
||||
return _dataPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance of the device assigned to the HAMqtt class.
|
||||
* It's the same object (pointer) that was passed to the HAMqtt constructor.
|
||||
*/
|
||||
inline HADevice const *getDevice() const
|
||||
{
|
||||
return &_device;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new callback method that will be called when the device receives an MQTT message.
|
||||
* Please note that the callback is also fired by internal MQTT messages used by the library.
|
||||
* You should always verify the topic of the received message.
|
||||
*
|
||||
* @param callback Callback method.
|
||||
*/
|
||||
inline void onMessage(HAMQTT_MESSAGE_CALLBACK(callback))
|
||||
{
|
||||
_messageCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new callback method that will be called each time a connection to the MQTT broker is acquired.
|
||||
* The callback is also fired after reconnecting to the broker.
|
||||
* You can use this method to register topics' subscriptions.
|
||||
*
|
||||
* @param callback Callback method.
|
||||
*/
|
||||
inline void onConnected(HAMQTT_CALLBACK(callback))
|
||||
{
|
||||
_connectedCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets parameters of the MQTT connection using the IP address and port.
|
||||
* 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. It can be nullptr if the anonymous connection needs to be performed.
|
||||
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
*/
|
||||
bool begin(
|
||||
const IPAddress serverIp,
|
||||
const uint16_t serverPort = HAMQTT_DEFAULT_PORT,
|
||||
const char *username = nullptr,
|
||||
const char *password = nullptr,
|
||||
const char *clientID = nullptr);
|
||||
|
||||
/**
|
||||
* Sets parameters of the MQTT connection using the IP address and the default port (1883).
|
||||
* 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 username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
*/
|
||||
bool begin(
|
||||
const IPAddress serverIp,
|
||||
const char *username,
|
||||
const char *password,
|
||||
const char *clientID);
|
||||
|
||||
/**
|
||||
* Sets parameters of the MQTT connection using the hostname and port.
|
||||
* 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 serverHostname Hostname of the MQTT broker.
|
||||
* @param serverPort Port of the MQTT broker.
|
||||
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
*/
|
||||
bool begin(
|
||||
const char *serverHostname,
|
||||
const uint16_t serverPort = HAMQTT_DEFAULT_PORT,
|
||||
const char *username = nullptr,
|
||||
const char *password = nullptr,
|
||||
const char *clientID = nullptr);
|
||||
|
||||
/**
|
||||
* Sets parameters of the MQTT connection using the hostname and the default port (1883).
|
||||
* 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 serverHostname Hostname of the MQTT broker.
|
||||
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
*/
|
||||
bool begin(
|
||||
const char *serverHostname,
|
||||
const char *username,
|
||||
const char *password,
|
||||
const char *clientID);
|
||||
|
||||
/**
|
||||
* Closes the MQTT connection.
|
||||
*/
|
||||
bool disconnect();
|
||||
void disableHA();
|
||||
/**
|
||||
* This method should be called periodically inside the main loop of the firmware.
|
||||
* It's safe to call this method in some interval (like 5ms).
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/**
|
||||
* Returns true if connection to the MQTT broker is established.
|
||||
*/
|
||||
bool isConnected() const;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @note The HAMqtt class doesn't take ownership of the given pointer.
|
||||
* @param deviceType Instance of the device's type (HASwitch, HABinarySensor, etc.).
|
||||
*/
|
||||
void addDeviceType(HABaseDeviceType *deviceType);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with given topic and payload.
|
||||
* Message won't be published if the connection with the MQTT broker is not established.
|
||||
* In this case method returns false.
|
||||
*
|
||||
* @param topic The topic to publish.
|
||||
* @param payload The payload to publish (it may be empty const char).
|
||||
* @param retained Specifies whether message should be retained.
|
||||
*/
|
||||
bool publish(const char *topic, const char *payload, bool retained = false);
|
||||
|
||||
/**
|
||||
* Begins publishing of a message with the given properties.
|
||||
* When this method returns true the payload can be written using HAMqtt::writePayload method.
|
||||
*
|
||||
* @param topic Topic of the published message.
|
||||
* @param payloadLength Length of the payload (bytes) that's going to be published.
|
||||
* @param retained Specifies whether the published message should be retained.
|
||||
*/
|
||||
bool beginPublish(const char *topic, uint16_t payloadLength, bool retained = false);
|
||||
|
||||
/**
|
||||
* Writes given string to the TCP stream.
|
||||
* Please note that before writing any data the HAMqtt::beginPublish method
|
||||
* needs to be called.
|
||||
*
|
||||
* @param data The string to publish.
|
||||
* @param length Length of the data (bytes).
|
||||
*/
|
||||
void writePayload(const char *data, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Writes given data to the TCP stream.
|
||||
* Please note that before writing any data the HAMqtt::beginPublish method
|
||||
* needs to be called.
|
||||
*
|
||||
* @param data The data to publish.
|
||||
* @param length Length of the data (bytes).
|
||||
*/
|
||||
void writePayload(const uint8_t *data, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Writes given progmem data to the TCP stream.
|
||||
* Please note that before writing any data the HAMqtt::beginPublish method
|
||||
* needs to be called.
|
||||
*
|
||||
* @param data Progmem data to publish.
|
||||
*/
|
||||
void writePayload(const __FlashStringHelper *data);
|
||||
|
||||
/**
|
||||
* Finishes publishing of a message.
|
||||
* After calling this method the message will be processed by the broker.
|
||||
*/
|
||||
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 the last will message that will be produced when the device disconnects from the broker.
|
||||
* If you want to change availability of the device in Home Assistant panel
|
||||
* please use enableLastWill() method from the HADevice class instead.
|
||||
*
|
||||
* @param lastWillTopic The topic to publish.
|
||||
* @param lastWillMessage The message (payload) to publish.
|
||||
* @param lastWillRetain Specifies whether the published message should be retained.
|
||||
*/
|
||||
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).
|
||||
*
|
||||
* @note Do not use this method on your own. It's only for the internal purpose.
|
||||
* @param topic Topic of the message.
|
||||
* @param payload Content of the message.
|
||||
* @param length Length of the message.
|
||||
*/
|
||||
void processMessage(const char *topic, const uint8_t *payload, uint16_t length);
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
inline uint8_t getDevicesTypesNb() const
|
||||
{
|
||||
return _devicesTypesNb;
|
||||
}
|
||||
|
||||
inline HABaseDeviceType **getDevicesTypes() const
|
||||
{
|
||||
return _devicesTypes;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
/// Interval between MQTT reconnects (milliseconds).
|
||||
static const uint16_t ReconnectInterval = 5000;
|
||||
|
||||
/// Living instance of the HAMqtt class. It can be nullptr.
|
||||
static HAMqtt *_instance;
|
||||
|
||||
/**
|
||||
* Attempts to connect to the MQTT broker.
|
||||
* The method uses properties passed to the "begin" method.
|
||||
*/
|
||||
void connectToServer();
|
||||
bool noHA = false;
|
||||
/**
|
||||
* This method is called each time the connection with MQTT broker is acquired.
|
||||
*/
|
||||
void onConnectedLogic();
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
PubSubClientMock *_mqtt;
|
||||
#else
|
||||
/// Instance of the PubSubClient class. It's initialized in the constructor.
|
||||
PubSubClient *_mqtt;
|
||||
#endif
|
||||
|
||||
/// Instance of the HADevice passed to the constructor.
|
||||
const HADevice &_device;
|
||||
|
||||
/// The callback method that will be called when an MQTT message is received.
|
||||
HAMQTT_MESSAGE_CALLBACK(_messageCallback);
|
||||
|
||||
/// The callback method that will be called when the MQTT connection is acquired.
|
||||
HAMQTT_CALLBACK(_connectedCallback);
|
||||
|
||||
/// Specifies whether the HAMqtt::begin method was ever called.
|
||||
bool _initialized;
|
||||
|
||||
/// Teh discovery prefix that's used for the configuration messages.
|
||||
const char *_discoveryPrefix;
|
||||
|
||||
/// The data prefix that's used for publishing data messages.
|
||||
const char *_dataPrefix;
|
||||
|
||||
/// The username used for the authentication. It's set in the HAMqtt::begin method.
|
||||
const char *_username;
|
||||
|
||||
/// The username used for the authentication. It's set in the HAMqtt::begin method.
|
||||
const char *_clientID;
|
||||
|
||||
/// The password used for the authentication. It's set in the HAMqtt::begin method.
|
||||
const char *_password;
|
||||
|
||||
/// Time of the last connection attemps (milliseconds since boot).
|
||||
uint32_t _lastConnectionAttemptAt;
|
||||
|
||||
/// The amount of registered devices types.
|
||||
uint8_t _devicesTypesNb;
|
||||
|
||||
/// The maximum amount of devices types that can be registered.
|
||||
uint8_t _maxDevicesTypesNb;
|
||||
|
||||
/// Pointers of all registered devices types (array of pointers).
|
||||
HABaseDeviceType **_devicesTypes;
|
||||
|
||||
/// The last will topic set by HAMqtt::setLastWill
|
||||
const char *_lastWillTopic;
|
||||
|
||||
/// The last will message set by HAMqtt::setLastWill
|
||||
const char *_lastWillMessage;
|
||||
|
||||
/// The last will retain set by HAMqtt::setLastWill
|
||||
bool _lastWillRetain;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HABaseDeviceType::HABaseDeviceType(
|
||||
const __FlashStringHelper* componentName,
|
||||
const char* uniqueId
|
||||
) :
|
||||
_componentName(componentName),
|
||||
_uniqueId(uniqueId),
|
||||
_name(nullptr),
|
||||
_serializer(nullptr),
|
||||
_availability(AvailabilityDefault)
|
||||
{
|
||||
if (mqtt()) {
|
||||
mqtt()->addDeviceType(this);
|
||||
}
|
||||
}
|
||||
|
||||
void HABaseDeviceType::setAvailability(bool online)
|
||||
{
|
||||
_availability = (online ? AvailabilityOnline : AvailabilityOffline);
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
HAMqtt* HABaseDeviceType::mqtt()
|
||||
{
|
||||
return HAMqtt::instance();
|
||||
}
|
||||
|
||||
void HABaseDeviceType::subscribeTopic(
|
||||
const char* uniqueId,
|
||||
const __FlashStringHelper* topic
|
||||
)
|
||||
{
|
||||
const uint16_t topicLength = HASerializer::calculateDataTopicLength(
|
||||
uniqueId,
|
||||
topic
|
||||
);
|
||||
if (topicLength == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char fullTopic[topicLength];
|
||||
if (!HASerializer::generateDataTopic(
|
||||
fullTopic,
|
||||
uniqueId,
|
||||
topic
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HAMqtt::instance()->subscribe(fullTopic);
|
||||
}
|
||||
|
||||
void HABaseDeviceType::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
(void)topic;
|
||||
(void)payload;
|
||||
(void)length;
|
||||
}
|
||||
|
||||
void HABaseDeviceType::destroySerializer()
|
||||
{
|
||||
if (_serializer) {
|
||||
delete _serializer;
|
||||
_serializer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HABaseDeviceType::publishConfig()
|
||||
{
|
||||
buildSerializer();
|
||||
|
||||
if (_serializer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t topicLength = HASerializer::calculateConfigTopicLength(
|
||||
componentName(),
|
||||
uniqueId()
|
||||
);
|
||||
const uint16_t dataLength = _serializer->calculateSize();
|
||||
|
||||
if (topicLength > 0 && dataLength > 0) {
|
||||
char topic[topicLength];
|
||||
HASerializer::generateConfigTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
uniqueId()
|
||||
);
|
||||
|
||||
if (mqtt()->beginPublish(topic, dataLength, true)) {
|
||||
_serializer->flush();
|
||||
mqtt()->endPublish();
|
||||
}
|
||||
}
|
||||
|
||||
destroySerializer();
|
||||
}
|
||||
|
||||
void HABaseDeviceType::publishAvailability()
|
||||
{
|
||||
const HADevice* device = mqtt()->getDevice();
|
||||
if (
|
||||
!device ||
|
||||
device->isSharedAvailabilityEnabled() ||
|
||||
!isAvailabilityConfigured()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishOnDataTopic(
|
||||
AHATOFSTR(HAAvailabilityTopic),
|
||||
_availability == AvailabilityOnline
|
||||
? AHATOFSTR(HAOnline)
|
||||
: AHATOFSTR(HAOffline),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HABaseDeviceType::publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const __FlashStringHelper* payload,
|
||||
bool retained
|
||||
)
|
||||
{
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
topic,
|
||||
reinterpret_cast<const uint8_t*>(payload),
|
||||
strlen_P(AHAFROMFSTR(payload)),
|
||||
retained,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HABaseDeviceType::publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const char* payload,
|
||||
bool retained
|
||||
)
|
||||
{
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
topic,
|
||||
reinterpret_cast<const uint8_t*>(payload),
|
||||
strlen(payload),
|
||||
retained
|
||||
);
|
||||
}
|
||||
|
||||
bool HABaseDeviceType::publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length,
|
||||
bool retained,
|
||||
bool isProgmemData
|
||||
)
|
||||
{
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t topicLength = HASerializer::calculateDataTopicLength(
|
||||
uniqueId(),
|
||||
topic
|
||||
);
|
||||
if (topicLength == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char fullTopic[topicLength];
|
||||
if (!HASerializer::generateDataTopic(
|
||||
fullTopic,
|
||||
uniqueId(),
|
||||
topic
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mqtt()->beginPublish(fullTopic, length, retained)) {
|
||||
if (isProgmemData) {
|
||||
mqtt()->writePayload(AHATOFSTR(payload));
|
||||
} else {
|
||||
mqtt()->writePayload(payload, length);
|
||||
}
|
||||
|
||||
return mqtt()->endPublish();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
#ifndef AHA_HABASEDEVICETYPE_H
|
||||
#define AHA_HABASEDEVICETYPE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "../ArduinoHADefines.h"
|
||||
|
||||
class HAMqtt;
|
||||
class HASerializer;
|
||||
|
||||
class HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
enum NumberPrecision {
|
||||
/// No digits after the decimal point.
|
||||
PrecisionP0 = 0,
|
||||
|
||||
/// One digit after the decimal point.
|
||||
PrecisionP1,
|
||||
|
||||
/// Two digits after the decimal point.
|
||||
PrecisionP2,
|
||||
|
||||
/// Three digits after the decimal point.
|
||||
PrecisionP3
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new device type instance and registers it in the HAMqtt class.
|
||||
*
|
||||
* @param componentName The name of the Home Assistant component (e.g. `binary_sensor`).
|
||||
* You can find all available component names in the Home Assistant documentation.
|
||||
* The component name needs to be stored in the flash memory.
|
||||
* @param uniqueId The unique ID of the device type. It needs to be unique in a scope of the HADevice.
|
||||
*/
|
||||
HABaseDeviceType(
|
||||
const __FlashStringHelper* componentName,
|
||||
const char* uniqueId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns unique ID of the device type.
|
||||
*/
|
||||
inline const char* uniqueId() const
|
||||
{ return _uniqueId; }
|
||||
|
||||
/**
|
||||
* Returns component name defined by the device type.
|
||||
* It's used for the MQTT discovery topic.
|
||||
*/
|
||||
inline const __FlashStringHelper* componentName() const
|
||||
{ return _componentName; }
|
||||
|
||||
/**
|
||||
* Returns `true` if the availability was configured for this device type.
|
||||
*/
|
||||
inline bool isAvailabilityConfigured() const
|
||||
{ return (_availability != AvailabilityDefault); }
|
||||
|
||||
/**
|
||||
* Returns online state of the device type.
|
||||
*/
|
||||
inline bool isOnline() const
|
||||
{ return (_availability == AvailabilityOnline); }
|
||||
|
||||
/**
|
||||
* Sets name of the device type that will be used as a label in the HA panel.
|
||||
* Keep the name short to save the resources.
|
||||
*
|
||||
* @param name The device type name.
|
||||
*/
|
||||
inline void setName(const char* name)
|
||||
{ _name = name; }
|
||||
|
||||
/**
|
||||
* Returns name of the deviced type that was assigned via setName method.
|
||||
* It can be nullptr if there is no name assigned.
|
||||
*/
|
||||
inline const char* getName() const
|
||||
{ return _name; }
|
||||
|
||||
/**
|
||||
* Sets availability of the device type.
|
||||
* Setting the initial availability enables availability reporting for this device type.
|
||||
* Please note that not all device types support this feature.
|
||||
* Follow HA documentation of a specific device type to get more information.
|
||||
*
|
||||
* @param online Specifies whether the device type is online.
|
||||
*/
|
||||
virtual void setAvailability(bool online);
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
inline HASerializer* getSerializer() const
|
||||
{ return _serializer; }
|
||||
|
||||
inline void buildSerializerTest()
|
||||
{ buildSerializer(); }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns instance of the HAMqtt class.
|
||||
*/
|
||||
static HAMqtt* mqtt();
|
||||
|
||||
/**
|
||||
* Subscribes to the given data topic.
|
||||
*
|
||||
* @param uniqueId THe unique ID of the device type assigned via the constructor.
|
||||
* @param topic Topic to subscribe (progmem string).
|
||||
*/
|
||||
static void subscribeTopic(
|
||||
const char* uniqueId,
|
||||
const __FlashStringHelper* topic
|
||||
);
|
||||
|
||||
/**
|
||||
* This method should build serializer that will be used for publishing the configuration.
|
||||
* The serializer is built each time the MQTT connection is acquired.
|
||||
* Follow implementation of the existing device types to get better understanding of the logic.
|
||||
*/
|
||||
virtual void buildSerializer() { };
|
||||
|
||||
/**
|
||||
* This method is called each time the MQTT connection is acquired.
|
||||
* Each device type should publish its configuration and availability.
|
||||
* It can be also used for subscribing to MQTT topics.
|
||||
*/
|
||||
virtual void onMqttConnected() = 0;
|
||||
|
||||
/**
|
||||
* This method is called each time the device receives a MQTT message.
|
||||
* It can be any MQTT message so the method should always verify the topic.
|
||||
*
|
||||
* @param topic The topic on which the message was produced.
|
||||
* @param payload The payload of the message. It can be nullptr.
|
||||
* @param length The length of the payload.
|
||||
*/
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
);
|
||||
|
||||
/**
|
||||
* Destroys the existing serializer.
|
||||
*/
|
||||
void destroySerializer();
|
||||
|
||||
/**
|
||||
* Publishes configuration of this device type on the HA discovery topic.
|
||||
*/
|
||||
void publishConfig();
|
||||
|
||||
/**
|
||||
* Publishes current availability of the device type.
|
||||
* The message is only produced if the availability is configured for this device type.
|
||||
*/
|
||||
void publishAvailability();
|
||||
|
||||
/**
|
||||
* Publishes the given flash string on the data topic.
|
||||
*
|
||||
* @param topic The topic to publish on (progmem string).
|
||||
* @param payload The message's payload (progmem string).
|
||||
* @param retained Specifies whether the message should be retained.
|
||||
*/
|
||||
bool publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const __FlashStringHelper* payload,
|
||||
bool retained = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Publishes the given string on the data topic.
|
||||
*
|
||||
* @param topic The topic to publish on (progmem string).
|
||||
* @param payload The message's payload.
|
||||
* @param retained Specifies whether the message should be retained.
|
||||
*/
|
||||
bool publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const char* payload,
|
||||
bool retained = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Publishes the given data on the data topic.
|
||||
*
|
||||
* @param topic The topic to publish on (progmem string).
|
||||
* @param payload The message's payload.
|
||||
* @param length The length of the payload.
|
||||
* @param retained Specifies whether the message should be retained.
|
||||
* @param isProgmemData Specifies whether the given data is stored in the flash memory.
|
||||
*/
|
||||
bool publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length,
|
||||
bool retained = false,
|
||||
bool isProgmemData = false
|
||||
);
|
||||
|
||||
/// The component name that was assigned via the constructor.
|
||||
const __FlashStringHelper* const _componentName;
|
||||
|
||||
/// The unique ID that was assigned via the constructor.
|
||||
const char* _uniqueId;
|
||||
|
||||
/// The name that was set using setName method. It can be nullptr.
|
||||
const char* _name;
|
||||
|
||||
/// HASerializer that belongs to this device type. It can be nullptr.
|
||||
HASerializer* _serializer;
|
||||
|
||||
private:
|
||||
enum Availability {
|
||||
AvailabilityDefault = 0,
|
||||
AvailabilityOnline,
|
||||
AvailabilityOffline
|
||||
};
|
||||
|
||||
/// The current availability of this device type. AvailabilityDefault means that the initial availability was never set.
|
||||
Availability _availability;
|
||||
friend class HAMqtt;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "HABinarySensor.h"
|
||||
#ifndef EX_ARDUINOHA_BINARY_SENSOR
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HABinarySensor::HABinarySensor(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentBinarySensor), uniqueId),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_currentState(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HABinarySensor::setState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HABinarySensor::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 7); // 7 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
}
|
||||
|
||||
void HABinarySensor::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
bool HABinarySensor::publishState(const bool state)
|
||||
{
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,90 @@
|
||||
#ifndef AHA_HABINARYSENSOR_H
|
||||
#define AHA_HABINARYSENSOR_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_BINARY_SENSOR
|
||||
|
||||
/**
|
||||
* HABinarySensor represents a binary sensor that allows publishing on/off state to the Home Assistant panel.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/binary_sensor.mqtt/
|
||||
*/
|
||||
class HABinarySensor : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the button. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HABinarySensor(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Changes state of the sensor and publish MQTT message.
|
||||
* Please note that if a new value is the same as the previous one the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the sensor (`true` - on, `false` - off).
|
||||
* @param force Forces to update the state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets the current state of the sensor without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the state before the connection with the MQTT broker is acquired.
|
||||
*
|
||||
* @param state New state of the sensor.
|
||||
*/
|
||||
inline void setCurrentState(const bool state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns the last known state of the sensor.
|
||||
*/
|
||||
inline bool getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/binary_sensor/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the sensor.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(bool state);
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the sensor. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// Current state of the sensor. By default it's false.
|
||||
bool _currentState;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
72
lib/home-assistant-integration/src/device-types/HAButton.cpp
Normal file
72
lib/home-assistant-integration/src/device-types/HAButton.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "HAButton.h"
|
||||
#ifndef EX_ARDUINOHA_BUTTON
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HAButton::HAButton(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentButton), uniqueId),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HAButton::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 8); // 8 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
// optional property
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAButton::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAButton::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
|
||||
if (_commandCallback && HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
_commandCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
86
lib/home-assistant-integration/src/device-types/HAButton.h
Normal file
86
lib/home-assistant-integration/src/device-types/HAButton.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#ifndef AHA_HABUTTON_H
|
||||
#define AHA_HABUTTON_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_BUTTON
|
||||
|
||||
#define HABUTTON_CALLBACK(name) void (*name)(HAButton* sender)
|
||||
|
||||
/**
|
||||
* HAButton represents a button that's displayed in the Home Assistant panel and
|
||||
* triggers some logic on your Arduino/ESP device once clicked.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/button.mqtt/
|
||||
*/
|
||||
class HAButton : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the button. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HAButton(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/button/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the button.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the button's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the press command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same button.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onCommand(HABUTTON_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The command callback that will be called once clicking the button in HA panel.
|
||||
HABUTTON_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
65
lib/home-assistant-integration/src/device-types/HACamera.cpp
Normal file
65
lib/home-assistant-integration/src/device-types/HACamera.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "HACamera.h"
|
||||
#ifndef EX_ARDUINOHA_CAMERA
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HACamera::HACamera(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentCamera), uniqueId),
|
||||
_encoding(EncodingBinary),
|
||||
_icon(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HACamera::publishImage(const uint8_t* data, const uint16_t length)
|
||||
{
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HATopic), data, length, true);
|
||||
}
|
||||
|
||||
void HACamera::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 7); // 7 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAEncodingProperty),
|
||||
getEncodingProperty(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HATopic));
|
||||
}
|
||||
|
||||
void HACamera::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HACamera::getEncodingProperty() const
|
||||
{
|
||||
switch (_encoding) {
|
||||
case EncodingBase64:
|
||||
return AHATOFSTR(HAEncodingBase64);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
77
lib/home-assistant-integration/src/device-types/HACamera.h
Normal file
77
lib/home-assistant-integration/src/device-types/HACamera.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef AHA_HACAMERA_H
|
||||
#define AHA_HACAMERA_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_CAMERA
|
||||
|
||||
/**
|
||||
* HACamera allows to display an image in the Home Assistant panel.
|
||||
* It can be used for publishing an image from the ESP32-Cam module or any other
|
||||
* module that's equipped with a camera.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/camera.mqtt/
|
||||
*/
|
||||
class HACamera : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
enum ImageEncoding {
|
||||
EncodingBinary = 1,
|
||||
EncodingBase64
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the camera. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HACamera(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Publishes MQTT message with the given image data as a message content.
|
||||
* It updates image displayed in the Home Assistant panel.
|
||||
*
|
||||
* @param data Image data (raw binary data or base64)
|
||||
* @param length The length of the data.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishImage(const uint8_t* data, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Sets encoding of the image content.
|
||||
* Bu default Home Assistant expects raw binary data (e.g. JPEG binary data).
|
||||
*
|
||||
* @param encoding The image's data encoding.
|
||||
*/
|
||||
inline void setEncoding(const ImageEncoding encoding)
|
||||
{ _encoding = encoding; }
|
||||
|
||||
/**
|
||||
* Sets icon of the camera.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns progmem string representing the encoding property.
|
||||
*/
|
||||
const __FlashStringHelper* getEncodingProperty() const;
|
||||
|
||||
/// The encoding of the image's data. By default it's `HACamera::EncodingBinary`.
|
||||
ImageEncoding _encoding;
|
||||
|
||||
/// The icon of the camera. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
183
lib/home-assistant-integration/src/device-types/HACover.cpp
Normal file
183
lib/home-assistant-integration/src/device-types/HACover.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "HACover.h"
|
||||
#ifndef EX_ARDUINOHA_COVER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HACover::HACover(const char* uniqueId, const Features features) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentCover), uniqueId),
|
||||
_features(features),
|
||||
_currentState(StateUnknown),
|
||||
_currentPosition(DefaultPosition),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HACover::setState(const CoverState state, const bool force)
|
||||
{
|
||||
if (!force && _currentState == state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HACover::setPosition(const int16_t position, const bool force)
|
||||
{
|
||||
if (!force && _currentPosition == position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishPosition(position)) {
|
||||
_currentPosition = position;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HACover::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 11); // 11 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
|
||||
if (_features & PositionFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAPositionTopic));
|
||||
}
|
||||
}
|
||||
|
||||
void HACover::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
publishPosition(_currentPosition);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HACover::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HACover::publishState(CoverState state)
|
||||
{
|
||||
if (state == StateUnknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (state) {
|
||||
case StateClosed:
|
||||
stateStr = AHATOFSTR(HAClosedState);
|
||||
break;
|
||||
|
||||
case StateClosing:
|
||||
stateStr = AHATOFSTR(HAClosingState);
|
||||
break;
|
||||
|
||||
case StateOpen:
|
||||
stateStr = AHATOFSTR(HAOpenState);
|
||||
break;
|
||||
|
||||
case StateOpening:
|
||||
stateStr = AHATOFSTR(HAOpeningState);
|
||||
break;
|
||||
|
||||
case StateStopped:
|
||||
stateStr = AHATOFSTR(HAStoppedState);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), stateStr, true);
|
||||
}
|
||||
|
||||
bool HACover::publishPosition(int16_t position)
|
||||
{
|
||||
if (position == DefaultPosition || !(_features & PositionFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[6 + 1] = {0}; // int16_t digits with null terminator
|
||||
HANumeric(position, 0).toStr(str);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAPositionTopic), str, true);
|
||||
}
|
||||
|
||||
void HACover::handleCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_commandCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HACloseCommand, length) == 0) {
|
||||
_commandCallback(CommandClose, this);
|
||||
} else if (memcmp_P(cmd, HAOpenCommand, length) == 0) {
|
||||
_commandCallback(CommandOpen, this);
|
||||
} else if (memcmp_P(cmd, HAStopCommand, length) == 0) {
|
||||
_commandCallback(CommandStop, this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
210
lib/home-assistant-integration/src/device-types/HACover.h
Normal file
210
lib/home-assistant-integration/src/device-types/HACover.h
Normal file
@@ -0,0 +1,210 @@
|
||||
#ifndef AHA_HACOVER_H
|
||||
#define AHA_HACOVER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_COVER
|
||||
|
||||
#define HACOVER_CALLBACK(name) void (*name)(CoverCommand cmd, HACover* sender)
|
||||
|
||||
/**
|
||||
* HACover allows to control a cover (such as blinds, a roller shutter or a garage door).
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/cover.mqtt/
|
||||
*/
|
||||
class HACover : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
static const int16_t DefaultPosition = -32768;
|
||||
|
||||
enum CoverState {
|
||||
StateUnknown = 0,
|
||||
StateClosed,
|
||||
StateClosing,
|
||||
StateOpen,
|
||||
StateOpening,
|
||||
StateStopped
|
||||
};
|
||||
|
||||
enum CoverCommand {
|
||||
CommandOpen,
|
||||
CommandClose,
|
||||
CommandStop
|
||||
};
|
||||
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
PositionFeature = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the cover. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the fan.
|
||||
*/
|
||||
HACover(const char* uniqueId, const Features features = DefaultFeatures);
|
||||
|
||||
/**
|
||||
* 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(const CoverState state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the 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 The new position of the cover (0-100).
|
||||
* @param force Forces to update the state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setPosition(const int16_t position, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets the current state of the cover without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the state before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param state The new state of the cover.
|
||||
*/
|
||||
inline void setCurrentState(const CoverState state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the cover.
|
||||
* By default the state is set to CoverState::StateUnknown
|
||||
*/
|
||||
inline CoverState getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets the current position of the cover without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the position before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param position The new position of the cover (0-100).
|
||||
*/
|
||||
inline void setCurrentPosition(const int16_t position)
|
||||
{ _currentPosition = position; }
|
||||
|
||||
/**
|
||||
* Returns the last known position of the cover.
|
||||
* By default position is set to HACover::DefaultPosition
|
||||
*/
|
||||
inline int16_t getCurrentPosition() const
|
||||
{ return _currentPosition; }
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/cover/
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the cover.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the cover's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the cover state.
|
||||
* In this mode the cover state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* 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 cover.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onCommand(HACOVER_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const CoverState state);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given position.
|
||||
*
|
||||
* @param position The position to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishPosition(const int16_t position);
|
||||
|
||||
/**
|
||||
* Parses the given command and executes the cover's callback with proper enum's property.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/// Features enabled for the cover.
|
||||
const uint8_t _features;
|
||||
|
||||
/// The current state of the cover. By default it's `HACover::StateUnknown`.
|
||||
CoverState _currentState;
|
||||
|
||||
/// The current position of the cover. By default it's `HACover::DefaultPosition`.
|
||||
int16_t _currentPosition;
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the cover (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The command callback that will be called when clicking the cover's button in the HA panel.
|
||||
HACOVER_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "HADeviceTracker.h"
|
||||
#ifndef EX_ARDUINOHA_DEVICE_TRACKER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HADeviceTracker::HADeviceTracker(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceTracker), uniqueId),
|
||||
_icon(nullptr),
|
||||
_sourceType(SourceTypeUnknown),
|
||||
_currentState(StateUnknown)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HADeviceTracker::setState(const TrackerState state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HADeviceTracker::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 7); // 7 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASourceTypeProperty),
|
||||
getSourceTypeProperty(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
}
|
||||
|
||||
void HADeviceTracker::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
bool HADeviceTracker::publishState(const TrackerState state)
|
||||
{
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (state) {
|
||||
case StateHome:
|
||||
stateStr = AHATOFSTR(HAHome);
|
||||
break;
|
||||
|
||||
case StateNotHome:
|
||||
stateStr = AHATOFSTR(HANotHome);
|
||||
break;
|
||||
|
||||
case StateNotAvailable:
|
||||
stateStr = AHATOFSTR(HAOffline);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), stateStr, true);
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HADeviceTracker::getSourceTypeProperty() const
|
||||
{
|
||||
switch (_sourceType) {
|
||||
case SourceTypeGPS:
|
||||
return AHATOFSTR(HAGPSType);
|
||||
|
||||
case SourceTypeRouter:
|
||||
return AHATOFSTR(HARouterType);
|
||||
|
||||
case SourceTypeBluetooth:
|
||||
return AHATOFSTR(HABluetoothType);
|
||||
|
||||
case SourceTypeBluetoothLE:
|
||||
return AHATOFSTR(HABluetoothLEType);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,114 @@
|
||||
#ifndef AHA_HADEVICETRACKER_H
|
||||
#define AHA_HADEVICETRACKER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_DEVICE_TRACKER
|
||||
|
||||
/**
|
||||
* HADeviceTracker allows to implement a custom device's tracker.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/device_tracker.mqtt/
|
||||
*/
|
||||
class HADeviceTracker : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/// Available source types of the tracker.
|
||||
enum SourceType {
|
||||
SourceTypeUnknown = 0,
|
||||
SourceTypeGPS,
|
||||
SourceTypeRouter,
|
||||
SourceTypeBluetooth,
|
||||
SourceTypeBluetoothLE
|
||||
};
|
||||
|
||||
/// Available states that can be reported to the HA panel.
|
||||
enum TrackerState {
|
||||
StateUnknown = 0,
|
||||
StateHome,
|
||||
StateNotHome,
|
||||
StateNotAvailable
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the tracker. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HADeviceTracker(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Changes the state of the tracker 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 The new state of the tracker.
|
||||
* @param force Forces to update the state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const TrackerState state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets the current state of the tracker without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state The new state of the tracker.
|
||||
*/
|
||||
inline void setCurrentState(const TrackerState state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns the last known state of the tracker.
|
||||
* If setState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline TrackerState getState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets icon of the tracker.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets the source type of the tracker.
|
||||
*
|
||||
* @param type The source type (gps, router, bluetooth, bluetooth LE).
|
||||
*/
|
||||
inline void setSourceType(const SourceType type)
|
||||
{ _sourceType = type; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(TrackerState state);
|
||||
|
||||
/**
|
||||
* Returns progmem string representing source type of the tracker.
|
||||
*/
|
||||
const __FlashStringHelper* getSourceTypeProperty() const;
|
||||
|
||||
/// The icon of the tracker. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The source type of the tracker. By default it's `HADeviceTracker::SourceTypeUnknown`.
|
||||
SourceType _sourceType;
|
||||
|
||||
/// The current state of the device's tracker. By default its `HADeviceTracker::StateUnknown`.
|
||||
TrackerState _currentState;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,209 @@
|
||||
#include "HADeviceTrigger.h"
|
||||
#ifndef EX_ARDUINOHA_DEVICE_TRIGGER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HADeviceTrigger::HADeviceTrigger(const char* type, const char* subtype) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceAutomation), nullptr),
|
||||
_type(type),
|
||||
_subtype(subtype),
|
||||
_isProgmemType(false),
|
||||
_isProgmemSubtype(false)
|
||||
{
|
||||
buildUniqueId();
|
||||
}
|
||||
|
||||
HADeviceTrigger::HADeviceTrigger(TriggerType type, const char* subtype) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceAutomation), nullptr),
|
||||
_type(determineProgmemType(type)),
|
||||
_subtype(subtype),
|
||||
_isProgmemType(true),
|
||||
_isProgmemSubtype(false)
|
||||
{
|
||||
buildUniqueId();
|
||||
}
|
||||
|
||||
HADeviceTrigger::HADeviceTrigger(const char* type, TriggerSubtype subtype) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceAutomation), nullptr),
|
||||
_type(type),
|
||||
_subtype(determineProgmemSubtype(subtype)),
|
||||
_isProgmemType(false),
|
||||
_isProgmemSubtype(true)
|
||||
{
|
||||
buildUniqueId();
|
||||
}
|
||||
|
||||
HADeviceTrigger::HADeviceTrigger(TriggerType type, TriggerSubtype subtype) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceAutomation), nullptr),
|
||||
_type(determineProgmemType(type)),
|
||||
_subtype(determineProgmemSubtype(subtype)),
|
||||
_isProgmemType(true),
|
||||
_isProgmemSubtype(true)
|
||||
{
|
||||
buildUniqueId();
|
||||
}
|
||||
|
||||
HADeviceTrigger::~HADeviceTrigger()
|
||||
{
|
||||
if (_uniqueId) {
|
||||
delete _uniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
bool HADeviceTrigger::trigger()
|
||||
{
|
||||
if (!_type || !_subtype) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HATopic), "");
|
||||
}
|
||||
|
||||
void HADeviceTrigger::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 5); // 5 - max properties nb
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAAutomationTypeProperty),
|
||||
AHATOFSTR(HATrigger),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HATypeProperty),
|
||||
_type,
|
||||
_isProgmemType
|
||||
? HASerializer::ProgmemPropertyValue
|
||||
: HASerializer::ConstCharPropertyValue
|
||||
);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASubtypeProperty),
|
||||
_subtype,
|
||||
_isProgmemSubtype
|
||||
? HASerializer::ProgmemPropertyValue
|
||||
: HASerializer::ConstCharPropertyValue
|
||||
);
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->topic(AHATOFSTR(HATopic));
|
||||
}
|
||||
|
||||
void HADeviceTrigger::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
uint16_t HADeviceTrigger::calculateIdSize() const
|
||||
{
|
||||
if (!_type || !_subtype) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint16_t typeSize = _isProgmemType ? strlen_P(_type) : strlen(_type);
|
||||
const uint16_t subtypeSize = _isProgmemSubtype
|
||||
? strlen_P(_subtype)
|
||||
: strlen(_subtype);
|
||||
|
||||
// plus underscore separator and null terminator
|
||||
return typeSize + subtypeSize + 2;
|
||||
}
|
||||
|
||||
void HADeviceTrigger::buildUniqueId()
|
||||
{
|
||||
const uint16_t idSize = calculateIdSize();
|
||||
if (idSize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* id = new char[idSize];
|
||||
|
||||
if (_isProgmemType) {
|
||||
strcpy_P(id, _type);
|
||||
} else {
|
||||
strcpy(id, _type);
|
||||
}
|
||||
|
||||
strcat_P(id, HASerializerUnderscore);
|
||||
|
||||
if (_isProgmemSubtype) {
|
||||
strcat_P(id, _subtype);
|
||||
} else {
|
||||
strcat(id, _subtype);
|
||||
}
|
||||
|
||||
_uniqueId = id;
|
||||
}
|
||||
|
||||
const char* HADeviceTrigger::determineProgmemType(TriggerType type) const
|
||||
{
|
||||
switch (type) {
|
||||
case ButtonShortPressType:
|
||||
return HAButtonShortPressType;
|
||||
|
||||
case ButtonShortReleaseType:
|
||||
return HAButtonShortReleaseType;
|
||||
|
||||
case ButtonLongPressType:
|
||||
return HAButtonLongPressType;
|
||||
|
||||
case ButtonLongReleaseType:
|
||||
return HAButtonLongReleaseType;
|
||||
|
||||
case ButtonDoublePressType:
|
||||
return HAButtonDoublePressType;
|
||||
|
||||
case ButtonTriplePressType:
|
||||
return HAButtonTriplePressType;
|
||||
|
||||
case ButtonQuadruplePressType:
|
||||
return HAButtonQuadruplePressType;
|
||||
|
||||
case ButtonQuintuplePressType:
|
||||
return HAButtonQuintuplePressType;
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const char* HADeviceTrigger::determineProgmemSubtype(
|
||||
TriggerSubtype subtype
|
||||
) const
|
||||
{
|
||||
switch (subtype) {
|
||||
case TurnOnSubtype:
|
||||
return HATurnOnSubtype;
|
||||
|
||||
case TurnOffSubtype:
|
||||
return HATurnOffSubtype;
|
||||
|
||||
case Button1Subtype:
|
||||
return HAButton1Subtype;
|
||||
|
||||
case Button2Subtype:
|
||||
return HAButton2Subtype;
|
||||
|
||||
case Button3Subtype:
|
||||
return HAButton3Subtype;
|
||||
|
||||
case Button4Subtype:
|
||||
return HAButton4Subtype;
|
||||
|
||||
case Button5Subtype:
|
||||
return HAButton5Subtype;
|
||||
|
||||
case Button6Subtype:
|
||||
return HAButton6Subtype;
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,176 @@
|
||||
#ifndef AHA_HADEVICETRIGGER_H
|
||||
#define AHA_HADEVICETRIGGER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_DEVICE_TRIGGER
|
||||
|
||||
/**
|
||||
* HADeviceTrigger allows to a custom trigger that can be used in the Home Assistant automation.
|
||||
* For example, it can be a wall switch that produces `press` and `long_press` actions.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/device_trigger.mqtt/
|
||||
*/
|
||||
class HADeviceTrigger : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/// Built-in types of the trigger.
|
||||
enum TriggerType {
|
||||
ButtonShortPressType = 1,
|
||||
ButtonShortReleaseType,
|
||||
ButtonLongPressType,
|
||||
ButtonLongReleaseType,
|
||||
ButtonDoublePressType,
|
||||
ButtonTriplePressType,
|
||||
ButtonQuadruplePressType,
|
||||
ButtonQuintuplePressType
|
||||
};
|
||||
|
||||
/// Built-in subtypes of the trigger.
|
||||
enum TriggerSubtype {
|
||||
TurnOnSubtype = 1,
|
||||
TurnOffSubtype,
|
||||
Button1Subtype,
|
||||
Button2Subtype,
|
||||
Button3Subtype,
|
||||
Button4Subtype,
|
||||
Button5Subtype,
|
||||
Button6Subtype
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the device trigger with a custom type and subtype.
|
||||
* For example, it can be `click` as the type and `btn0` as the subtype.
|
||||
* Please note that combination of the type and subtype needs to be unique in a scope of your device.
|
||||
*
|
||||
* @param type String representation of the type.
|
||||
* @param subtype String representation of the subtype.
|
||||
*/
|
||||
HADeviceTrigger(const char* type, const char* subtype);
|
||||
|
||||
/**
|
||||
* Creates the device trigger with a built-in type and a custom subtype.
|
||||
* For example, it can be `HADeviceTrigger::ButtonShortPressType` as the type and `btn0` as the subtype.
|
||||
* Please note that combination of the type and subtype needs to be unique in a scope of your device.
|
||||
*
|
||||
* @param type Built-in type of the trigger.
|
||||
* @param subtype String representation of the subtype.
|
||||
*/
|
||||
HADeviceTrigger(TriggerType type, const char* subtype);
|
||||
|
||||
/**
|
||||
* Creates the device trigger with a custom type and a built-in subtype.
|
||||
* For example, it can be `click` as the type and `HADeviceTrigger::Button1Subtype` as the subtype.
|
||||
* Please note that combination of the type and subtype needs to be unique in a scope of your device.
|
||||
*
|
||||
* @param type String representation of the subtype.
|
||||
* @param subtype Built-in subtype of the trigger.
|
||||
*/
|
||||
HADeviceTrigger(const char* type, TriggerSubtype subtype);
|
||||
|
||||
/**
|
||||
* Creates the device trigger with a built-in type and built-in subtype.
|
||||
* For example, it can be `HADeviceTrigger::ButtonShortPressType` as the type and `HADeviceTrigger::Button1Subtype` as the subtype.
|
||||
* Please note that combination of the type and subtype needs to be unique in a scope of your device.
|
||||
*
|
||||
* @param type Built-in type of the trigger.
|
||||
* @param subtype Built-in subtype of the trigger.
|
||||
*/
|
||||
HADeviceTrigger(TriggerType type, TriggerSubtype subtype);
|
||||
|
||||
/**
|
||||
* Frees memory allocated by the class.
|
||||
*/
|
||||
~HADeviceTrigger();
|
||||
|
||||
/**
|
||||
* Publishes MQTT message with the trigger event.
|
||||
* The published message is not retained.
|
||||
*
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool trigger();
|
||||
|
||||
/**
|
||||
* Returns the type of the trigger.
|
||||
* If the built-in type is used the returned value points to the flash memory.
|
||||
* Use `HADeviceTrigger::isProgmemType` to verify if the returned value is the progmem pointer.
|
||||
*
|
||||
* @returns Pointer to the type.
|
||||
*/
|
||||
inline const char* getType() const
|
||||
{ return _type; }
|
||||
|
||||
/**
|
||||
* Returns `true` if the built-in type was assigned to the trigger.
|
||||
*/
|
||||
inline bool isProgmemType() const
|
||||
{ return _isProgmemType; }
|
||||
|
||||
/**
|
||||
* Returns the subtype of the trigger.
|
||||
* If the built-in subtype is used the returned value points to the flash memory.
|
||||
* Use `HADeviceTrigger::isProgmemSubtype` to verify if the returned value is the progmem pointer.
|
||||
*
|
||||
* @returns Pointer to the subtype.
|
||||
*/
|
||||
inline const char* getSubtype() const
|
||||
{ return _subtype; }
|
||||
|
||||
/**
|
||||
* Returns `true` if the built-in subtype was assigned to the trigger.
|
||||
*/
|
||||
inline bool isProgmemSubtype() const
|
||||
{ return _isProgmemSubtype; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Calculates desired size of the unique ID based on the type and subtype that were passed to the constructor.
|
||||
*/
|
||||
uint16_t calculateIdSize() const;
|
||||
|
||||
/**
|
||||
* Builds the unique ID of the device's type based on the type and subtype that were passed to the constructor.
|
||||
*/
|
||||
void buildUniqueId();
|
||||
|
||||
/**
|
||||
* Returns pointer to the flash memory that represents the given type.
|
||||
*
|
||||
* @param subtype Built-in type enum's value.
|
||||
* @returns Pointer to the flash memory if the given type is supported.
|
||||
* For the unsupported type the nullptr is returned.
|
||||
*/
|
||||
const char* determineProgmemType(TriggerType type) const;
|
||||
|
||||
/**
|
||||
* Returns pointer to the flash memory that represents the given subtype.
|
||||
*
|
||||
* @param subtype Built-in subtype enum's value.
|
||||
* @returns Pointer to the flash memory if the given subtype is supported.
|
||||
* For the unsupported subtype the nullptr is returned.
|
||||
*/
|
||||
const char* determineProgmemSubtype(TriggerSubtype subtype) const;
|
||||
|
||||
private:
|
||||
/// Pointer to the trigger's type. It can be pointer to the flash memory.
|
||||
const char* _type;
|
||||
|
||||
/// Pointer to the trigger's subtype. It can be pointer to the flash memory.
|
||||
const char* _subtype;
|
||||
|
||||
/// Specifies whether the type points to the flash memory.
|
||||
bool _isProgmemType;
|
||||
|
||||
/// Specifies whether the subtype points to the flash memory.
|
||||
bool _isProgmemSubtype;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
193
lib/home-assistant-integration/src/device-types/HAFan.cpp
Normal file
193
lib/home-assistant-integration/src/device-types/HAFan.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#include "HAFan.h"
|
||||
#ifndef EX_ARDUINOHA_FAN
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HAFan::HAFan(const char* uniqueId, const uint8_t features) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentFan), uniqueId),
|
||||
_features(features),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_speedRangeMax(),
|
||||
_speedRangeMin(),
|
||||
_currentState(false),
|
||||
_currentSpeed(0),
|
||||
_stateCallback(nullptr),
|
||||
_speedCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HAFan::setState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAFan::setSpeed(const uint16_t speed, const bool force)
|
||||
{
|
||||
if (!force && speed == _currentSpeed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishSpeed(speed)) {
|
||||
_currentSpeed = speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HAFan::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 13); // 13 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_features & SpeedsFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAPercentageStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HAPercentageCommandTopic));
|
||||
|
||||
if (_speedRangeMax.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASpeedRangeMaxProperty),
|
||||
&_speedRangeMax,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_speedRangeMin.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASpeedRangeMinProperty),
|
||||
&_speedRangeMin,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAFan::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
publishSpeed(_currentSpeed);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
|
||||
if (_features & SpeedsFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAPercentageCommandTopic));
|
||||
}
|
||||
}
|
||||
|
||||
void HAFan::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleStateCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAPercentageCommandTopic)
|
||||
)) {
|
||||
handleSpeedCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HAFan::publishState(const bool state)
|
||||
{
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAFan::publishSpeed(const uint16_t speed)
|
||||
{
|
||||
if (!(_features & SpeedsFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[5 + 1] = {0}; // uint16_t digits with null terminator
|
||||
HANumeric(speed, 0).toStr(str);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAPercentageStateTopic), str, true);
|
||||
}
|
||||
|
||||
void HAFan::handleStateCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
if (!_stateCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_stateCallback(state, this);
|
||||
}
|
||||
|
||||
void HAFan::handleSpeedCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_speedCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HANumeric& number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isUInt16()) {
|
||||
_speedCallback(number.toUInt16(), this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
245
lib/home-assistant-integration/src/device-types/HAFan.h
Normal file
245
lib/home-assistant-integration/src/device-types/HAFan.h
Normal file
@@ -0,0 +1,245 @@
|
||||
#ifndef AHA_HAFAN_H
|
||||
#define AHA_HAFAN_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_FAN
|
||||
|
||||
#define HAFAN_STATE_CALLBACK(name) void (*name)(bool state, HAFan* sender)
|
||||
#define HAFAN_SPEED_CALLBACK(name) void (*name)(uint16_t speed, HAFan* sender)
|
||||
|
||||
/**
|
||||
* HAFan allows adding a controllable fan in the Home Assistant panel.
|
||||
* The library supports only the state and speed of the fan.
|
||||
* If you want more features please open a new GitHub issue.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/fan.mqtt/
|
||||
*/
|
||||
class HAFan : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
SpeedsFeature = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the fan. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the fan.
|
||||
*/
|
||||
HAFan(const char* uniqueId, const uint8_t features = DefaultFeatures);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the speed 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 speed The new speed of the fan. It should be in range of min and max value.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setSpeed(const uint16_t speed, const bool force = false);
|
||||
|
||||
/**
|
||||
* Alias for `setState(true)`.
|
||||
*/
|
||||
inline bool turnOn()
|
||||
{ return setState(true); }
|
||||
|
||||
/**
|
||||
* Alias for `setState(false)`.
|
||||
*/
|
||||
inline bool turnOff()
|
||||
{ return setState(false); }
|
||||
|
||||
/**
|
||||
* Sets current state of the fan without publishing it 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 fan.
|
||||
*/
|
||||
inline void setCurrentState(const bool state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the fan.
|
||||
* By default it's `false`.
|
||||
*/
|
||||
inline bool getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets the current speed of the fan without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the speed before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param speed The new speed of the fan. It should be in range of min and max value.
|
||||
*/
|
||||
inline void setCurrentSpeed(const uint16_t speed)
|
||||
{ _currentSpeed = speed; }
|
||||
|
||||
/**
|
||||
* Returns the last known speed of the fan.
|
||||
* By default speed is set to `0`.
|
||||
*/
|
||||
inline uint16_t getCurrentSpeed() const
|
||||
{ return _currentSpeed; }
|
||||
|
||||
/**
|
||||
* Sets icon of the fan.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the fan's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the fan state.
|
||||
* In this mode the fan state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Sets the maximum of numeric output range (representing 100%).
|
||||
* The number of speeds within the speed_range / 100 will determine the percentage step.
|
||||
* By default the maximum range is `100`.
|
||||
*
|
||||
* @param max The maximum of numeric output range.
|
||||
*/
|
||||
inline void setSpeedRangeMax(const uint16_t max)
|
||||
{ _speedRangeMax.setBaseValue(max); }
|
||||
|
||||
/**
|
||||
* Sets the minimum of numeric output range (off is not included, so speed_range_min - 1 represents 0 %).
|
||||
* The number of speeds within the speed_range / 100 will determine the percentage step.
|
||||
* By default the minimum range is `1`.
|
||||
*
|
||||
* @param min The minimum of numeric output range.
|
||||
*/
|
||||
inline void setSpeedRangeMin(const uint16_t min)
|
||||
{ _speedRangeMin.setBaseValue(min); }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the state command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same fan.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the state must be reported back to HA using the HAFan::setState method.
|
||||
*/
|
||||
inline void onStateCommand(HAFAN_STATE_CALLBACK(callback))
|
||||
{ _stateCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the speed command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same fan.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the speed must be reported back to HA using the HAFan::setSpeed method.
|
||||
*/
|
||||
inline void onSpeedCommand(HAFAN_SPEED_CALLBACK(callback))
|
||||
{ _speedCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const bool state);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given speed.
|
||||
*
|
||||
* @param speed The speed to publish. It should be in range of min and max value.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishSpeed(const uint16_t speed);
|
||||
|
||||
/**
|
||||
* Parses the given state command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleStateCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given speed command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleSpeedCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/// Features enabled for the fan.
|
||||
const uint8_t _features;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the fan (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The maximum of numeric output range.
|
||||
HANumeric _speedRangeMax;
|
||||
|
||||
/// The minimum of numeric output range.
|
||||
HANumeric _speedRangeMin;
|
||||
|
||||
/// The current state of the fan. By default it's `false`.
|
||||
bool _currentState;
|
||||
|
||||
/// The current speed of the fan. By default it's `0`.
|
||||
uint16_t _currentSpeed;
|
||||
|
||||
/// The callback that will be called when the state command is received from the HA.
|
||||
HAFAN_STATE_CALLBACK(_stateCallback);
|
||||
|
||||
/// The callback that will be called when the speed command is received from the HA.
|
||||
HAFAN_SPEED_CALLBACK(_speedCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
755
lib/home-assistant-integration/src/device-types/HAHVAC.cpp
Normal file
755
lib/home-assistant-integration/src/device-types/HAHVAC.cpp
Normal file
@@ -0,0 +1,755 @@
|
||||
#include "HAHVAC.h"
|
||||
#ifndef EX_ARDUINOHA_HVAC
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
const uint8_t HAHVAC::DefaultFanModes = AutoFanMode | LowFanMode | MediumFanMode | HighFanMode;
|
||||
const uint8_t HAHVAC::DefaultSwingModes = OnSwingMode | OffSwingMode;
|
||||
const uint8_t HAHVAC::DefaultModes = AutoMode | OffMode | CoolMode | HeatMode | DryMode | FanOnlyMode;
|
||||
|
||||
HAHVAC::HAHVAC(
|
||||
const char* uniqueId,
|
||||
const uint16_t features,
|
||||
const NumberPrecision precision
|
||||
) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentClimate), uniqueId),
|
||||
_features(features),
|
||||
_precision(precision),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_CURRENT_TEMPerature(),
|
||||
_action(UnknownAction),
|
||||
_temperatureUnit(DefaultUnit),
|
||||
_minTemp(),
|
||||
_maxTemp(),
|
||||
_tempStep(),
|
||||
_auxCallback(nullptr),
|
||||
_auxState(false),
|
||||
_powerCallback(nullptr),
|
||||
_fanMode(UnknownFanMode),
|
||||
_fanModes(DefaultFanModes),
|
||||
_fanModesSerializer(nullptr),
|
||||
_fanModeCallback(nullptr),
|
||||
_swingMode(UnknownSwingMode),
|
||||
_swingModes(DefaultSwingModes),
|
||||
_swingModesSerializer(nullptr),
|
||||
_swingModeCallback(nullptr),
|
||||
_mode(UnknownMode),
|
||||
_modes(DefaultModes),
|
||||
_modesSerializer(nullptr),
|
||||
_modeCallback(nullptr),
|
||||
_targetTemperature(),
|
||||
_targetTemperatureCallback(nullptr)
|
||||
{
|
||||
if (_features & FanFeature) {
|
||||
_fanModesSerializer = new HASerializerArray(4);
|
||||
}
|
||||
|
||||
if (_features & SwingFeature) {
|
||||
_swingModesSerializer = new HASerializerArray(2);
|
||||
}
|
||||
|
||||
if (_features & ModesFeature) {
|
||||
_modesSerializer = new HASerializerArray(6);
|
||||
}
|
||||
}
|
||||
|
||||
HAHVAC::~HAHVAC()
|
||||
{
|
||||
if (_fanModesSerializer) {
|
||||
delete _fanModesSerializer;
|
||||
}
|
||||
|
||||
if (_swingModesSerializer) {
|
||||
delete _swingModesSerializer;
|
||||
}
|
||||
|
||||
if (_modesSerializer) {
|
||||
delete _modesSerializer;
|
||||
}
|
||||
}
|
||||
|
||||
bool HAHVAC::setCurrentTemperature(const HANumeric& temperature, const bool force)
|
||||
{
|
||||
if (temperature.getPrecision() != _precision) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!force && temperature == _CURRENT_TEMPerature) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishCurrentTemperature(temperature)) {
|
||||
_CURRENT_TEMPerature = temperature;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setAction(const Action action, const bool force)
|
||||
{
|
||||
if (!force && action == _action) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishAction(action)) {
|
||||
_action = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setAuxState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _auxState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishAuxState(state)) {
|
||||
_auxState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setFanMode(const FanMode mode, const bool force)
|
||||
{
|
||||
if (!force && mode == _fanMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishFanMode(mode)) {
|
||||
_fanMode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setSwingMode(const SwingMode mode, const bool force)
|
||||
{
|
||||
if (!force && mode == _swingMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishSwingMode(mode)) {
|
||||
_swingMode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setMode(const Mode mode, const bool force)
|
||||
{
|
||||
if (!force && mode == _mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishMode(mode)) {
|
||||
_mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setTargetTemperature(const HANumeric& temperature, const bool force)
|
||||
{
|
||||
if (temperature.getPrecision() != _precision) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!force && temperature == _targetTemperature) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishTargetTemperature(temperature)) {
|
||||
_targetTemperature = temperature;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HAHVAC::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 27); // 27 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_features & ActionFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAActionTopic));
|
||||
}
|
||||
|
||||
if (_features & AuxHeatingFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAAuxCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HAAuxStateTopic));
|
||||
}
|
||||
|
||||
if (_features & PowerFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAPowerCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & FanFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAFanModeCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HAFanModeStateTopic));
|
||||
|
||||
if (_fanModes != DefaultFanModes) {
|
||||
_fanModesSerializer->clear();
|
||||
|
||||
if (_fanModes & AutoFanMode) {
|
||||
_fanModesSerializer->add(HAFanModeAuto);
|
||||
}
|
||||
|
||||
if (_fanModes & LowFanMode) {
|
||||
_fanModesSerializer->add(HAFanModeLow);
|
||||
}
|
||||
|
||||
if (_fanModes & MediumFanMode) {
|
||||
_fanModesSerializer->add(HAFanModeMedium);
|
||||
}
|
||||
|
||||
if (_fanModes & HighFanMode) {
|
||||
_fanModesSerializer->add(HAFanModeHigh);
|
||||
}
|
||||
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAFanModesProperty),
|
||||
_fanModesSerializer,
|
||||
HASerializer::ArrayPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & SwingFeature) {
|
||||
_serializer->topic(AHATOFSTR(HASwingModeCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HASwingModeStateTopic));
|
||||
|
||||
if (_swingModes != DefaultSwingModes) {
|
||||
_swingModesSerializer->clear();
|
||||
|
||||
if (_swingModes & OnSwingMode) {
|
||||
_swingModesSerializer->add(HASwingModeOn);
|
||||
}
|
||||
|
||||
if (_swingModes & OffSwingMode) {
|
||||
_swingModesSerializer->add(HASwingModeOff);
|
||||
}
|
||||
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASwingModesProperty),
|
||||
_swingModesSerializer,
|
||||
HASerializer::ArrayPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & ModesFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAModeCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HAModeStateTopic));
|
||||
|
||||
if (_modes != DefaultModes) {
|
||||
_modesSerializer->clear();
|
||||
|
||||
if (_modes & AutoMode) {
|
||||
_modesSerializer->add(HAModeAuto);
|
||||
}
|
||||
|
||||
if (_modes & OffMode) {
|
||||
_modesSerializer->add(HAModeOff);
|
||||
}
|
||||
|
||||
if (_modes & CoolMode) {
|
||||
_modesSerializer->add(HAModeCool);
|
||||
}
|
||||
|
||||
if (_modes & HeatMode) {
|
||||
_modesSerializer->add(HAModeHeat);
|
||||
}
|
||||
|
||||
if (_modes & DryMode) {
|
||||
_modesSerializer->add(HAModeDry);
|
||||
}
|
||||
|
||||
if (_modes & FanOnlyMode) {
|
||||
_modesSerializer->add(HAModeFanOnly);
|
||||
}
|
||||
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAModesProperty),
|
||||
_modesSerializer,
|
||||
HASerializer::ArrayPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & TargetTemperatureFeature) {
|
||||
_serializer->topic(AHATOFSTR(HATemperatureCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HATemperatureStateTopic));
|
||||
_serializer->set(
|
||||
AHATOFSTR(HATemperatureCommandTemplateProperty),
|
||||
getCommandWithFloatTemplate(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
}
|
||||
|
||||
if (_temperatureUnit != DefaultUnit) {
|
||||
const __FlashStringHelper *unitStr = _temperatureUnit == CelsiusUnit
|
||||
? AHATOFSTR(HATemperatureUnitC)
|
||||
: AHATOFSTR(HATemperatureUnitF);
|
||||
|
||||
_serializer->set(
|
||||
AHATOFSTR(HATemperatureUnitProperty),
|
||||
unitStr,
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
}
|
||||
|
||||
if (_minTemp.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMinTempProperty),
|
||||
&_minTemp,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_maxTemp.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMaxTempProperty),
|
||||
&_maxTemp,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_tempStep.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HATempStepProperty),
|
||||
&_tempStep,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->topic(AHATOFSTR(HACurrentTemperatureTopic));
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
}
|
||||
|
||||
void HAHVAC::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishCurrentTemperature(_CURRENT_TEMPerature);
|
||||
publishAction(_action);
|
||||
publishAuxState(_auxState);
|
||||
publishFanMode(_fanMode);
|
||||
publishSwingMode(_swingMode);
|
||||
publishMode(_mode);
|
||||
publishTargetTemperature(_targetTemperature);
|
||||
}
|
||||
|
||||
if (_features & AuxHeatingFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAAuxCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & PowerFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAPowerCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & FanFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAFanModeCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & SwingFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HASwingModeCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & ModesFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAModeCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & TargetTemperatureFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HATemperatureCommandTopic));
|
||||
}
|
||||
}
|
||||
|
||||
void HAHVAC::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAAuxCommandTopic)
|
||||
)) {
|
||||
handleAuxStateCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAPowerCommandTopic)
|
||||
)) {
|
||||
handlePowerCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAFanModeCommandTopic)
|
||||
)) {
|
||||
handleFanModeCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HASwingModeCommandTopic)
|
||||
)) {
|
||||
handleSwingModeCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAModeCommandTopic)
|
||||
)) {
|
||||
handleModeCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HATemperatureCommandTopic)
|
||||
)) {
|
||||
handleTargetTemperatureCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HAHVAC::publishCurrentTemperature(const HANumeric& temperature)
|
||||
{
|
||||
if (!temperature.isSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t size = temperature.calculateSize();
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[size + 1]; // with null terminator
|
||||
str[size] = 0;
|
||||
temperature.toStr(str);
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HACurrentTemperatureTopic),
|
||||
str,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishAction(const Action action)
|
||||
{
|
||||
if (action == UnknownAction || !(_features & ActionFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (action) {
|
||||
case OffAction:
|
||||
stateStr = AHATOFSTR(HAActionOff);
|
||||
break;
|
||||
|
||||
case HeatingAction:
|
||||
stateStr = AHATOFSTR(HAActionHeating);
|
||||
break;
|
||||
|
||||
case CoolingAction:
|
||||
stateStr = AHATOFSTR(HAActionCooling);
|
||||
break;
|
||||
|
||||
case DryingAction:
|
||||
stateStr = AHATOFSTR(HAActionDrying);
|
||||
break;
|
||||
|
||||
case IdleAction:
|
||||
stateStr = AHATOFSTR(HAActionIdle);
|
||||
break;
|
||||
|
||||
case FanAction:
|
||||
stateStr = AHATOFSTR(HAActionFan);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAActionTopic),
|
||||
stateStr,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishAuxState(const bool state)
|
||||
{
|
||||
if (!(_features & AuxHeatingFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAAuxStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishFanMode(const FanMode mode)
|
||||
{
|
||||
if (mode == UnknownFanMode || !(_features & FanFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (mode) {
|
||||
case AutoFanMode:
|
||||
stateStr = AHATOFSTR(HAFanModeAuto);
|
||||
break;
|
||||
|
||||
case LowFanMode:
|
||||
stateStr = AHATOFSTR(HAFanModeLow);
|
||||
break;
|
||||
|
||||
case MediumFanMode:
|
||||
stateStr = AHATOFSTR(HAFanModeMedium);
|
||||
break;
|
||||
|
||||
case HighFanMode:
|
||||
stateStr = AHATOFSTR(HAFanModeHigh);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAFanModeStateTopic),
|
||||
stateStr,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishSwingMode(const SwingMode mode)
|
||||
{
|
||||
if (mode == UnknownSwingMode || !(_features & SwingFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (mode) {
|
||||
case OnSwingMode:
|
||||
stateStr = AHATOFSTR(HASwingModeOn);
|
||||
break;
|
||||
|
||||
case OffSwingMode:
|
||||
stateStr = AHATOFSTR(HASwingModeOff);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HASwingModeStateTopic),
|
||||
stateStr,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishMode(const Mode mode)
|
||||
{
|
||||
if (mode == UnknownMode || !(_features & ModesFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (mode) {
|
||||
case AutoMode:
|
||||
stateStr = AHATOFSTR(HAModeAuto);
|
||||
break;
|
||||
|
||||
case OffMode:
|
||||
stateStr = AHATOFSTR(HAModeOff);
|
||||
break;
|
||||
|
||||
case CoolMode:
|
||||
stateStr = AHATOFSTR(HAModeCool);
|
||||
break;
|
||||
|
||||
case HeatMode:
|
||||
stateStr = AHATOFSTR(HAModeHeat);
|
||||
break;
|
||||
|
||||
case DryMode:
|
||||
stateStr = AHATOFSTR(HAModeDry);
|
||||
break;
|
||||
|
||||
case FanOnlyMode:
|
||||
stateStr = AHATOFSTR(HAModeFanOnly);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAModeStateTopic),
|
||||
stateStr,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishTargetTemperature(const HANumeric& temperature)
|
||||
{
|
||||
if (!temperature.isSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t size = temperature.calculateSize();
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[size + 1]; // with null terminator
|
||||
str[size] = 0;
|
||||
temperature.toStr(str);
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HATemperatureStateTopic),
|
||||
str,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
void HAHVAC::handleAuxStateCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
if (!_auxCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_auxCallback(state, this);
|
||||
}
|
||||
|
||||
void HAHVAC::handlePowerCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
if (!_powerCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_powerCallback(state, this);
|
||||
}
|
||||
|
||||
void HAHVAC::handleFanModeCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_fanModeCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HAFanModeAuto, length) == 0) {
|
||||
_fanModeCallback(AutoFanMode, this);
|
||||
} else if (memcmp_P(cmd, HAFanModeLow, length) == 0) {
|
||||
_fanModeCallback(LowFanMode, this);
|
||||
} else if (memcmp_P(cmd, HAFanModeMedium, length) == 0) {
|
||||
_fanModeCallback(MediumFanMode, this);
|
||||
} else if (memcmp_P(cmd, HAFanModeHigh, length) == 0) {
|
||||
_fanModeCallback(HighFanMode, this);
|
||||
}
|
||||
}
|
||||
|
||||
void HAHVAC::handleSwingModeCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_swingModeCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HASwingModeOn, length) == 0) {
|
||||
_swingModeCallback(OnSwingMode, this);
|
||||
} else if (memcmp_P(cmd, HASwingModeOff, length) == 0) {
|
||||
_swingModeCallback(OffSwingMode, this);
|
||||
}
|
||||
}
|
||||
|
||||
void HAHVAC::handleModeCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_modeCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HAModeAuto, length) == 0) {
|
||||
_modeCallback(AutoMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeOff, length) == 0) {
|
||||
_modeCallback(OffMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeCool, length) == 0) {
|
||||
_modeCallback(CoolMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeHeat, length) == 0) {
|
||||
_modeCallback(HeatMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeDry, length) == 0) {
|
||||
_modeCallback(DryMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeFanOnly, length) == 0) {
|
||||
_modeCallback(FanOnlyMode, this);
|
||||
}
|
||||
}
|
||||
|
||||
void HAHVAC::handleTargetTemperatureCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_targetTemperatureCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
HANumeric number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isSet()) {
|
||||
number.setPrecision(_precision);
|
||||
_targetTemperatureCallback(number, this);
|
||||
}
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HAHVAC::getCommandWithFloatTemplate()
|
||||
{
|
||||
switch (_precision) {
|
||||
case PrecisionP1:
|
||||
return AHATOFSTR(HAValueTemplateFloatP1);
|
||||
|
||||
case PrecisionP2:
|
||||
return AHATOFSTR(HAValueTemplateFloatP2);
|
||||
|
||||
case PrecisionP3:
|
||||
return AHATOFSTR(HAValueTemplateFloatP3);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
704
lib/home-assistant-integration/src/device-types/HAHVAC.h
Normal file
704
lib/home-assistant-integration/src/device-types/HAHVAC.h
Normal file
@@ -0,0 +1,704 @@
|
||||
#ifndef AHA_HAHVAC_H
|
||||
#define AHA_HAHVAC_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_HVAC
|
||||
|
||||
#define _SET_CURRENT_TEMPERATURE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline bool setCurrentTemperature(const type temperature, const bool force = false) \
|
||||
{ return setCurrentTemperature(HANumeric(temperature, _precision), force); }
|
||||
|
||||
#define _SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline void setCurrentCurrentTemperature(const type temperature) \
|
||||
{ setCurrentCurrentTemperature(HANumeric(temperature, _precision)); }
|
||||
|
||||
#define _SET_TARGET_TEMPERATURE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline bool setTargetTemperature(const type temperature, const bool force = false) \
|
||||
{ return setTargetTemperature(HANumeric(temperature, _precision), force); }
|
||||
|
||||
#define _SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline void setCurrentTargetTemperature(const type temperature) \
|
||||
{ setCurrentTargetTemperature(HANumeric(temperature, _precision)); }
|
||||
|
||||
#define HAHVAC_CALLBACK_BOOL(name) void (*name)(bool state, HAHVAC* sender)
|
||||
#define HAHVAC_CALLBACK_TARGET_TEMP(name) void (*name)(HANumeric temperature, HAHVAC* sender)
|
||||
#define HAHVAC_CALLBACK_FAN_MODE(name) void (*name)(FanMode mode, HAHVAC* sender)
|
||||
#define HAHVAC_CALLBACK_SWING_MODE(name) void (*name)(SwingMode mode, HAHVAC* sender)
|
||||
#define HAHVAC_CALLBACK_MODE(name) void (*name)(Mode mode, HAHVAC* sender)
|
||||
|
||||
class HASerializerArray;
|
||||
|
||||
/**
|
||||
* HAHVAC lets you control your HVAC devices.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/climate.mqtt/
|
||||
*/
|
||||
class HAHVAC : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
static const uint8_t DefaultFanModes;
|
||||
static const uint8_t DefaultSwingModes;
|
||||
static const uint8_t DefaultModes;
|
||||
|
||||
/// The list of features available in the HVAC. They're used in the constructor.
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
ActionFeature = 1,
|
||||
AuxHeatingFeature = 2,
|
||||
PowerFeature = 4,
|
||||
FanFeature = 8,
|
||||
SwingFeature = 16,
|
||||
ModesFeature = 32,
|
||||
TargetTemperatureFeature = 64
|
||||
};
|
||||
|
||||
/// The list of available actions of the HVAC.
|
||||
enum Action {
|
||||
UnknownAction = 0,
|
||||
OffAction,
|
||||
HeatingAction,
|
||||
CoolingAction,
|
||||
DryingAction,
|
||||
IdleAction,
|
||||
FanAction
|
||||
};
|
||||
|
||||
/// The list of available fan modes.
|
||||
enum FanMode {
|
||||
UnknownFanMode = 0,
|
||||
AutoFanMode = 1,
|
||||
LowFanMode = 2,
|
||||
MediumFanMode = 4,
|
||||
HighFanMode = 8
|
||||
};
|
||||
|
||||
/// The list of available swing modes.
|
||||
enum SwingMode {
|
||||
UnknownSwingMode = 0,
|
||||
OnSwingMode = 1,
|
||||
OffSwingMode = 2
|
||||
};
|
||||
|
||||
/// The list of available HVAC's modes.
|
||||
enum Mode {
|
||||
UnknownMode = 0,
|
||||
AutoMode = 1,
|
||||
OffMode = 2,
|
||||
CoolMode = 4,
|
||||
HeatMode = 8,
|
||||
DryMode = 16,
|
||||
FanOnlyMode = 32
|
||||
};
|
||||
|
||||
/// Temperature units available in the HVAC.
|
||||
enum TemperatureUnit {
|
||||
DefaultUnit = 1,
|
||||
CelsiusUnit,
|
||||
FahrenheitUnit
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the HVAC. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the HVAC.
|
||||
* @param precision The precision of temperatures reported by the HVAC.
|
||||
*/
|
||||
HAHVAC(
|
||||
const char* uniqueId,
|
||||
const uint16_t features = DefaultFeatures,
|
||||
const NumberPrecision precision = PrecisionP1
|
||||
);
|
||||
|
||||
/**
|
||||
* Frees memory allocated for the arrays serialization.
|
||||
*/
|
||||
~HAHVAC();
|
||||
|
||||
/**
|
||||
* Changes current temperature of the HVAC 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 temperature New current temperature.
|
||||
* @param force Forces to update the temperature without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setCurrentTemperature(const HANumeric& temperature, const bool force = false);
|
||||
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(float)
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Changes action of the HVAC 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 action New action.
|
||||
* @param force Forces to update the action without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setAction(const Action action, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes state of the aux heating 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 The new state.
|
||||
* @param force Forces to update the state without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setAuxState(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes mode of the fan of the HVAC 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 mode New fan's mode.
|
||||
* @param force Forces to update the mode without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setFanMode(const FanMode mode, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes swing mode of the HVAC 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 mode New swing mode.
|
||||
* @param force Forces to update the mode without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setSwingMode(const SwingMode mode, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes mode of the HVAC 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 mode New HVAC's mode.
|
||||
* @param force Forces to update the mode without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setMode(const Mode mode, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes target temperature of the HVAC 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 temperature Target temperature to set.
|
||||
* @param force Forces to update the mode without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setTargetTemperature(const HANumeric& temperature, const bool force = false);
|
||||
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(int8_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(int16_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(int32_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(uint8_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(uint16_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(uint32_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(float)
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets current temperature of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change temperature before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param temperature New current temperature.
|
||||
*/
|
||||
inline void setCurrentCurrentTemperature(const HANumeric& temperature)
|
||||
{ if (temperature.getPrecision() == _precision) { _CURRENT_TEMPerature = temperature; } }
|
||||
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(float)
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns last known current temperature of the HVAC.
|
||||
* If setCurrentTemperature method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline const HANumeric& getCurrentTemperature() const
|
||||
{ return _CURRENT_TEMPerature; }
|
||||
|
||||
/**
|
||||
* Sets action of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the action before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param action New action.
|
||||
*/
|
||||
inline void setCurrentAction(const Action action)
|
||||
{ _action = action; }
|
||||
|
||||
/**
|
||||
* Returns last known action of the HVAC.
|
||||
* If setAction method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline Action getCurrentAction() const
|
||||
{ return _action; }
|
||||
|
||||
/**
|
||||
* Sets aux heating state without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state The new state.
|
||||
*/
|
||||
inline void setCurrentAuxState(const bool state)
|
||||
{ _auxState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the aux heating.
|
||||
* If setAuxState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline bool getCurrentAuxState() const
|
||||
{ return _auxState; }
|
||||
|
||||
/**
|
||||
* Sets fan's mode of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the mode before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param mode New fan's mode.
|
||||
*/
|
||||
inline void setCurrentFanMode(const FanMode mode)
|
||||
{ _fanMode = mode; }
|
||||
|
||||
/**
|
||||
* Returns last known fan's mode of the HVAC.
|
||||
* If setFanMode method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline FanMode getCurrentFanMode() const
|
||||
{ return _fanMode; }
|
||||
|
||||
/**
|
||||
* Sets available fan modes.
|
||||
*
|
||||
* @param modes The modes to set (for example: `HAHVAC::AutoFanMode | HAHVAC::HighFanMode`).
|
||||
*/
|
||||
inline void setFanModes(const uint8_t modes)
|
||||
{ _fanModes = modes; }
|
||||
|
||||
/**
|
||||
* Sets swing mode of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the mode before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param mode New swing mode.
|
||||
*/
|
||||
inline void setCurrentSwingMode(const SwingMode mode)
|
||||
{ _swingMode = mode; }
|
||||
|
||||
/**
|
||||
* Returns last known swing mode of the HVAC.
|
||||
* If setSwingMode method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline SwingMode getCurrentSwingMode() const
|
||||
{ return _swingMode; }
|
||||
|
||||
/**
|
||||
* Sets available swing modes.
|
||||
*
|
||||
* @param modes The modes to set (for example: `HAHVAC::OnSwingMode`).
|
||||
*/
|
||||
inline void setSwingModes(const uint8_t modes)
|
||||
{ _swingModes = modes; }
|
||||
|
||||
/**
|
||||
* Sets mode of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the mode before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param mode New HVAC's mode.
|
||||
*/
|
||||
inline void setCurrentMode(const Mode mode)
|
||||
{ _mode = mode; }
|
||||
|
||||
/**
|
||||
* Returns last known mode of the HVAC.
|
||||
* If setMode method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline Mode getCurrentMode() const
|
||||
{ return _mode; }
|
||||
|
||||
/**
|
||||
* Sets available HVAC's modes.
|
||||
*
|
||||
* @param modes The modes to set (for example: `HAHVAC::CoolMode | HAHVAC::HeatMode`).
|
||||
*/
|
||||
inline void setModes(const uint8_t modes)
|
||||
{ _modes = modes; }
|
||||
|
||||
/**
|
||||
* Sets target temperature of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the target before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param temperature Target temperature to set.
|
||||
*/
|
||||
inline void setCurrentTargetTemperature(const HANumeric& temperature)
|
||||
{ if (temperature.getPrecision() == _precision) { _targetTemperature = temperature; } }
|
||||
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(float)
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns last known target temperature of the HVAC.
|
||||
* If setTargetTemperature method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline const HANumeric& getCurrentTargetTemperature() const
|
||||
{ return _targetTemperature; }
|
||||
|
||||
/**
|
||||
* Sets icon of the HVAC.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the HVAC's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Changes the temperature unit.
|
||||
*
|
||||
* @param unit See the TemperatureUnit enum above.
|
||||
*/
|
||||
inline void setTemperatureUnit(TemperatureUnit unit)
|
||||
{ _temperatureUnit = unit; }
|
||||
|
||||
/**
|
||||
* Sets the minimum temperature that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param min The minimum value.
|
||||
*/
|
||||
inline void setMinTemp(const float min)
|
||||
{ _minTemp = HANumeric(min, _precision); }
|
||||
|
||||
/**
|
||||
* Sets the maximum temperature that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param min The maximum value.
|
||||
*/
|
||||
inline void setMaxTemp(const float max)
|
||||
{ _maxTemp = HANumeric(max, _precision); }
|
||||
|
||||
/**
|
||||
* Sets the step of the temperature that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param step The setp value. By default it's `1`.
|
||||
*/
|
||||
inline void setTempStep(const float step)
|
||||
{ _tempStep = HANumeric(step, _precision); }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the aux state command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The aux state must be reported back to HA using the HAHVAC::setAuxState method.
|
||||
*/
|
||||
inline void onAuxStateCommand(HAHVAC_CALLBACK_BOOL(callback))
|
||||
{ _auxCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the power command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onPowerCommand(HAHVAC_CALLBACK_BOOL(callback))
|
||||
{ _powerCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the fan mode command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The fan mode must be reported back to HA using the HAHVAC::setFanMode method.
|
||||
*/
|
||||
inline void onFanModeCommand(HAHVAC_CALLBACK_FAN_MODE(callback))
|
||||
{ _fanModeCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the swing mode command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The swing mode must be reported back to HA using the HAHVAC::setSwingMode method.
|
||||
*/
|
||||
inline void onSwingModeCommand(HAHVAC_CALLBACK_SWING_MODE(callback))
|
||||
{ _swingModeCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the HVAC mode command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The mode must be reported back to HA using the HAHVAC::setMode method.
|
||||
*/
|
||||
inline void onModeCommand(HAHVAC_CALLBACK_MODE(callback))
|
||||
{ _modeCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the target temperature is set via HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The target temperature must be reported back to HA using the HAHVAC::setTargetTemperature method.
|
||||
*/
|
||||
inline void onTargetTemperatureCommand(HAHVAC_CALLBACK_TARGET_TEMP(callback))
|
||||
{ _targetTemperatureCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given current temperature.
|
||||
*
|
||||
* @param temperature The temperature to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishCurrentTemperature(const HANumeric& temperature);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given action.
|
||||
*
|
||||
* @param action The action to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishAction(const Action action);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given aux heating state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishAuxState(const bool state);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given fan mode.
|
||||
*
|
||||
* @param mode The mode to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishFanMode(const FanMode mode);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given swing mode.
|
||||
*
|
||||
* @param mode The mode to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishSwingMode(const SwingMode mode);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given mode.
|
||||
*
|
||||
* @param mode The mode to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishMode(const Mode mode);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given target temperature.
|
||||
*
|
||||
* @param temperature The temperature to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishTargetTemperature(const HANumeric& temperature);
|
||||
|
||||
/**
|
||||
* Parses the given aux state command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleAuxStateCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given power command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handlePowerCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given fan mode command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleFanModeCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given swing mode command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleSwingModeCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given HVAC's mode command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleModeCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given HVAC's target temperature command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleTargetTemperatureCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Returns progmem string representing value template for the command
|
||||
* that contains floating point numbers.
|
||||
*/
|
||||
const __FlashStringHelper* getCommandWithFloatTemplate();
|
||||
|
||||
/// Features enabled for the HVAC.
|
||||
const uint16_t _features;
|
||||
|
||||
/// The precision of temperatures. By default it's `HANumber::PrecisionP1`.
|
||||
const NumberPrecision _precision;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The current temperature of the HVAC. By default it's not set.
|
||||
HANumeric _CURRENT_TEMPerature;
|
||||
|
||||
/// The current action of the HVAC. By default it's `HAHVAC::UnknownAction`.
|
||||
Action _action;
|
||||
|
||||
/// The temperature unit for the HVAC. By default it's `HAHVAC::DefaultUnit`.
|
||||
TemperatureUnit _temperatureUnit;
|
||||
|
||||
/// The minimum temperature that can be set.
|
||||
HANumeric _minTemp;
|
||||
|
||||
/// The maximum temperature that can be set.
|
||||
HANumeric _maxTemp;
|
||||
|
||||
/// The step of the temperature that can be set.
|
||||
HANumeric _tempStep;
|
||||
|
||||
/// Callback that will be called when the aux state command is received from the HA.
|
||||
HAHVAC_CALLBACK_BOOL(_auxCallback);
|
||||
|
||||
/// The state of the aux heating. By default it's `false`.
|
||||
bool _auxState;
|
||||
|
||||
/// Callback that will be called when the power command is received from the HA.
|
||||
HAHVAC_CALLBACK_BOOL(_powerCallback);
|
||||
|
||||
/// The current mode of the fan. By default it's `HAHVAC::UnknownFanMode`.
|
||||
FanMode _fanMode;
|
||||
|
||||
/// The supported fan modes. By default it's `HAHVAC::DefaultFanModes`.
|
||||
uint8_t _fanModes;
|
||||
|
||||
/// The serializer for the fan modes. It's `nullptr` if the fan feature is disabled.
|
||||
HASerializerArray* _fanModesSerializer;
|
||||
|
||||
/// Callback that will be called when the fan mode command is received from the HA.
|
||||
HAHVAC_CALLBACK_FAN_MODE(_fanModeCallback);
|
||||
|
||||
/// The current swing mode. By default it's `HAHVAC::UnknownSwingMode`.
|
||||
SwingMode _swingMode;
|
||||
|
||||
/// The supported swing modes. By default it's `HAHVAC::DefaultSwingModes`.
|
||||
uint8_t _swingModes;
|
||||
|
||||
/// The serializer for the swing modes. It's `nullptr` if the swing feature is disabled.
|
||||
HASerializerArray* _swingModesSerializer;
|
||||
|
||||
/// Callback that will be called when the swing mode command is received from the HA.
|
||||
HAHVAC_CALLBACK_SWING_MODE(_swingModeCallback);
|
||||
|
||||
/// The current mode. By default it's `HAHVAC::UnknownMode`.
|
||||
Mode _mode;
|
||||
|
||||
/// The supported modes. By default it's `HAHVAC::DefaultModes`.
|
||||
uint8_t _modes;
|
||||
|
||||
/// The serializer for the modes. It's `nullptr` if the modes feature is disabled.
|
||||
HASerializerArray* _modesSerializer;
|
||||
|
||||
/// Callback that will be called when the mode command is received from the HA.
|
||||
HAHVAC_CALLBACK_MODE(_modeCallback);
|
||||
|
||||
/// The target temperature of the HVAC. By default it's not set.
|
||||
HANumeric _targetTemperature;
|
||||
|
||||
/// Callback that will be called when the target temperature is changed via the HA panel.
|
||||
HAHVAC_CALLBACK_TARGET_TEMP(_targetTemperatureCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
372
lib/home-assistant-integration/src/device-types/HALight.cpp
Normal file
372
lib/home-assistant-integration/src/device-types/HALight.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
#include "HALight.h"
|
||||
#ifndef EX_ARDUINOHA_LIGHT
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
const uint8_t HALight::RGBStringMaxLength = 3*4; // 4 characters per color
|
||||
|
||||
void HALight::RGBColor::fromBuffer(const uint8_t* data, const uint16_t length)
|
||||
{
|
||||
if (length > RGBStringMaxLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t firstCommaPos = 0;
|
||||
uint8_t secondCommaPos = 0;
|
||||
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
if (data[i] == ',') {
|
||||
if (firstCommaPos == 0) {
|
||||
firstCommaPos = i;
|
||||
} else if (secondCommaPos == 0) {
|
||||
secondCommaPos = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCommaPos == 0 || secondCommaPos == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t redLen = firstCommaPos;
|
||||
const uint8_t greenLen = secondCommaPos - firstCommaPos - 1; // minus comma
|
||||
const uint8_t blueLen = length - redLen - greenLen - 2; // minus two commas
|
||||
|
||||
const HANumeric& r = HANumeric::fromStr(data, redLen);
|
||||
const HANumeric& g = HANumeric::fromStr(&data[redLen + 1], greenLen);
|
||||
const HANumeric& b = HANumeric::fromStr(&data[redLen + greenLen + 2], blueLen);
|
||||
|
||||
if (r.isUInt8() && g.isUInt8() && b.isUInt8()) {
|
||||
red = r.toUInt8();
|
||||
green = g.toUInt8();
|
||||
blue = b.toUInt8();
|
||||
isSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
HALight::HALight(const char* uniqueId, const uint8_t features) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentLight), uniqueId),
|
||||
_features(features),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_brightnessScale(),
|
||||
_currentState(false),
|
||||
_BRIGHTNESS(0),
|
||||
_minMireds(),
|
||||
_maxMireds(),
|
||||
_currentColorTemperature(0),
|
||||
_currentRGBColor(),
|
||||
_stateCallback(nullptr),
|
||||
_brightnessCallback(nullptr),
|
||||
_colorTemperatureCallback(nullptr),
|
||||
_rgbColorCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HALight::setState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HALight::setBrightness(const uint8_t brightness, const bool force)
|
||||
{
|
||||
if (!force && brightness == _BRIGHTNESS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishBrightness(brightness)) {
|
||||
_BRIGHTNESS = brightness;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HALight::setColorTemperature(const uint16_t temperature, const bool force)
|
||||
{
|
||||
if (!force && temperature == _currentColorTemperature) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishColorTemperature(temperature)) {
|
||||
_currentColorTemperature = temperature;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HALight::setRGBColor(const RGBColor& color, const bool force)
|
||||
{
|
||||
if (!force && color == _currentRGBColor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishRGBColor(color)) {
|
||||
_currentRGBColor = color;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HALight::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 18); // 18 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_features & BrightnessFeature) {
|
||||
_serializer->topic(AHATOFSTR(HABrightnessStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HABrightnessCommandTopic));
|
||||
|
||||
if (_brightnessScale.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HABrightnessScaleProperty),
|
||||
&_brightnessScale,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & ColorTemperatureFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAColorTemperatureStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HAColorTemperatureCommandTopic));
|
||||
|
||||
if (_minMireds.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMinMiredsProperty),
|
||||
&_minMireds,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_maxMireds.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMaxMiredsProperty),
|
||||
&_maxMireds,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & RGBFeature) {
|
||||
_serializer->topic(AHATOFSTR(HARGBCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HARGBStateTopic));
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HALight::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
publishBrightness(_BRIGHTNESS);
|
||||
publishColorTemperature(_currentColorTemperature);
|
||||
publishRGBColor(_currentRGBColor);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
|
||||
if (_features & BrightnessFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HABrightnessCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & ColorTemperatureFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAColorTemperatureCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & RGBFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HARGBCommandTopic));
|
||||
}
|
||||
}
|
||||
|
||||
void HALight::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleStateCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HABrightnessCommandTopic)
|
||||
)) {
|
||||
handleBrightnessCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAColorTemperatureCommandTopic)
|
||||
)) {
|
||||
handleColorTemperatureCommand(payload, length);
|
||||
} else if (
|
||||
HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HARGBCommandTopic)
|
||||
)
|
||||
) {
|
||||
handleRGBCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HALight::publishState(const bool state)
|
||||
{
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HALight::publishBrightness(const uint8_t brightness)
|
||||
{
|
||||
if (!(_features & BrightnessFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[3 + 1] = {0}; // uint8_t digits with null terminator
|
||||
HANumeric(brightness, 0).toStr(str);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HABrightnessStateTopic), str, true);
|
||||
}
|
||||
|
||||
bool HALight::publishColorTemperature(const uint16_t temperature)
|
||||
{
|
||||
if (!(_features & ColorTemperatureFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[5 + 1] = {0}; // uint16_t digits with null terminator
|
||||
HANumeric(temperature, 0).toStr(str);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAColorTemperatureStateTopic), str, true);
|
||||
}
|
||||
|
||||
bool HALight::publishRGBColor(const RGBColor& color)
|
||||
{
|
||||
if (!(_features & RGBFeature) || !color.isSet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[RGBStringMaxLength] = {0};
|
||||
uint16_t len = 0;
|
||||
|
||||
// append red color with comma
|
||||
len += HANumeric(color.red, 0).toStr(&str[0]);
|
||||
str[len++] = ',';
|
||||
|
||||
// append green color with comma
|
||||
len += HANumeric(color.green, 0).toStr(&str[len]);
|
||||
str[len++] = ',';
|
||||
|
||||
// append blue color
|
||||
HANumeric(color.blue, 0).toStr(&str[len]);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HARGBStateTopic), str, true);
|
||||
}
|
||||
|
||||
void HALight::handleStateCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
if (!_stateCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_stateCallback(state, this);
|
||||
}
|
||||
|
||||
void HALight::handleBrightnessCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_brightnessCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HANumeric& number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isUInt8()) {
|
||||
_brightnessCallback(number.toUInt8(), this);
|
||||
}
|
||||
}
|
||||
|
||||
void HALight::handleColorTemperatureCommand(
|
||||
const uint8_t* cmd,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (!_colorTemperatureCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HANumeric& number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isUInt16()) {
|
||||
_colorTemperatureCallback(number.toUInt16(), this);
|
||||
}
|
||||
}
|
||||
|
||||
void HALight::handleRGBCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_rgbColorCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
RGBColor color;
|
||||
color.fromBuffer(cmd, length);
|
||||
|
||||
if (color.isSet) {
|
||||
_rgbColorCallback(color, this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
421
lib/home-assistant-integration/src/device-types/HALight.h
Normal file
421
lib/home-assistant-integration/src/device-types/HALight.h
Normal file
@@ -0,0 +1,421 @@
|
||||
#ifndef AHA_HALIGHT_H
|
||||
#define AHA_HALIGHT_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_LIGHT
|
||||
|
||||
#define HALIGHT_STATE_CALLBACK(name) void (*name)(bool state, HALight* sender)
|
||||
#define HALIGHT_BRIGHTNESS_CALLBACK(name) void (*name)(uint8_t brightness, HALight* sender)
|
||||
#define HALIGHT_COLOR_TEMP_CALLBACK(name) void (*name)(uint16_t temperature, HALight* sender)
|
||||
#define HALIGHT_RGB_COLOR_CALLBACK(name) void (*name)(HALight::RGBColor color, HALight* sender)
|
||||
|
||||
/**
|
||||
* HALight allows adding a controllable light in the Home Assistant panel.
|
||||
* The library supports only the state, brightness, color temperature and RGB color.
|
||||
* If you need more features please open a new GitHub issue.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/light.mqtt/
|
||||
*/
|
||||
class HALight : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
static const uint8_t RGBStringMaxLength;
|
||||
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
BrightnessFeature = 1,
|
||||
ColorTemperatureFeature = 2,
|
||||
RGBFeature = 4
|
||||
};
|
||||
|
||||
struct RGBColor {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
bool isSet;
|
||||
|
||||
RGBColor() :
|
||||
red(0), green(0), blue(0), isSet(false) { }
|
||||
|
||||
RGBColor(uint8_t r, uint8_t g, uint8_t b) :
|
||||
red(r), green(g), blue(b), isSet(true) { }
|
||||
|
||||
void operator= (const RGBColor& a) {
|
||||
red = a.red;
|
||||
green = a.green;
|
||||
blue = a.blue;
|
||||
isSet = a.isSet;
|
||||
}
|
||||
|
||||
bool operator== (const RGBColor& a) const {
|
||||
return (
|
||||
red == a.red &&
|
||||
green == a.green &&
|
||||
blue == a.blue
|
||||
);
|
||||
}
|
||||
|
||||
bool operator!= (const RGBColor& a) const {
|
||||
return (
|
||||
red != a.red ||
|
||||
green != a.green ||
|
||||
blue != a.blue
|
||||
);
|
||||
}
|
||||
|
||||
void fromBuffer(const uint8_t* data, const uint16_t length);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the light. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the light.
|
||||
* You can enable multiple features by using OR bitwise operator, for example:
|
||||
* `HALight::BrightnessFeature | HALight::ColorTemperatureFeature`
|
||||
*/
|
||||
HALight(const char* uniqueId, const uint8_t features = DefaultFeatures);
|
||||
|
||||
/**
|
||||
* Changes state of the light 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 light.
|
||||
* @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(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the brightness of the light 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 brightness The new brightness of the light.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setBrightness(const uint8_t brightness, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the color temperature of the light 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 temperature The new color temperature of the light.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setColorTemperature(const uint16_t temperature, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the RGB color of the light and publishes MQTT message.
|
||||
* Please note that if a new color is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param color The new RGB color of the light.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setRGBColor(const RGBColor& color, const bool force = false);
|
||||
|
||||
/**
|
||||
* Alias for `setState(true)`.
|
||||
*/
|
||||
inline bool turnOn()
|
||||
{ return setState(true); }
|
||||
|
||||
/**
|
||||
* Alias for `setState(false)`.
|
||||
*/
|
||||
inline bool turnOff()
|
||||
{ return setState(false); }
|
||||
|
||||
/**
|
||||
* Sets current state of the light without publishing it 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 light.
|
||||
*/
|
||||
inline void setCurrentState(const bool state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the light.
|
||||
* By default it's `false`.
|
||||
*/
|
||||
inline bool getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets the current brightness of the light without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the brightness before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param brightness The new brightness of the light.
|
||||
*/
|
||||
inline void setBRIGHTNESS(const uint8_t brightness)
|
||||
{ _BRIGHTNESS = brightness; }
|
||||
|
||||
/**
|
||||
* Returns the last known brightness of the light.
|
||||
* By default brightness is set to `0`.
|
||||
*/
|
||||
inline uint8_t getBRIGHTNESS() const
|
||||
{ return _BRIGHTNESS; }
|
||||
|
||||
/**
|
||||
* Sets the current color temperature of the light without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the color temperature before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param colorTemp The new color temperature (mireds).
|
||||
*/
|
||||
inline void setCurrentColorTemperature(const uint16_t temperature)
|
||||
{ _currentColorTemperature = temperature; }
|
||||
|
||||
/**
|
||||
* Returns the last known color temperature of the light.
|
||||
* By default temperature is set to `0`.
|
||||
*/
|
||||
inline uint16_t getCurrentColorTemperature() const
|
||||
{ return _currentColorTemperature; }
|
||||
|
||||
/**
|
||||
* Sets the current RGB color of the light without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the color before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param color The new RGB color.
|
||||
*/
|
||||
inline void setCurrentRGBColor(const RGBColor& color)
|
||||
{ _currentRGBColor = color; }
|
||||
|
||||
/**
|
||||
* Returns the last known RGB color of the light.
|
||||
* By default the RGB color is set to `0,0,0`.
|
||||
*/
|
||||
inline const RGBColor& getCurrentRGBColor() const
|
||||
{ return _currentRGBColor; }
|
||||
|
||||
/**
|
||||
* Sets icon of the light.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the light's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the light state.
|
||||
* In this mode the light state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Sets the maximum brightness value that can be set via HA panel.
|
||||
* By default it's `255`.
|
||||
*
|
||||
* @param scale The maximum value of the brightness.
|
||||
*/
|
||||
inline void setBrightnessScale(const uint8_t scale)
|
||||
{ _brightnessScale.setBaseValue(scale); }
|
||||
|
||||
/**
|
||||
* Sets the minimum color temperature (mireds) value that can be set via HA panel.
|
||||
* By default it's `153`.
|
||||
*
|
||||
* @param mireds The minimum value of the brightness.
|
||||
*/
|
||||
inline void setMinMireds(const uint16_t mireds)
|
||||
{ _minMireds.setBaseValue(mireds); }
|
||||
|
||||
/**
|
||||
* Sets the maximum color temperature (mireds) value that can be set via HA panel.
|
||||
* By default it's `500`.
|
||||
*
|
||||
* @param mireds The maximum value of the brightness.
|
||||
*/
|
||||
inline void setMaxMireds(const uint16_t mireds)
|
||||
{ _maxMireds.setBaseValue(mireds); }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the state command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same light.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the state must be reported back to HA using the HALight::setState method.
|
||||
*/
|
||||
inline void onStateCommand(HALIGHT_STATE_CALLBACK(callback))
|
||||
{ _stateCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the brightness command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same light.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the brightness must be reported back to HA using the HALight::setBrightness method.
|
||||
*/
|
||||
inline void onBrightnessCommand(HALIGHT_BRIGHTNESS_CALLBACK(callback))
|
||||
{ _brightnessCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the color temperature command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same light.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the color temperature must be reported back to HA using the HALight::setColorTemperature method.
|
||||
*/
|
||||
inline void onColorTemperatureCommand(HALIGHT_COLOR_TEMP_CALLBACK(callback))
|
||||
{ _colorTemperatureCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the RGB color command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same light.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the color must be reported back to HA using the HALight::setRGBColor method.
|
||||
*/
|
||||
inline void onRGBColorCommand(HALIGHT_RGB_COLOR_CALLBACK(callback))
|
||||
{ _rgbColorCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const bool state);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given brightness.
|
||||
*
|
||||
* @param brightness The brightness to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishBrightness(const uint8_t brightness);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given color temperature (mireds).
|
||||
*
|
||||
* @param temperature The color temperature to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishColorTemperature(const uint16_t temperature);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given RGB color.
|
||||
*
|
||||
* @param color The color to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishRGBColor(const RGBColor& color);
|
||||
|
||||
/**
|
||||
* Parses the given state command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleStateCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given brightness command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleBrightnessCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given color temperature command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleColorTemperatureCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given RGB color command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleRGBCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/// Features enabled for the light.
|
||||
const uint8_t _features;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the light (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The maximum value of the brightness. By default it's 255.
|
||||
HANumeric _brightnessScale;
|
||||
|
||||
/// The current state of the light. By default it's `false`.
|
||||
bool _currentState;
|
||||
|
||||
/// The current brightness of the light. By default it's `0`.
|
||||
uint8_t _BRIGHTNESS;
|
||||
|
||||
/// The minimum color temperature (mireds). By default the value is not set.
|
||||
HANumeric _minMireds;
|
||||
|
||||
/// The maximum color temperature (mireds). By default the value is not set.
|
||||
HANumeric _maxMireds;
|
||||
|
||||
/// The current color temperature (mireds). By default the value is not set.
|
||||
uint16_t _currentColorTemperature;
|
||||
|
||||
/// The current RBB color. By default the value is not set.
|
||||
RGBColor _currentRGBColor;
|
||||
|
||||
/// The callback that will be called when the state command is received from the HA.
|
||||
HALIGHT_STATE_CALLBACK(_stateCallback);
|
||||
|
||||
/// The callback that will be called when the brightness command is received from the HA.
|
||||
HALIGHT_BRIGHTNESS_CALLBACK(_brightnessCallback);
|
||||
|
||||
/// The callback that will be called when the color temperature command is received from the HA.
|
||||
HALIGHT_COLOR_TEMP_CALLBACK(_colorTemperatureCallback);
|
||||
|
||||
/// The callback that will be called when the RGB command is received from the HA.
|
||||
HALIGHT_RGB_COLOR_CALLBACK(_rgbColorCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
124
lib/home-assistant-integration/src/device-types/HALock.cpp
Normal file
124
lib/home-assistant-integration/src/device-types/HALock.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "HALock.h"
|
||||
#ifndef EX_ARDUINOHA_LOCK
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HALock::HALock(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentLock), uniqueId),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_currentState(StateUnknown),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HALock::setState(const LockState state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HALock::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 9); // 9 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HALock::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HALock::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HALock::publishState(const LockState state)
|
||||
{
|
||||
if (state == StateUnknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state == StateLocked ? HAStateLocked : HAStateUnlocked),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
void HALock::handleCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_commandCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HALockCommand, length) == 0) {
|
||||
_commandCallback(CommandLock, this);
|
||||
} else if (memcmp_P(cmd, HAUnlockCommand, length) == 0) {
|
||||
_commandCallback(CommandUnlock, this);
|
||||
} else if (memcmp_P(cmd, HAOpenCommand, length) == 0) {
|
||||
_commandCallback(CommandOpen, this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
148
lib/home-assistant-integration/src/device-types/HALock.h
Normal file
148
lib/home-assistant-integration/src/device-types/HALock.h
Normal file
@@ -0,0 +1,148 @@
|
||||
#ifndef AHA_HALOCK_H
|
||||
#define AHA_HALOCK_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_LOCK
|
||||
|
||||
#define HALOCK_CALLBACK(name) void (*name)(LockCommand command, HALock* sender)
|
||||
|
||||
/**
|
||||
* HALock allows to implement a custom lock (for example: door lock)
|
||||
* that can be controlled from the Home Assistant panel.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/lock.mqtt/
|
||||
*/
|
||||
class HALock : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/// Available states of the lock that can be reported to the HA panel.
|
||||
enum LockState {
|
||||
StateUnknown = 0,
|
||||
StateLocked,
|
||||
StateUnlocked
|
||||
};
|
||||
|
||||
/// Commands that will be produced by the HA panel.
|
||||
enum LockCommand {
|
||||
CommandLock = 1,
|
||||
CommandUnlock,
|
||||
CommandOpen
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the lock. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HALock(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Changes state of the lock 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 lock.
|
||||
* @param force Forces to update state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const LockState state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets current state of the lock without publishing it 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 lock.
|
||||
*/
|
||||
inline void setCurrentState(const LockState state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the lock.
|
||||
* If setState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline LockState getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets icon of the lock.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the lock's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the lock state.
|
||||
* In this mode the lock state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the lock/unlock/open command from the HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same lock.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onCommand(HALOCK_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const LockState state);
|
||||
|
||||
/**
|
||||
* Parses the given command and executes the lock's callback with proper enum's property.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/// The icon of the lock. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the lock (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The current state of the lock. By default it's `HALock::StateUnknown`.
|
||||
LockState _currentState;
|
||||
|
||||
/// The callback that will be called when lock/unlock/open command is received from the HA.
|
||||
HALOCK_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
214
lib/home-assistant-integration/src/device-types/HANumber.cpp
Normal file
214
lib/home-assistant-integration/src/device-types/HANumber.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
#include "HANumber.h"
|
||||
#ifndef EX_ARDUINOHA_NUMBER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HANumber::HANumber(const char* uniqueId, const NumberPrecision precision) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentNumber), uniqueId),
|
||||
_precision(precision),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_mode(ModeAuto),
|
||||
_unitOfMeasurement(nullptr),
|
||||
_minValue(),
|
||||
_maxValue(),
|
||||
_step(),
|
||||
_currentState(),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HANumber::setState(const HANumeric& state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HANumber::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 15); // 15 - max properties nb
|
||||
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(AHATOFSTR(HAUnitOfMeasurementProperty), _unitOfMeasurement);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAModeProperty),
|
||||
getModeProperty(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HACommandTemplateProperty),
|
||||
getCommandTemplate(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
|
||||
if (_minValue.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMinProperty),
|
||||
&_minValue,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_maxValue.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMaxProperty),
|
||||
&_maxValue,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_step.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAStepProperty),
|
||||
&_step,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HANumber::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HANumber::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HANumber::publishState(const HANumeric& state)
|
||||
{
|
||||
if (!state.isSet()) {
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(HAStateNone),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
const uint8_t size = state.calculateSize();
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[size + 1]; // with null terminator
|
||||
str[size] = 0;
|
||||
state.toStr(str);
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
str,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
void HANumber::handleCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_commandCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HAStateNone, length) == 0) {
|
||||
_commandCallback(HANumeric(), this);
|
||||
} else {
|
||||
HANumeric number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isSet()) {
|
||||
number.setPrecision(_precision);
|
||||
_commandCallback(number, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HANumber::getModeProperty() const
|
||||
{
|
||||
switch (_mode) {
|
||||
case ModeBox:
|
||||
return AHATOFSTR(HAModeBox);
|
||||
|
||||
case ModeSlider:
|
||||
return AHATOFSTR(HAModeSlider);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HANumber::getCommandTemplate()
|
||||
{
|
||||
switch (_precision) {
|
||||
case PrecisionP1:
|
||||
return AHATOFSTR(HAValueTemplateFloatP1);
|
||||
|
||||
case PrecisionP2:
|
||||
return AHATOFSTR(HAValueTemplateFloatP2);
|
||||
|
||||
case PrecisionP3:
|
||||
return AHATOFSTR(HAValueTemplateFloatP3);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
263
lib/home-assistant-integration/src/device-types/HANumber.h
Normal file
263
lib/home-assistant-integration/src/device-types/HANumber.h
Normal file
@@ -0,0 +1,263 @@
|
||||
#ifndef AHA_HANUMBER_H
|
||||
#define AHA_HANUMBER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_NUMBER
|
||||
|
||||
#define _SET_STATE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline bool setState(const type state, const bool force = false) \
|
||||
{ return setState(HANumeric(state, _precision), force); }
|
||||
|
||||
#define _SET_CURRENT_STATE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline void setCurrentState(const type state) \
|
||||
{ setCurrentState(HANumeric(state, _precision)); }
|
||||
|
||||
#define HANUMBER_CALLBACK(name) void (*name)(HANumeric number, HANumber* sender)
|
||||
|
||||
/**
|
||||
* HANumber adds a slider or a box in the Home Assistant panel
|
||||
* that controls the numeric value stored on your device.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/number.mqtt/
|
||||
*/
|
||||
class HANumber : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/// Represents mode of the number.
|
||||
enum Mode {
|
||||
ModeAuto = 0,
|
||||
ModeBox,
|
||||
ModeSlider
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates instance of the HANumber entity with the given numbers precision.
|
||||
* The given precision applies to the state, min, max and step values.
|
||||
*
|
||||
* @param uniqueId The unique ID of the number. It needs to be unique in a scope of your device.
|
||||
* @param precision Precision of the floating point number that will be displayed in the HA panel.
|
||||
*/
|
||||
HANumber(const char* uniqueId, const NumberPrecision precision = PrecisionP0);
|
||||
|
||||
/**
|
||||
* Changes state of the number 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 number.
|
||||
* @param force Forces to update state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const HANumeric& state, const bool force = false);
|
||||
|
||||
_SET_STATE_OVERLOAD(int8_t)
|
||||
_SET_STATE_OVERLOAD(int16_t)
|
||||
_SET_STATE_OVERLOAD(int32_t)
|
||||
_SET_STATE_OVERLOAD(uint8_t)
|
||||
_SET_STATE_OVERLOAD(uint16_t)
|
||||
_SET_STATE_OVERLOAD(uint32_t)
|
||||
_SET_STATE_OVERLOAD(float)
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
_SET_STATE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets current state of the number without publishing it 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 number.
|
||||
*/
|
||||
inline void setCurrentState(const HANumeric& state)
|
||||
{ if (state.getPrecision() == _precision) { _currentState = state; } }
|
||||
|
||||
_SET_CURRENT_STATE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(float)
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
_SET_CURRENT_STATE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns last known state of the number.
|
||||
* If setState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline const HANumeric& getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/number/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the number.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the number's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the number state.
|
||||
* In this mode the number state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Sets mode of the number.
|
||||
* It controls how the number should be displayed in the UI.
|
||||
* By default it's `HANumber::ModeAuto`.
|
||||
*
|
||||
* @param mode Mode to set.
|
||||
*/
|
||||
inline void setMode(const Mode mode)
|
||||
{ _mode = mode; }
|
||||
|
||||
/**
|
||||
* Defines the units of measurement of the number, if any.
|
||||
*
|
||||
* @param units For example: °C, %
|
||||
*/
|
||||
inline void setUnitOfMeasurement(const char* unitOfMeasurement)
|
||||
{ _unitOfMeasurement = unitOfMeasurement; }
|
||||
|
||||
/**
|
||||
* Sets the minimum value that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param min The minimal value. By default the value is not set.
|
||||
*/
|
||||
inline void setMin(const float min)
|
||||
{ _minValue = HANumeric(min, _precision); }
|
||||
|
||||
/**
|
||||
* Sets the maximum value that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param min The maximum value. By default the value is not set.
|
||||
*/
|
||||
inline void setMax(const float max)
|
||||
{ _maxValue = HANumeric(max, _precision); }
|
||||
|
||||
/**
|
||||
* Sets step of the slider's movement in the Home Assistant panel.
|
||||
*
|
||||
* @param step The step value. Smallest value `0.001`. By default the value is not set.
|
||||
*/
|
||||
inline void setStep(const float step)
|
||||
{ _step = HANumeric(step, _precision); }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the number is changed in the HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same number.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the number must be reported back to HA using the HANumber::setState method.
|
||||
*/
|
||||
inline void onCommand(HANUMBER_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const HANumeric& state);
|
||||
|
||||
/**
|
||||
* Parses the given command and executes the number's callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Returns progmem string representing mode of the number
|
||||
*/
|
||||
const __FlashStringHelper* getModeProperty() const;
|
||||
|
||||
/**
|
||||
* Returns progmem string representing value template for the command.
|
||||
*/
|
||||
const __FlashStringHelper* getCommandTemplate();
|
||||
|
||||
/// The precision of the number. By default it's `HANumber::PrecisionP0`.
|
||||
const NumberPrecision _precision;
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the number. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the number (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// Controls how the number should be displayed in the UI. By default it's `HANumber::ModeAuto`.
|
||||
Mode _mode;
|
||||
|
||||
/// The unit of measurement for the sensor. It can be nullptr.
|
||||
const char* _unitOfMeasurement;
|
||||
|
||||
/// The minimal value that can be set from the HA panel.
|
||||
HANumeric _minValue;
|
||||
|
||||
/// The maximum value that can be set from the HA panel.
|
||||
HANumeric _maxValue;
|
||||
|
||||
/// The step of the slider's movement.
|
||||
HANumeric _step;
|
||||
|
||||
/// The current state of the number. By default the value is not set.
|
||||
HANumeric _currentState;
|
||||
|
||||
/// The callback that will be called when the command is received from the HA.
|
||||
HANUMBER_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
76
lib/home-assistant-integration/src/device-types/HAScene.cpp
Normal file
76
lib/home-assistant-integration/src/device-types/HAScene.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "HAScene.h"
|
||||
#ifndef EX_ARDUINOHA_SCENE
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HAScene::HAScene(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentScene), uniqueId),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HAScene::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 7); // 7 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
// optional property
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
// HA 2022.10 throws an exception if this property is not set
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAPayloadOnProperty),
|
||||
AHATOFSTR(HAStateOn),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAScene::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAScene::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
|
||||
if (_commandCallback && HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
_commandCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
73
lib/home-assistant-integration/src/device-types/HAScene.h
Normal file
73
lib/home-assistant-integration/src/device-types/HAScene.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef AHA_HASCENE_H
|
||||
#define AHA_HASCENE_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SCENE
|
||||
|
||||
#define HASCENE_CALLBACK(name) void (*name)(HAScene* sender)
|
||||
|
||||
/**
|
||||
* HAScene adds a new scene to the Home Assistant that triggers your callback once activated.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/scene.mqtt/
|
||||
*/
|
||||
class HAScene : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the scene. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HAScene(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Sets icon of the scene.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the scene's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called when the scene is activated in the HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same scene.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onCommand(HASCENE_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/// The icon of the scene. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The command callback that will be called when scene is activated from the HA panel.
|
||||
HASCENE_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
198
lib/home-assistant-integration/src/device-types/HASelect.cpp
Normal file
198
lib/home-assistant-integration/src/device-types/HASelect.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
#include "HASelect.h"
|
||||
#ifndef EX_ARDUINOHA_SELECT
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HASelect::HASelect(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentSelect), uniqueId),
|
||||
_options(nullptr),
|
||||
_currentState(-1),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HASelect::~HASelect()
|
||||
{
|
||||
if (_options) {
|
||||
const uint8_t optionsNb = _options->getItemsNb();
|
||||
const HASerializerArray::ItemType* options = _options->getItems();
|
||||
|
||||
if (optionsNb > 1) {
|
||||
for (uint8_t i = 0; i < optionsNb; i++) {
|
||||
delete options[i];
|
||||
}
|
||||
}
|
||||
|
||||
delete _options;
|
||||
}
|
||||
}
|
||||
|
||||
void HASelect::setOptions(const char* options)
|
||||
{
|
||||
if (!options || _options) { // options can be set only once
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t optionsNb = countOptionsInString(options);
|
||||
if (optionsNb == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t optionsLen = strlen(options) + 1; // include null terminator
|
||||
_options = new HASerializerArray(optionsNb, false);
|
||||
|
||||
if (optionsNb == 1) {
|
||||
_options->add(options);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t optionLen = 0;
|
||||
for (uint16_t i = 0; i < optionsLen; i++) {
|
||||
if (options[i] == ';' || options[i] == 0) {
|
||||
if (optionLen == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
char* option = new char[optionLen + 1]; // including null terminator
|
||||
option[optionLen] = 0;
|
||||
memcpy(option, &options[i - optionLen], optionLen);
|
||||
|
||||
_options->add(option);
|
||||
optionLen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
optionLen++;
|
||||
}
|
||||
}
|
||||
|
||||
bool HASelect::setState(const int8_t state, const bool force)
|
||||
{
|
||||
if (!force && _currentState == state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HASelect::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId() || !_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 10); // 10 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptionsProperty),
|
||||
_options,
|
||||
HASerializer::ArrayPropertyType
|
||||
);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HASelect::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HASelect::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (_commandCallback && HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
const uint8_t optionsNb = _options->getItemsNb();
|
||||
const HASerializerArray::ItemType* options = _options->getItems();
|
||||
|
||||
for (uint8_t i = 0; i < optionsNb; i++) {
|
||||
if (memcmp(payload, options[i], length) == 0) {
|
||||
_commandCallback(i, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HASelect::publishState(const int8_t state)
|
||||
{
|
||||
if (state == -1 || !_options || state >= _options->getItemsNb()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* item = _options->getItems()[state];
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), item, true);
|
||||
}
|
||||
|
||||
uint8_t HASelect::countOptionsInString(const char* options) const
|
||||
{
|
||||
// the given string is treated as a single option if there are no semicolons
|
||||
uint8_t optionsNb = 1;
|
||||
const uint16_t optionsLen = strlen(options);
|
||||
|
||||
if (optionsLen == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < optionsLen; i++) {
|
||||
if (options[i] == ';') {
|
||||
optionsNb++;
|
||||
}
|
||||
}
|
||||
|
||||
return optionsNb;
|
||||
}
|
||||
|
||||
#endif
|
||||
155
lib/home-assistant-integration/src/device-types/HASelect.h
Normal file
155
lib/home-assistant-integration/src/device-types/HASelect.h
Normal file
@@ -0,0 +1,155 @@
|
||||
#ifndef AHA_HASELECT_H
|
||||
#define AHA_HASELECT_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SELECT
|
||||
|
||||
class HASerializerArray;
|
||||
|
||||
#define HASELECT_CALLBACK(name) void (*name)(int8_t index, HASelect* sender)
|
||||
|
||||
/**
|
||||
* HASelect adds a dropdown with options in the Home Assistant panel.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/button.mqtt/
|
||||
*/
|
||||
class HASelect : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the select. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HASelect(const char* uniqueId);
|
||||
~HASelect();
|
||||
|
||||
/**
|
||||
* Sets the list of available options that will be listed in the dropdown.
|
||||
* The input string should contain options separated using semicolons.
|
||||
* For example: `setOptions("Option A;Option B;Option C");
|
||||
*
|
||||
* @param options The list of options that are separated by semicolons.
|
||||
* @note The options list can be set only once.
|
||||
*/
|
||||
void setOptions(const char* options);
|
||||
|
||||
/**
|
||||
* Changes state of the select and publishes MQTT message.
|
||||
* State represents the index of the option that was set using the setOptions method.
|
||||
* 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 select.
|
||||
* @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(const int8_t state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets the current state of the select without publishing it to Home Assistant.
|
||||
* State represents the index of the option that was set using the setOptions method.
|
||||
* This method may be useful if you want to change the state before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param state The new state of the cover.
|
||||
*/
|
||||
inline void setCurrentState(const int8_t state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the select.
|
||||
* State represents the index of the option that was set using the setOptions method.
|
||||
* By default the state is set to `-1`.
|
||||
*/
|
||||
inline int8_t getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets icon of the select.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the select's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the select state.
|
||||
* In this mode the select state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the option is changed from the HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same select.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the selected option must be reported back to HA using the HASelect::setState method.
|
||||
*/
|
||||
inline void onCommand(HASELECT_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
inline HASerializerArray* getOptions() const
|
||||
{ return _options; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const int8_t state);
|
||||
|
||||
/**
|
||||
* Counts the amount of options in the given string.
|
||||
*/
|
||||
uint8_t countOptionsInString(const char* options) const;
|
||||
|
||||
/// Array of options for the serializer.
|
||||
HASerializerArray* _options;
|
||||
|
||||
/// Stores the current state (the current option's index). By default it's `-1`.
|
||||
int8_t _currentState;
|
||||
|
||||
/// The icon of the select. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the select (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The command callback that will be called when option is changed via the HA panel.
|
||||
HASELECT_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
59
lib/home-assistant-integration/src/device-types/HASensor.cpp
Normal file
59
lib/home-assistant-integration/src/device-types/HASensor.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "HASensor.h"
|
||||
#ifndef EX_ARDUINOHA_SENSOR
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HASensor::HASensor(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentSensor), uniqueId),
|
||||
_deviceClass(nullptr),
|
||||
_forceUpdate(false),
|
||||
_icon(nullptr),
|
||||
_unitOfMeasurement(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HASensor::setValue(const char* value)
|
||||
{
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), value, true);
|
||||
}
|
||||
|
||||
void HASensor::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 9); // 9 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _deviceClass);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(AHATOFSTR(HAUnitOfMeasurementProperty), _unitOfMeasurement);
|
||||
|
||||
// optional property
|
||||
if (_forceUpdate) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAForceUpdateProperty),
|
||||
&_forceUpdate,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
}
|
||||
|
||||
void HASensor::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
#endif
|
||||
89
lib/home-assistant-integration/src/device-types/HASensor.h
Normal file
89
lib/home-assistant-integration/src/device-types/HASensor.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef AHA_HASENSOR_H
|
||||
#define AHA_HASENSOR_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SENSOR
|
||||
|
||||
/**
|
||||
* HASensor allows to publish textual sensor values that will be displayed in the HA panel.
|
||||
* If you need to publish numbers then HASensorNumber is what you're looking for.
|
||||
*
|
||||
* @note It's not possible to define a sensor that publishes mixed values (e.g. string + integer values).
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/sensor.mqtt/
|
||||
*/
|
||||
class HASensor : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the sensor. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HASensor(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given value.
|
||||
* Unlike the other device types, the HASensor doesn't store the previous value that was set.
|
||||
* It means that the MQTT message is produced each time the setValue method is called.
|
||||
*
|
||||
* @param value String representation of the sensor's value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setValue(const char* value);
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/sensor/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _deviceClass = deviceClass; }
|
||||
|
||||
/**
|
||||
* Forces HA panel to process each incoming value (MQTT message).
|
||||
* It's useful if you want to have meaningful value graphs in history.
|
||||
*
|
||||
* @param forceUpdate
|
||||
*/
|
||||
inline void setForceUpdate(bool forceUpdate)
|
||||
{ _forceUpdate = forceUpdate; }
|
||||
|
||||
/**
|
||||
* Sets icon of the sensor.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param class The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Defines the units of measurement of the sensor, if any.
|
||||
*
|
||||
* @param units For example: °C, %
|
||||
*/
|
||||
inline void setUnitOfMeasurement(const char* unitOfMeasurement)
|
||||
{ _unitOfMeasurement = unitOfMeasurement; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override final;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _deviceClass;
|
||||
|
||||
/// The force update flag for the HA panel.
|
||||
bool _forceUpdate;
|
||||
|
||||
/// The icon of the sensor. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The unit of measurement for the sensor. It can be nullptr.
|
||||
const char* _unitOfMeasurement;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "HASensorNumber.h"
|
||||
#ifndef EX_ARDUINOHA_SENSOR
|
||||
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HASensorNumber::HASensorNumber(
|
||||
const char* uniqueId,
|
||||
const NumberPrecision precision
|
||||
) :
|
||||
HASensor(uniqueId),
|
||||
_precision(precision),
|
||||
_currentValue()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HASensorNumber::setValue(const HANumeric& value, const bool force)
|
||||
{
|
||||
if (value.getPrecision() != _precision) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!force && value == _currentValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishValue(value)) {
|
||||
_currentValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HASensorNumber::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HASensor::onMqttConnected();
|
||||
publishValue(_currentValue);
|
||||
}
|
||||
|
||||
bool HASensorNumber::publishValue(const HANumeric& value)
|
||||
{
|
||||
if (!value.isSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t size = value.calculateSize();
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[size + 1]; // with null terminator
|
||||
str[size] = 0;
|
||||
value.toStr(str);
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
str,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
||||
106
lib/home-assistant-integration/src/device-types/HASensorNumber.h
Normal file
106
lib/home-assistant-integration/src/device-types/HASensorNumber.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef AHA_HASENSORNUMBER_H
|
||||
#define AHA_HASENSORNUMBER_H
|
||||
|
||||
#include "HASensor.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SENSOR
|
||||
|
||||
#define _SET_VALUE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline bool setValue(const type value, const bool force = false) \
|
||||
{ return setValue(HANumeric(value, _precision), force); }
|
||||
|
||||
#define _SET_CURRENT_VALUE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline void setCurrentValue(const type value) \
|
||||
{ setCurrentValue(HANumeric(value, _precision)); }
|
||||
|
||||
/**
|
||||
* HASensorInteger allows to publish numeric values of a sensor that will be displayed in the HA panel.
|
||||
*
|
||||
* @note You can find more information about this class in HASensor documentation.
|
||||
*/
|
||||
class HASensorNumber : public HASensor
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the sensor. It needs to be unique in a scope of your device.
|
||||
* @param precision Precision of the floating point number that will be displayed in the HA panel.
|
||||
*/
|
||||
HASensorNumber(
|
||||
const char* uniqueId,
|
||||
const NumberPrecision precision = PrecisionP0
|
||||
);
|
||||
|
||||
/**
|
||||
* Changes value of the sensor and publish MQTT message.
|
||||
* Please note that if a new value is the same as the previous one the MQTT message won't be published.
|
||||
*
|
||||
* @param value New value of the sensor. THe precision of the value needs to match precision of the sensor.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool setValue(const HANumeric& value, const bool force = false);
|
||||
|
||||
_SET_VALUE_OVERLOAD(int8_t)
|
||||
_SET_VALUE_OVERLOAD(int16_t)
|
||||
_SET_VALUE_OVERLOAD(int32_t)
|
||||
_SET_VALUE_OVERLOAD(uint8_t)
|
||||
_SET_VALUE_OVERLOAD(uint16_t)
|
||||
_SET_VALUE_OVERLOAD(uint32_t)
|
||||
_SET_VALUE_OVERLOAD(float)
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
_SET_VALUE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets the current value of the sensor without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the value before the connection with the MQTT broker is acquired.
|
||||
*
|
||||
* @param value New value of the sensor.
|
||||
*/
|
||||
inline void setCurrentValue(const HANumeric& value)
|
||||
{ if (value.getPrecision() == _precision) { _currentValue = value; } }
|
||||
|
||||
_SET_CURRENT_VALUE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(float)
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
_SET_CURRENT_VALUE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns the last known value of the sensor.
|
||||
* By default the value is not set.
|
||||
*/
|
||||
inline const HANumeric& getCurrentValue() const
|
||||
{ return _currentValue; }
|
||||
|
||||
protected:
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given value.
|
||||
*
|
||||
* @param state The value to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishValue(const HANumeric& value);
|
||||
|
||||
/// The precision of the sensor. By default it's `HASensorNumber::PrecisionP0`.
|
||||
const NumberPrecision _precision;
|
||||
|
||||
/// The current value of the sensor. By default the value is not set.
|
||||
HANumeric _currentValue;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
111
lib/home-assistant-integration/src/device-types/HASwitch.cpp
Normal file
111
lib/home-assistant-integration/src/device-types/HASwitch.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "HASwitch.h"
|
||||
#ifndef EX_ARDUINOHA_SWITCH
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HASwitch::HASwitch(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentSwitch), uniqueId),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_currentState(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HASwitch::setState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HASwitch::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 10); // 10 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAUniqueIdProperty), _uniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
// optional property
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HASwitch::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HASwitch::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
(void)payload;
|
||||
|
||||
if (_commandCallback && HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_commandCallback(state, this);
|
||||
}
|
||||
}
|
||||
|
||||
bool HASwitch::publishState(const bool state)
|
||||
{
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
||||
150
lib/home-assistant-integration/src/device-types/HASwitch.h
Normal file
150
lib/home-assistant-integration/src/device-types/HASwitch.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#ifndef AHA_HASWITCH_H
|
||||
#define AHA_HASWITCH_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SWITCH
|
||||
|
||||
#define HASWITCH_CALLBACK(name) void (*name)(bool state, HASwitch* sender)
|
||||
|
||||
/**
|
||||
* HASwitch allows to display on/off switch in the HA panel and receive commands on your device.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/switch.mqtt/
|
||||
*/
|
||||
class HASwitch : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the sensor. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HASwitch(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* 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(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Alias for `setState(true)`.
|
||||
*/
|
||||
inline bool turnOn()
|
||||
{ return setState(true); }
|
||||
|
||||
/**
|
||||
* Alias for `setState(false)`.
|
||||
*/
|
||||
inline bool turnOff()
|
||||
{ return setState(false); }
|
||||
|
||||
/**
|
||||
* Sets current state of the switch without publishing it 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 switch.
|
||||
*/
|
||||
inline void setCurrentState(const bool state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the switch.
|
||||
* By default it's `false`.
|
||||
*/
|
||||
inline bool getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/switch/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the sensor.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the switch command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the switch state.
|
||||
* In this mode the switch state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the on/off command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same switch.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the state must be reported back to HA using the HASwitch::setState method.
|
||||
*/
|
||||
inline void onCommand(HASWITCH_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const bool state);
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the switch (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The current state of the switch. By default it's `false`.
|
||||
bool _currentState;
|
||||
|
||||
/// The callback that will be called when switch command is received from the HA.
|
||||
HASWITCH_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "HATagScanner.h"
|
||||
#ifndef EX_ARDUINOHA_TAG_SCANNER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HATagScanner::HATagScanner(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentTag), uniqueId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HATagScanner::tagScanned(const char* tag)
|
||||
{
|
||||
if (!tag || strlen(tag) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HATopic), tag);
|
||||
}
|
||||
|
||||
void HATagScanner::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 2); // 2 - max properties nb
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->topic(AHATOFSTR(HATopic));
|
||||
}
|
||||
|
||||
void HATagScanner::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef AHA_HATAGSCANNER_H
|
||||
#define AHA_HATAGSCANNER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_TAG_SCANNER
|
||||
|
||||
/**
|
||||
* HATagScanner allow to produce scan events that can be used in the HA automation.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/tag.mqtt/
|
||||
*/
|
||||
class HATagScanner : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the scanner. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HATagScanner(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool tagScanned(const char* tag);
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
102
lib/home-assistant-integration/src/mocks/AUnitHelpers.h
Normal file
102
lib/home-assistant-integration/src/mocks/AUnitHelpers.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#ifndef AHA_AUNITHELPERS_H
|
||||
#define AHA_AUNITHELPERS_H
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
|
||||
#ifdef AUNITER
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(__AVR__)
|
||||
#include <MemoryUsage.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define initMqttTest(testDeviceId) \
|
||||
PubSubClientMock* mock = new PubSubClientMock(); \
|
||||
HADevice device(testDeviceId); \
|
||||
HAMqtt mqtt(mock, device); \
|
||||
mqtt.setDataPrefix("testData"); \
|
||||
mqtt.begin("testHost", "testUser", "testPass");
|
||||
|
||||
#define assertNoMqttMessage() \
|
||||
assertTrue(mock->getFlushedMessagesNb() == 0);
|
||||
|
||||
#define assertMqttMessage(index, eTopic, eMessage, eRetained) { \
|
||||
const __FlashStringHelper* messageP = F(eMessage); \
|
||||
size_t messageLen = strlen_P(reinterpret_cast<const char *>(messageP)); \
|
||||
assertTrue(mock->getFlushedMessagesNb() > 0); \
|
||||
assertTrue(mock->getFlushedMessagesNb() > index); \
|
||||
MqttMessage* publishedMessage = mock->getFlushedMessages()[index]; \
|
||||
assertEqual(eTopic, publishedMessage->topic); \
|
||||
assertEqual(messageP, publishedMessage->buffer); \
|
||||
assertEqual(messageLen, publishedMessage->bufferSize - 1); \
|
||||
assertEqual(eRetained, publishedMessage->retained); \
|
||||
}
|
||||
|
||||
#define assertSingleMqttMessage(eTopic, eMessage, eRetained) { \
|
||||
assertEqual(1, mock->getFlushedMessagesNb()); \
|
||||
assertMqttMessage(0, eTopic, eMessage, eRetained) \
|
||||
}
|
||||
|
||||
#define assertEntityConfig(mock, entity, expectedJson) \
|
||||
{ \
|
||||
mqtt.loop(); \
|
||||
assertMqttMessage(0, AHATOFSTR(ConfigTopic), expectedJson, true) \
|
||||
assertTrue(entity.getSerializer() == nullptr); \
|
||||
}
|
||||
|
||||
#define assertEntityConfigOnTopic(mock, entity, topic, expectedJson) \
|
||||
{ \
|
||||
mqtt.loop(); \
|
||||
assertMqttMessage(0, topic, expectedJson, true) \
|
||||
assertTrue(entity.getSerializer() == nullptr); \
|
||||
}
|
||||
|
||||
#ifdef AUNITER
|
||||
|
||||
#if defined(__AVR__)
|
||||
#define AHA_FREERAM mu_freeRam()
|
||||
#elif defined(ESP8266) || defined(ESP32)
|
||||
#define AHA_FREERAM ESP.getFreeHeap()
|
||||
#else
|
||||
#define AHA_FREERAM 0
|
||||
#endif
|
||||
|
||||
#define AHA_LEAKTRACKSTART \
|
||||
int freeRam = AHA_FREERAM;
|
||||
|
||||
#define AHA_LEAKTRACKEND \
|
||||
int diff = freeRam - AHA_FREERAM; \
|
||||
if (diff < 0) { diff *= -1; } \
|
||||
if (diff != 0) { \
|
||||
Serial.print(Test::getName().getFString()); \
|
||||
Serial.print(F(" memory leak: ")); \
|
||||
Serial.print(diff); \
|
||||
Serial.println(F("b")); \
|
||||
Test::fail(); \
|
||||
}
|
||||
#else
|
||||
// EpoxyDuino doesn't support memory tracking
|
||||
#define AHA_LEAKTRACKSTART
|
||||
#define AHA_LEAKTRACKEND
|
||||
#endif
|
||||
|
||||
#define AHA_TEST(suiteName, name) \
|
||||
class suiteName##_##name : public aunit::TestOnce { \
|
||||
public: \
|
||||
suiteName##_##name(); \
|
||||
void once() override; \
|
||||
void loop() override { \
|
||||
AHA_LEAKTRACKSTART \
|
||||
once(); \
|
||||
if (isNotDone()) { pass(); } \
|
||||
AHA_LEAKTRACKEND \
|
||||
} \
|
||||
} suiteName##_##name##_instance; \
|
||||
suiteName##_##name :: suiteName##_##name() { \
|
||||
init(AUNIT_F(#suiteName "_" #name)); \
|
||||
} \
|
||||
void suiteName##_##name :: once()
|
||||
|
||||
#endif
|
||||
#endif
|
||||
268
lib/home-assistant-integration/src/mocks/PubSubClientMock.cpp
Normal file
268
lib/home-assistant-integration/src/mocks/PubSubClientMock.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
#include "PubSubClientMock.h"
|
||||
#ifdef ARDUINOHA_TEST
|
||||
|
||||
#include "../ArduinoHADefines.h"
|
||||
|
||||
PubSubClientMock::PubSubClientMock() :
|
||||
_pendingMessage(nullptr),
|
||||
_flushedMessages(nullptr),
|
||||
_flushedMessagesNb(0),
|
||||
_subscriptions(nullptr),
|
||||
_subscriptionsNb(0),
|
||||
callback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PubSubClientMock::~PubSubClientMock()
|
||||
{
|
||||
if (_pendingMessage) {
|
||||
delete _pendingMessage;
|
||||
}
|
||||
|
||||
clearFlushedMessages();
|
||||
clearSubscriptions();
|
||||
}
|
||||
|
||||
bool PubSubClientMock::loop()
|
||||
{
|
||||
return connected();
|
||||
}
|
||||
|
||||
void PubSubClientMock::disconnect()
|
||||
{
|
||||
_connection.connected = false;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::connected()
|
||||
{
|
||||
return _connection.connected;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::connect(
|
||||
const char *id,
|
||||
const char *user,
|
||||
const char *pass,
|
||||
const char* willTopic,
|
||||
uint8_t willQos,
|
||||
bool willRetain,
|
||||
const char* willMessage,
|
||||
bool cleanSession
|
||||
)
|
||||
{
|
||||
(void)willQos;
|
||||
(void)cleanSession;
|
||||
|
||||
_connection.connected = true;
|
||||
_connection.id = id;
|
||||
_connection.user = user;
|
||||
_connection.pass = pass;
|
||||
|
||||
_lastWill.topic = willTopic;
|
||||
_lastWill.message = willMessage;
|
||||
_lastWill.retain = willRetain;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::connectDummy()
|
||||
{
|
||||
_connection.connected = true;
|
||||
_connection.id = "dummyId";
|
||||
_connection.user = nullptr;
|
||||
_connection.pass = nullptr;
|
||||
|
||||
_lastWill.topic = nullptr;
|
||||
_lastWill.message = nullptr;
|
||||
_lastWill.retain = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PubSubClientMock& PubSubClientMock::setServer(IPAddress ip, uint16_t port)
|
||||
{
|
||||
_connection.ip = ip;
|
||||
_connection.port = port;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubSubClientMock& PubSubClientMock::setServer(
|
||||
const char * domain,
|
||||
uint16_t port
|
||||
)
|
||||
{
|
||||
_connection.domain = domain;
|
||||
_connection.port = port;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubSubClientMock& PubSubClientMock::setCallback(MQTT_CALLBACK_SIGNATURE)
|
||||
{
|
||||
this->callback = callback;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::beginPublish(
|
||||
const char* topic,
|
||||
unsigned int plength,
|
||||
bool retained
|
||||
)
|
||||
{
|
||||
if (!connected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_pendingMessage) {
|
||||
delete _pendingMessage;
|
||||
}
|
||||
|
||||
_pendingMessage = new MqttMessage();
|
||||
_pendingMessage->retained = retained;
|
||||
|
||||
{
|
||||
size_t size = strlen(topic) + 1;
|
||||
_pendingMessage->topic = new char[size];
|
||||
_pendingMessage->topicSize = size;
|
||||
|
||||
memset(_pendingMessage->topic, 0, size);
|
||||
memcpy(_pendingMessage->topic, topic, size);
|
||||
}
|
||||
|
||||
{
|
||||
size_t size = plength + 1;
|
||||
_pendingMessage->buffer = new char[size];
|
||||
_pendingMessage->bufferSize = size;
|
||||
|
||||
memset(_pendingMessage->buffer, 0, size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t PubSubClientMock::write(const uint8_t *buffer, size_t size)
|
||||
{
|
||||
if (!_pendingMessage || !_pendingMessage->buffer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
strncat(_pendingMessage->buffer, (const char*)buffer, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t PubSubClientMock::print(const __FlashStringHelper* buffer)
|
||||
{
|
||||
const size_t len = strlen_P(reinterpret_cast<const char*>(buffer));
|
||||
char data[len + 1]; // including null terminator
|
||||
strcpy_P(data, reinterpret_cast<const char*>(buffer));
|
||||
|
||||
return write((const uint8_t*)(data), len);
|
||||
}
|
||||
|
||||
int PubSubClientMock::endPublish()
|
||||
{
|
||||
if (!_pendingMessage) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t messageSize = _pendingMessage->bufferSize;
|
||||
uint8_t index = _flushedMessagesNb;
|
||||
|
||||
_flushedMessagesNb++;
|
||||
_flushedMessages = static_cast<MqttMessage**>(
|
||||
realloc(_flushedMessages, _flushedMessagesNb * sizeof(MqttMessage*))
|
||||
);
|
||||
|
||||
_flushedMessages[index] = _pendingMessage; // handover memory responsibility
|
||||
_pendingMessage = nullptr; // do not call destructor
|
||||
|
||||
return messageSize;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::subscribe(const char* topic)
|
||||
{
|
||||
uint8_t index = _subscriptionsNb;
|
||||
|
||||
_subscriptionsNb++;
|
||||
_subscriptions = static_cast<MqttSubscription**>(
|
||||
realloc(_subscriptions, _subscriptionsNb * sizeof(MqttSubscription*))
|
||||
);
|
||||
|
||||
size_t topicSize = strlen(topic) + 1;
|
||||
MqttSubscription* subscription = new MqttSubscription();
|
||||
subscription->topic = new char[topicSize];
|
||||
memcpy(subscription->topic, topic, topicSize);
|
||||
|
||||
_subscriptions[index] = subscription;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PubSubClientMock::clearFlushedMessages()
|
||||
{
|
||||
if (_flushedMessages) {
|
||||
for (uint8_t i = 0; i < _flushedMessagesNb; i++) {
|
||||
delete _flushedMessages[i];
|
||||
}
|
||||
|
||||
delete _flushedMessages;
|
||||
}
|
||||
|
||||
_flushedMessagesNb = 0;
|
||||
}
|
||||
|
||||
void PubSubClientMock::clearSubscriptions()
|
||||
{
|
||||
if (_subscriptions) {
|
||||
for (uint8_t i = 0; i < _subscriptionsNb; i++) {
|
||||
delete _subscriptions[i];
|
||||
}
|
||||
|
||||
delete _subscriptions;
|
||||
}
|
||||
|
||||
_subscriptionsNb = 0;
|
||||
}
|
||||
|
||||
void PubSubClientMock::fakeMessage(const char* topic, const char* message)
|
||||
{
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t len = strlen(message);
|
||||
uint8_t data[len];
|
||||
memcpy(data, message, len);
|
||||
|
||||
callback(const_cast<char*>(topic), data, len);
|
||||
}
|
||||
|
||||
void PubSubClientMock::fakeMessage(
|
||||
const __FlashStringHelper* topic,
|
||||
const char* message
|
||||
)
|
||||
{
|
||||
char topicStr[strlen_P(AHAFROMFSTR(topic)) + 1];
|
||||
topicStr[0] = 0;
|
||||
strcpy_P(topicStr, AHAFROMFSTR(topic));
|
||||
|
||||
fakeMessage(topicStr, message);
|
||||
}
|
||||
|
||||
void PubSubClientMock::fakeMessage(
|
||||
const __FlashStringHelper* topic,
|
||||
const __FlashStringHelper* message
|
||||
)
|
||||
{
|
||||
char topicStr[strlen_P(AHAFROMFSTR(topic)) + 1];
|
||||
topicStr[0] = 0;
|
||||
strcpy_P(topicStr, AHAFROMFSTR(topic));
|
||||
|
||||
char messageStr[strlen_P(AHAFROMFSTR(message)) + 1];
|
||||
messageStr[0] = 0;
|
||||
strcpy_P(messageStr, AHAFROMFSTR(message));
|
||||
|
||||
fakeMessage(topicStr, messageStr);
|
||||
}
|
||||
|
||||
#endif
|
||||
166
lib/home-assistant-integration/src/mocks/PubSubClientMock.h
Normal file
166
lib/home-assistant-integration/src/mocks/PubSubClientMock.h
Normal file
@@ -0,0 +1,166 @@
|
||||
#ifndef AHA_PUBSUBCLIENTMOCK_H
|
||||
#define AHA_PUBSUBCLIENTMOCK_H
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
#include <functional>
|
||||
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
|
||||
#else
|
||||
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
|
||||
#endif
|
||||
|
||||
struct MqttMessage
|
||||
{
|
||||
char* topic;
|
||||
size_t topicSize;
|
||||
char* buffer;
|
||||
size_t bufferSize;
|
||||
bool retained;
|
||||
|
||||
MqttMessage() :
|
||||
topic(nullptr),
|
||||
topicSize(0),
|
||||
buffer(nullptr),
|
||||
bufferSize(0),
|
||||
retained(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~MqttMessage()
|
||||
{
|
||||
if (topic) {
|
||||
delete topic;
|
||||
}
|
||||
|
||||
if (buffer) {
|
||||
delete buffer;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct MqttSubscription {
|
||||
char* topic;
|
||||
|
||||
MqttSubscription() :
|
||||
topic(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~MqttSubscription()
|
||||
{
|
||||
if (topic) {
|
||||
delete topic;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct MqttConnection
|
||||
{
|
||||
bool connected;
|
||||
const char* domain;
|
||||
IPAddress ip;
|
||||
uint16_t port;
|
||||
const char* id;
|
||||
const char* user;
|
||||
const char* pass;
|
||||
|
||||
MqttConnection() :
|
||||
connected(false),
|
||||
domain(nullptr),
|
||||
port(0),
|
||||
id(nullptr),
|
||||
user(nullptr),
|
||||
pass(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct MqttWill
|
||||
{
|
||||
const char* topic;
|
||||
const char* message;
|
||||
bool retain;
|
||||
|
||||
MqttWill() :
|
||||
topic(nullptr),
|
||||
message(nullptr),
|
||||
retain(false)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
class PubSubClientMock
|
||||
{
|
||||
public:
|
||||
PubSubClientMock();
|
||||
~PubSubClientMock();
|
||||
|
||||
bool loop();
|
||||
void disconnect();
|
||||
bool connected();
|
||||
bool connect(
|
||||
const char *id,
|
||||
const char *user,
|
||||
const char *pass,
|
||||
const char* willTopic,
|
||||
uint8_t willQos,
|
||||
bool willRetain,
|
||||
const char* willMessage,
|
||||
bool cleanSession
|
||||
);
|
||||
bool connectDummy();
|
||||
PubSubClientMock& setServer(IPAddress ip, uint16_t port);
|
||||
PubSubClientMock& setServer(const char* domain, uint16_t port);
|
||||
PubSubClientMock& setCallback(MQTT_CALLBACK_SIGNATURE);
|
||||
|
||||
bool beginPublish(const char* topic, unsigned int plength, bool retained);
|
||||
size_t write(const uint8_t *buffer, size_t size);
|
||||
size_t print(const __FlashStringHelper* buffer);
|
||||
int endPublish();
|
||||
bool subscribe(const char* topic);
|
||||
|
||||
inline uint8_t getFlushedMessagesNb() const
|
||||
{ return _flushedMessagesNb; }
|
||||
|
||||
inline MqttMessage** getFlushedMessages() const
|
||||
{ return _flushedMessages; }
|
||||
|
||||
inline uint8_t getSubscriptionsNb() const
|
||||
{ return _subscriptionsNb; }
|
||||
|
||||
inline MqttSubscription** getSubscriptions() const
|
||||
{ return _subscriptions; }
|
||||
|
||||
inline const MqttConnection& getConnection() const
|
||||
{ return _connection; }
|
||||
|
||||
inline const MqttWill& getLastWill() const
|
||||
{ return _lastWill; }
|
||||
|
||||
void clearFlushedMessages();
|
||||
void clearSubscriptions();
|
||||
void fakeMessage(const char* topic, const char* message);
|
||||
void fakeMessage(const __FlashStringHelper* topic, const char* message);
|
||||
void fakeMessage(const __FlashStringHelper* topic, const __FlashStringHelper* message);
|
||||
|
||||
private:
|
||||
MqttMessage* _pendingMessage;
|
||||
MqttMessage** _flushedMessages;
|
||||
uint8_t _flushedMessagesNb;
|
||||
MqttSubscription** _subscriptions;
|
||||
uint8_t _subscriptionsNb;
|
||||
MqttConnection _connection;
|
||||
MqttWill _lastWill;
|
||||
MQTT_CALLBACK_SIGNATURE;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
195
lib/home-assistant-integration/src/utils/HADictionary.cpp
Normal file
195
lib/home-assistant-integration/src/utils/HADictionary.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#include <Arduino.h>
|
||||
#include "HADictionary.h"
|
||||
|
||||
// components
|
||||
const char HAComponentBinarySensor[] PROGMEM = {"binary_sensor"};
|
||||
const char HAComponentButton[] PROGMEM = {"button"};
|
||||
const char HAComponentCamera[] PROGMEM = {"camera"};
|
||||
const char HAComponentCover[] PROGMEM = {"cover"};
|
||||
const char HAComponentDeviceTracker[] PROGMEM = {"device_tracker"};
|
||||
const char HAComponentDeviceAutomation[] PROGMEM = {"device_automation"};
|
||||
const char HAComponentLock[] PROGMEM = {"lock"};
|
||||
const char HAComponentNumber[] PROGMEM = {"number"};
|
||||
const char HAComponentSelect[] PROGMEM = {"select"};
|
||||
const char HAComponentSensor[] PROGMEM = {"sensor"};
|
||||
const char HAComponentSwitch[] PROGMEM = {"switch"};
|
||||
const char HAComponentTag[] PROGMEM = {"tag"};
|
||||
const char HAComponentScene[] PROGMEM = {"scene"};
|
||||
const char HAComponentFan[] PROGMEM = {"fan"};
|
||||
const char HAComponentLight[] PROGMEM = {"light"};
|
||||
const char HAComponentClimate[] PROGMEM = {"climate"};
|
||||
|
||||
// decorators
|
||||
const char HASerializerSlash[] PROGMEM = {"/"};
|
||||
const char HASerializerJsonDataPrefix[] PROGMEM = {"{"};
|
||||
const char HASerializerJsonDataSuffix[] PROGMEM = {"}"};
|
||||
const char HASerializerJsonPropertyPrefix[] PROGMEM = {"\""};
|
||||
const char HASerializerJsonPropertySuffix[] PROGMEM = {"\":"};
|
||||
const char HASerializerJsonEscapeChar[] PROGMEM = {"\""};
|
||||
const char HASerializerJsonPropertiesSeparator[] PROGMEM = {","};
|
||||
const char HASerializerJsonArrayPrefix[] PROGMEM = {"["};
|
||||
const char HASerializerJsonArraySuffix[] PROGMEM = {"]"};
|
||||
const char HASerializerUnderscore[] PROGMEM = {"_"};
|
||||
|
||||
// properties
|
||||
const char HADeviceIdentifiersProperty[] PROGMEM = {"ids"};
|
||||
const char HADeviceManufacturerProperty[] PROGMEM = {"mf"};
|
||||
const char HADeviceModelProperty[] PROGMEM = {"mdl"};
|
||||
const char HADeviceSoftwareVersionProperty[] PROGMEM = {"sw"};
|
||||
const char HANameProperty[] PROGMEM = {"name"};
|
||||
const char HAUniqueIdProperty[] PROGMEM = {"uniq_id"};
|
||||
const char HADeviceProperty[] PROGMEM = {"dev"};
|
||||
const char HADeviceClassProperty[] PROGMEM = {"dev_cla"};
|
||||
const char HAIconProperty[] PROGMEM = {"ic"};
|
||||
const char HARetainProperty[] PROGMEM = {"ret"};
|
||||
const char HASourceTypeProperty[] PROGMEM = {"src_type"};
|
||||
const char HAEncodingProperty[] PROGMEM = {"e"};
|
||||
const char HAOptimisticProperty[] PROGMEM = {"opt"};
|
||||
const char HAAutomationTypeProperty[] PROGMEM = {"atype"};
|
||||
const char HATypeProperty[] PROGMEM = {"type"};
|
||||
const char HASubtypeProperty[] PROGMEM = {"stype"};
|
||||
const char HAForceUpdateProperty[] PROGMEM = {"frc_upd"};
|
||||
const char HAUnitOfMeasurementProperty[] PROGMEM = {"unit_of_meas"};
|
||||
const char HAValueTemplateProperty[] PROGMEM = {"val_tpl"};
|
||||
const char HAOptionsProperty[] PROGMEM = {"options"};
|
||||
const char HAMinProperty[] PROGMEM = {"min"};
|
||||
const char HAMaxProperty[] PROGMEM = {"max"};
|
||||
const char HAStepProperty[] PROGMEM = {"step"};
|
||||
const char HAModeProperty[] PROGMEM = {"mode"};
|
||||
const char HACommandTemplateProperty[] PROGMEM = {"cmd_tpl"};
|
||||
const char HASpeedRangeMaxProperty[] PROGMEM = {"spd_rng_max"};
|
||||
const char HASpeedRangeMinProperty[] PROGMEM = {"spd_rng_min"};
|
||||
const char HABrightnessScaleProperty[] PROGMEM = {"bri_scl"};
|
||||
const char HAMinMiredsProperty[] PROGMEM = {"min_mirs"};
|
||||
const char HAMaxMiredsProperty[] PROGMEM = {"max_mirs"};
|
||||
const char HATemperatureUnitProperty[] PROGMEM = {"temp_unit"};
|
||||
const char HAMinTempProperty[] PROGMEM = {"min_temp"};
|
||||
const char HAMaxTempProperty[] PROGMEM = {"max_temp"};
|
||||
const char HATempStepProperty[] PROGMEM = {"temp_step"};
|
||||
const char HAFanModesProperty[] PROGMEM = {"fan_modes"};
|
||||
const char HASwingModesProperty[] PROGMEM = {"swing_modes"};
|
||||
const char HAModesProperty[] PROGMEM = {"modes"};
|
||||
const char HATemperatureCommandTemplateProperty[] PROGMEM = {"temp_cmd_tpl"};
|
||||
const char HAPayloadOnProperty[] PROGMEM = {"pl_on"};
|
||||
|
||||
// topics
|
||||
const char HAConfigTopic[] PROGMEM = {"config"};
|
||||
const char HAAvailabilityTopic[] PROGMEM = {"avty_t"};
|
||||
const char HATopic[] PROGMEM = {"t"};
|
||||
const char HAStateTopic[] PROGMEM = {"stat_t"};
|
||||
const char HACommandTopic[] PROGMEM = {"cmd_t"};
|
||||
const char HAPositionTopic[] PROGMEM = {"pos_t"};
|
||||
const char HAPercentageStateTopic[] PROGMEM = {"pct_stat_t"};
|
||||
const char HAPercentageCommandTopic[] PROGMEM = {"pct_cmd_t"};
|
||||
const char HABrightnessCommandTopic[] PROGMEM = {"bri_cmd_t"};
|
||||
const char HABrightnessStateTopic[] PROGMEM = {"bri_stat_t"};
|
||||
const char HAColorTemperatureCommandTopic[] PROGMEM = {"clr_temp_cmd_t"};
|
||||
const char HAColorTemperatureStateTopic[] PROGMEM = {"clr_temp_stat_t"};
|
||||
const char HACurrentTemperatureTopic[] PROGMEM = {"curr_temp_t"};
|
||||
const char HAActionTopic[] PROGMEM = {"act_t"};
|
||||
const char HAAuxCommandTopic[] PROGMEM = {"aux_cmd_t"};
|
||||
const char HAAuxStateTopic[] PROGMEM = {"aux_stat_t"};
|
||||
const char HAPowerCommandTopic[] PROGMEM = {"pow_cmd_t"};
|
||||
const char HAFanModeCommandTopic[] PROGMEM = {"fan_mode_cmd_t"};
|
||||
const char HAFanModeStateTopic[] PROGMEM = {"fan_mode_stat_t"};
|
||||
const char HASwingModeCommandTopic[] PROGMEM = {"swing_mode_cmd_t"};
|
||||
const char HASwingModeStateTopic[] PROGMEM = {"swing_mode_stat_t"};
|
||||
const char HAModeCommandTopic[] PROGMEM = {"mode_cmd_t"};
|
||||
const char HAModeStateTopic[] PROGMEM = {"mode_stat_t"};
|
||||
const char HATemperatureCommandTopic[] PROGMEM = {"temp_cmd_t"};
|
||||
const char HATemperatureStateTopic[] PROGMEM = {"temp_stat_t"};
|
||||
const char HARGBCommandTopic[] PROGMEM = {"rgb_cmd_t"};
|
||||
const char HARGBStateTopic[] PROGMEM = {"rgb_stat_t"};
|
||||
|
||||
// misc
|
||||
const char HAOnline[] PROGMEM = {"online"};
|
||||
const char HAOffline[] PROGMEM = {"offline"};
|
||||
const char HAStateOn[] PROGMEM = {"ON"};
|
||||
const char HAStateOff[] PROGMEM = {"OFF"};
|
||||
const char HAStateLocked[] PROGMEM = {"LOCKED"};
|
||||
const char HAStateUnlocked[] PROGMEM = {"UNLOCKED"};
|
||||
const char HAStateNone[] PROGMEM = {"None"};
|
||||
const char HATrue[] PROGMEM = {"true"};
|
||||
const char HAFalse[] PROGMEM = {"false"};
|
||||
const char HAHome[] PROGMEM = {"home"};
|
||||
const char HANotHome[] PROGMEM = {"not_home"};
|
||||
const char HATrigger[] PROGMEM = {"trigger"};
|
||||
const char HAModeBox[] PROGMEM = {"box"};
|
||||
const char HAModeSlider[] PROGMEM = {"slider"};
|
||||
|
||||
// covers
|
||||
const char HAClosedState[] PROGMEM = {"closed"};
|
||||
const char HAClosingState[] PROGMEM = {"closing"};
|
||||
const char HAOpenState[] PROGMEM = {"open"};
|
||||
const char HAOpeningState[] PROGMEM = {"opening"};
|
||||
const char HAStoppedState[] PROGMEM = {"stopped"};
|
||||
|
||||
// commands
|
||||
const char HAOpenCommand[] PROGMEM = {"OPEN"};
|
||||
const char HACloseCommand[] PROGMEM = {"CLOSE"};
|
||||
const char HAStopCommand[] PROGMEM = {"STOP"};
|
||||
const char HALockCommand[] PROGMEM = {"LOCK"};
|
||||
const char HAUnlockCommand[] PROGMEM = {"UNLOCK"};
|
||||
|
||||
// device tracker
|
||||
const char HAGPSType[] PROGMEM = {"gps"};
|
||||
const char HARouterType[] PROGMEM = {"router"};
|
||||
const char HABluetoothType[] PROGMEM = {"bluetooth"};
|
||||
const char HABluetoothLEType[] PROGMEM = {"bluetooth_le"};
|
||||
|
||||
// camera
|
||||
const char HAEncodingBase64[] PROGMEM = {"b64"};
|
||||
|
||||
// trigger
|
||||
const char HAButtonShortPressType[] PROGMEM = {"button_short_press"};
|
||||
const char HAButtonShortReleaseType[] PROGMEM = {"button_short_release"};
|
||||
const char HAButtonLongPressType[] PROGMEM = {"button_long_press"};
|
||||
const char HAButtonLongReleaseType[] PROGMEM = {"button_long_release"};
|
||||
const char HAButtonDoublePressType[] PROGMEM = {"button_double_press"};
|
||||
const char HAButtonTriplePressType[] PROGMEM = {"button_triple_press"};
|
||||
const char HAButtonQuadruplePressType[] PROGMEM = {"button_quadruple_press"};
|
||||
const char HAButtonQuintuplePressType[] PROGMEM = {"button_quintuple_press"};
|
||||
const char HATurnOnSubtype[] PROGMEM = {"turn_on"};
|
||||
const char HATurnOffSubtype[] PROGMEM = {"turn_off"};
|
||||
const char HAButton1Subtype[] PROGMEM = {"button_1"};
|
||||
const char HAButton2Subtype[] PROGMEM = {"button_2"};
|
||||
const char HAButton3Subtype[] PROGMEM = {"button_3"};
|
||||
const char HAButton4Subtype[] PROGMEM = {"button_4"};
|
||||
const char HAButton5Subtype[] PROGMEM = {"button_5"};
|
||||
const char HAButton6Subtype[] PROGMEM = {"button_6"};
|
||||
|
||||
// actions
|
||||
const char HAActionOff[] PROGMEM = {"off"};
|
||||
const char HAActionHeating[] PROGMEM = {"heating"};
|
||||
const char HAActionCooling[] PROGMEM = {"cooling"};
|
||||
const char HAActionDrying[] PROGMEM = {"drying"};
|
||||
const char HAActionIdle[] PROGMEM = {"idle"};
|
||||
const char HAActionFan[] PROGMEM = {"fan"};
|
||||
|
||||
// fan modes
|
||||
const char HAFanModeAuto[] PROGMEM = {"auto"};
|
||||
const char HAFanModeLow[] PROGMEM = {"low"};
|
||||
const char HAFanModeMedium[] PROGMEM = {"medium"};
|
||||
const char HAFanModeHigh[] PROGMEM = {"high"};
|
||||
|
||||
// swing modes
|
||||
const char HASwingModeOn[] PROGMEM = {"on"};
|
||||
const char HASwingModeOff[] PROGMEM = {"off"};
|
||||
|
||||
// HVAC modes
|
||||
const char HAModeAuto[] PROGMEM = {"auto"};
|
||||
const char HAModeOff[] PROGMEM = {"off"};
|
||||
const char HAModeCool[] PROGMEM = {"cool"};
|
||||
const char HAModeHeat[] PROGMEM = {"heat"};
|
||||
const char HAModeDry[] PROGMEM = {"dry"};
|
||||
const char HAModeFanOnly[] PROGMEM = {"fan_only"};
|
||||
|
||||
// other
|
||||
const char HAHexMap[] PROGMEM = {"0123456789abcdef"};
|
||||
|
||||
// value templates
|
||||
const char HAValueTemplateFloatP1[] PROGMEM = {"{{int(float(value)*10**1)}}"};
|
||||
const char HAValueTemplateFloatP2[] PROGMEM = {"{{int(float(value)*10**2)}}"};
|
||||
const char HAValueTemplateFloatP3[] PROGMEM = {"{{int(float(value)*10**3)}}"};
|
||||
const char HATemperatureUnitC[] PROGMEM = {"C"};
|
||||
const char HATemperatureUnitF[] PROGMEM = {"F"};
|
||||
197
lib/home-assistant-integration/src/utils/HADictionary.h
Normal file
197
lib/home-assistant-integration/src/utils/HADictionary.h
Normal file
@@ -0,0 +1,197 @@
|
||||
#ifndef AHA_HADICTIONARY_H
|
||||
#define AHA_HADICTIONARY_H
|
||||
|
||||
// components
|
||||
extern const char HAComponentBinarySensor[];
|
||||
extern const char HAComponentButton[];
|
||||
extern const char HAComponentCamera[];
|
||||
extern const char HAComponentCover[];
|
||||
extern const char HAComponentDeviceTracker[];
|
||||
extern const char HAComponentDeviceAutomation[];
|
||||
extern const char HAComponentLock[];
|
||||
extern const char HAComponentNumber[];
|
||||
extern const char HAComponentSelect[];
|
||||
extern const char HAComponentSensor[];
|
||||
extern const char HAComponentSwitch[];
|
||||
extern const char HAComponentTag[];
|
||||
extern const char HAComponentScene[];
|
||||
extern const char HAComponentFan[];
|
||||
extern const char HAComponentLight[];
|
||||
extern const char HAComponentClimate[];
|
||||
|
||||
// decorators
|
||||
extern const char HASerializerSlash[];
|
||||
extern const char HASerializerJsonDataPrefix[];
|
||||
extern const char HASerializerJsonDataSuffix[];
|
||||
extern const char HASerializerJsonPropertyPrefix[];
|
||||
extern const char HASerializerJsonPropertySuffix[];
|
||||
extern const char HASerializerJsonEscapeChar[];
|
||||
extern const char HASerializerJsonPropertiesSeparator[];
|
||||
extern const char HASerializerJsonArrayPrefix[];
|
||||
extern const char HASerializerJsonArraySuffix[];
|
||||
extern const char HASerializerUnderscore[];
|
||||
|
||||
// properties
|
||||
extern const char HADeviceIdentifiersProperty[];
|
||||
extern const char HADeviceManufacturerProperty[];
|
||||
extern const char HADeviceModelProperty[];
|
||||
extern const char HADeviceSoftwareVersionProperty[];
|
||||
extern const char HANameProperty[];
|
||||
extern const char HAUniqueIdProperty[];
|
||||
extern const char HADeviceProperty[];
|
||||
extern const char HADeviceClassProperty[];
|
||||
extern const char HAIconProperty[];
|
||||
extern const char HARetainProperty[];
|
||||
extern const char HASourceTypeProperty[];
|
||||
extern const char HAEncodingProperty[];
|
||||
extern const char HAOptimisticProperty[];
|
||||
extern const char HAAutomationTypeProperty[];
|
||||
extern const char HATypeProperty[];
|
||||
extern const char HASubtypeProperty[];
|
||||
extern const char HAForceUpdateProperty[];
|
||||
extern const char HAUnitOfMeasurementProperty[];
|
||||
extern const char HAValueTemplateProperty[];
|
||||
extern const char HAOptionsProperty[];
|
||||
extern const char HAMinProperty[];
|
||||
extern const char HAMaxProperty[];
|
||||
extern const char HAStepProperty[];
|
||||
extern const char HAModeProperty[];
|
||||
extern const char HACommandTemplateProperty[];
|
||||
extern const char HASpeedRangeMaxProperty[];
|
||||
extern const char HASpeedRangeMinProperty[];
|
||||
extern const char HABrightnessScaleProperty[];
|
||||
extern const char HAMinMiredsProperty[];
|
||||
extern const char HAMaxMiredsProperty[];
|
||||
extern const char HATemperatureUnitProperty[];
|
||||
extern const char HAMinTempProperty[];
|
||||
extern const char HAMaxTempProperty[];
|
||||
extern const char HATempStepProperty[];
|
||||
extern const char HAFanModesProperty[];
|
||||
extern const char HASwingModesProperty[];
|
||||
extern const char HAModesProperty[];
|
||||
extern const char HATemperatureCommandTemplateProperty[];
|
||||
extern const char HAPayloadOnProperty[];
|
||||
|
||||
// topics
|
||||
extern const char HAConfigTopic[];
|
||||
extern const char HAAvailabilityTopic[];
|
||||
extern const char HATopic[];
|
||||
extern const char HAStateTopic[];
|
||||
extern const char HACommandTopic[];
|
||||
extern const char HAPositionTopic[];
|
||||
extern const char HAPercentageStateTopic[];
|
||||
extern const char HAPercentageCommandTopic[];
|
||||
extern const char HABrightnessCommandTopic[];
|
||||
extern const char HABrightnessStateTopic[];
|
||||
extern const char HAColorTemperatureCommandTopic[];
|
||||
extern const char HAColorTemperatureStateTopic[];
|
||||
extern const char HACurrentTemperatureTopic[];
|
||||
extern const char HAActionTopic[];
|
||||
extern const char HAAuxCommandTopic[];
|
||||
extern const char HAAuxStateTopic[];
|
||||
extern const char HAPowerCommandTopic[];
|
||||
extern const char HAFanModeCommandTopic[];
|
||||
extern const char HAFanModeStateTopic[];
|
||||
extern const char HASwingModeCommandTopic[];
|
||||
extern const char HASwingModeStateTopic[];
|
||||
extern const char HAModeCommandTopic[];
|
||||
extern const char HAModeStateTopic[];
|
||||
extern const char HATemperatureCommandTopic[];
|
||||
extern const char HATemperatureStateTopic[];
|
||||
extern const char HARGBCommandTopic[];
|
||||
extern const char HARGBStateTopic[];
|
||||
|
||||
// misc
|
||||
extern const char HAOnline[];
|
||||
extern const char HAOffline[];
|
||||
extern const char HAStateOn[];
|
||||
extern const char HAStateOff[];
|
||||
extern const char HAStateLocked[];
|
||||
extern const char HAStateUnlocked[];
|
||||
extern const char HAStateNone[];
|
||||
extern const char HATrue[];
|
||||
extern const char HAFalse[];
|
||||
extern const char HAHome[];
|
||||
extern const char HANotHome[];
|
||||
extern const char HATrigger[];
|
||||
extern const char HAModeBox[];
|
||||
extern const char HAModeSlider[];
|
||||
|
||||
// covers
|
||||
extern const char HAClosedState[];
|
||||
extern const char HAClosingState[];
|
||||
extern const char HAOpenState[];
|
||||
extern const char HAOpeningState[];
|
||||
extern const char HAStoppedState[];
|
||||
|
||||
// commands
|
||||
extern const char HAOpenCommand[];
|
||||
extern const char HACloseCommand[];
|
||||
extern const char HAStopCommand[];
|
||||
extern const char HALockCommand[];
|
||||
extern const char HAUnlockCommand[];
|
||||
|
||||
// device tracker
|
||||
extern const char HAGPSType[];
|
||||
extern const char HARouterType[];
|
||||
extern const char HABluetoothType[];
|
||||
extern const char HABluetoothLEType[];
|
||||
|
||||
// camera
|
||||
extern const char HAEncodingBase64[];
|
||||
|
||||
// trigger
|
||||
extern const char HAButtonShortPressType[];
|
||||
extern const char HAButtonShortReleaseType[];
|
||||
extern const char HAButtonLongPressType[];
|
||||
extern const char HAButtonLongReleaseType[];
|
||||
extern const char HAButtonDoublePressType[];
|
||||
extern const char HAButtonTriplePressType[];
|
||||
extern const char HAButtonQuadruplePressType[];
|
||||
extern const char HAButtonQuintuplePressType[];
|
||||
extern const char HATurnOnSubtype[];
|
||||
extern const char HATurnOffSubtype[];
|
||||
extern const char HAButton1Subtype[];
|
||||
extern const char HAButton2Subtype[];
|
||||
extern const char HAButton3Subtype[];
|
||||
extern const char HAButton4Subtype[];
|
||||
extern const char HAButton5Subtype[];
|
||||
extern const char HAButton6Subtype[];
|
||||
|
||||
// actions
|
||||
extern const char HAActionOff[];
|
||||
extern const char HAActionHeating[];
|
||||
extern const char HAActionCooling[];
|
||||
extern const char HAActionDrying[];
|
||||
extern const char HAActionIdle[];
|
||||
extern const char HAActionFan[];
|
||||
|
||||
// fan modes
|
||||
extern const char HAFanModeAuto[];
|
||||
extern const char HAFanModeLow[];
|
||||
extern const char HAFanModeMedium[];
|
||||
extern const char HAFanModeHigh[];
|
||||
|
||||
// swing modes
|
||||
extern const char HASwingModeOn[];
|
||||
extern const char HASwingModeOff[];
|
||||
|
||||
// HVAC modes
|
||||
extern const char HAModeAuto[];
|
||||
extern const char HAModeOff[];
|
||||
extern const char HAModeCool[];
|
||||
extern const char HAModeHeat[];
|
||||
extern const char HAModeDry[];
|
||||
extern const char HAModeFanOnly[];
|
||||
|
||||
// other
|
||||
extern const char HAHexMap[];
|
||||
|
||||
// value templates
|
||||
extern const char HAValueTemplateFloatP1[];
|
||||
extern const char HAValueTemplateFloatP2[];
|
||||
extern const char HAValueTemplateFloatP3[];
|
||||
extern const char HATemperatureUnitC[];
|
||||
extern const char HATemperatureUnitF[];
|
||||
|
||||
#endif
|
||||
214
lib/home-assistant-integration/src/utils/HANumeric.cpp
Normal file
214
lib/home-assistant-integration/src/utils/HANumeric.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
#include "HANumeric.h"
|
||||
|
||||
const uint8_t HANumeric::MaxDigitsNb = 19;
|
||||
|
||||
HANumeric HANumeric::fromStr(const uint8_t* buffer, const uint16_t length)
|
||||
{
|
||||
if (length == 0) {
|
||||
return HANumeric();
|
||||
}
|
||||
|
||||
const uint8_t* firstCh = &buffer[0];
|
||||
int64_t out = 0;
|
||||
bool isSigned = false;
|
||||
|
||||
if (*firstCh == '-') {
|
||||
isSigned = true;
|
||||
firstCh++;
|
||||
}
|
||||
|
||||
uint8_t digitsNb = isSigned ? length - 1 : length;
|
||||
if (digitsNb > MaxDigitsNb) {
|
||||
return HANumeric();
|
||||
}
|
||||
|
||||
uint64_t base = 1;
|
||||
const uint8_t* ptr = &buffer[length - 1];
|
||||
|
||||
while (ptr >= firstCh) {
|
||||
uint8_t digit = *ptr - '0';
|
||||
if (digit > 9) {
|
||||
return HANumeric();
|
||||
}
|
||||
|
||||
out += digit * base;
|
||||
ptr--;
|
||||
base *= 10;
|
||||
}
|
||||
|
||||
return HANumeric(isSigned ? out * -1 : out);
|
||||
}
|
||||
|
||||
HANumeric::HANumeric():
|
||||
_isSet(false),
|
||||
_value(0),
|
||||
_precision(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HANumeric::HANumeric(const float value, const uint8_t precision):
|
||||
_isSet(true),
|
||||
_precision(precision)
|
||||
{
|
||||
_value = value * static_cast<float>(getPrecisionBase());
|
||||
}
|
||||
|
||||
HANumeric::HANumeric(const int8_t value, const uint8_t precision):
|
||||
_isSet(true),
|
||||
_precision(precision)
|
||||
{
|
||||
_value = value * static_cast<int32_t>(getPrecisionBase());
|
||||
}
|
||||
|
||||
HANumeric::HANumeric(const int16_t value, const uint8_t precision):
|
||||
_isSet(true),
|
||||
_precision(precision)
|
||||
{
|
||||
_value = value * static_cast<int32_t>(getPrecisionBase());
|
||||
}
|
||||
|
||||
HANumeric::HANumeric(const int32_t value, const uint8_t precision):
|
||||
_isSet(true),
|
||||
_precision(precision)
|
||||
{
|
||||
_value = value * static_cast<int32_t>(getPrecisionBase());
|
||||
}
|
||||
|
||||
HANumeric::HANumeric(const uint8_t value, const uint8_t precision):
|
||||
_isSet(true),
|
||||
_precision(precision)
|
||||
{
|
||||
_value = value * getPrecisionBase();
|
||||
}
|
||||
|
||||
HANumeric::HANumeric(const uint16_t value, const uint8_t precision):
|
||||
_isSet(true),
|
||||
_precision(precision)
|
||||
{
|
||||
_value = value * getPrecisionBase();
|
||||
}
|
||||
|
||||
HANumeric::HANumeric(const uint32_t value, const uint8_t precision):
|
||||
_isSet(true),
|
||||
_precision(precision)
|
||||
{
|
||||
_value = value * getPrecisionBase();
|
||||
}
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
HANumeric::HANumeric(const int value, const uint8_t precision):
|
||||
_isSet(true),
|
||||
_precision(precision)
|
||||
{
|
||||
_value = value * static_cast<int>(getPrecisionBase());
|
||||
}
|
||||
#endif
|
||||
|
||||
HANumeric::HANumeric(const int64_t value):
|
||||
_isSet(true),
|
||||
_value(value),
|
||||
_precision(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
uint32_t HANumeric::getPrecisionBase() const
|
||||
{
|
||||
// using pow() increases the flash size by ~2KB
|
||||
switch (_precision) {
|
||||
case 1:
|
||||
return 10;
|
||||
|
||||
case 2:
|
||||
return 100;
|
||||
|
||||
case 3:
|
||||
return 1000;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t HANumeric::calculateSize() const
|
||||
{
|
||||
if (!_isSet) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t value = _value;
|
||||
const bool isSigned = value < 0;
|
||||
|
||||
if (isSigned) {
|
||||
value *= -1;
|
||||
}
|
||||
|
||||
uint8_t digitsNb = 1;
|
||||
while (value > 9) {
|
||||
value /= 10;
|
||||
digitsNb++;
|
||||
}
|
||||
|
||||
if (isSigned) {
|
||||
digitsNb++; // sign
|
||||
}
|
||||
|
||||
if (_precision > 0) {
|
||||
if (value == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// one digit + dot + decimal digits (+ sign)
|
||||
const uint8_t minValue = isSigned ? _precision + 3 : _precision + 2;
|
||||
return digitsNb >= minValue ? digitsNb + 1 : minValue;
|
||||
}
|
||||
|
||||
return digitsNb;
|
||||
}
|
||||
|
||||
uint16_t HANumeric::toStr(char* dst) const
|
||||
{
|
||||
char* prefixCh = &dst[0];
|
||||
if (!_isSet || _value == 0) {
|
||||
*prefixCh = '0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
int64_t value = _value;
|
||||
const uint8_t numberLength = calculateSize();
|
||||
if (value < 0) {
|
||||
value *= -1;
|
||||
*prefixCh = '-';
|
||||
prefixCh++;
|
||||
}
|
||||
|
||||
if (_precision > 0) {
|
||||
uint8_t i = _precision;
|
||||
char* dotPtr = prefixCh + 1;
|
||||
do {
|
||||
*prefixCh = '0';
|
||||
prefixCh++;
|
||||
} while(i-- > 0);
|
||||
|
||||
*dotPtr = '.';
|
||||
}
|
||||
|
||||
char* ch = &dst[numberLength - 1];
|
||||
char* lastCh = ch;
|
||||
char* dotPos = _precision > 0 ? &dst[numberLength - 1 - _precision] : nullptr;
|
||||
|
||||
while (value != 0) {
|
||||
if (ch == dotPos) {
|
||||
*dotPos = '.';
|
||||
ch--;
|
||||
continue;
|
||||
}
|
||||
|
||||
*ch = (value % 10) + '0';
|
||||
value /= 10;
|
||||
ch--;
|
||||
}
|
||||
|
||||
return lastCh - &dst[0] + 1;
|
||||
}
|
||||
233
lib/home-assistant-integration/src/utils/HANumeric.h
Normal file
233
lib/home-assistant-integration/src/utils/HANumeric.h
Normal file
@@ -0,0 +1,233 @@
|
||||
|
||||
#ifndef AHA_NUMERIC_H
|
||||
#define AHA_NUMERIC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* This class represents a numeric value that simplifies use of different types of numbers across the library.
|
||||
*/
|
||||
class HANumeric
|
||||
{
|
||||
public:
|
||||
/// The maximum number of digits that the base value can have (int64_t).
|
||||
static const uint8_t MaxDigitsNb;
|
||||
|
||||
/**
|
||||
* Deserializes number from the given buffer.
|
||||
* Please note that the class expected buffer to contain the base number.
|
||||
* For example, deserializing `1234` number and setting precision to `1`
|
||||
* results in representation of `123.4` float.
|
||||
*
|
||||
* @param buffer The buffer that contains the number.
|
||||
* @param length The length of the buffer.
|
||||
*/
|
||||
static HANumeric fromStr(const uint8_t* buffer, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Creates an empty number representation.
|
||||
*/
|
||||
HANumeric();
|
||||
|
||||
/**
|
||||
* Converts the given float into number representation of the given precision.
|
||||
* If the precision is set to zero the given float will be converted into integer.
|
||||
*
|
||||
* @param value The value that should be used as a base.
|
||||
* @param precision The number of digits in the decimal part.
|
||||
*/
|
||||
HANumeric(const float value, const uint8_t precision);
|
||||
|
||||
/**
|
||||
* Converts the given int8_t into number representation of the given precision.
|
||||
* If the precision is greater than zero the given value will be converted to float.
|
||||
*
|
||||
* @param value The value that should be used as a base.
|
||||
* @param precision The number of digits in the decimal part.
|
||||
*/
|
||||
HANumeric(const int8_t value, const uint8_t precision);
|
||||
|
||||
/**
|
||||
* Converts the given int16_t into number representation of the given precision.
|
||||
* If the precision is greater than zero the given value will be converted to float.
|
||||
*
|
||||
* @param value The value that should be used as a base.
|
||||
* @param precision The number of digits in the decimal part.
|
||||
*/
|
||||
HANumeric(const int16_t value, const uint8_t precision);
|
||||
|
||||
/**
|
||||
* Converts the given int32_t into number representation of the given precision.
|
||||
* If the precision is greater than zero the given value will be converted to float.
|
||||
*
|
||||
* @param value The value that should be used as a base.
|
||||
* @param precision The number of digits in the decimal part.
|
||||
*/
|
||||
HANumeric(const int32_t value, const uint8_t precision);
|
||||
|
||||
/**
|
||||
* Converts the given uint8_t into number representation of the given precision.
|
||||
* If the precision is greater than zero the given value will be converted to float.
|
||||
*
|
||||
* @param value The value that should be used as a base.
|
||||
* @param precision The number of digits in the decimal part.
|
||||
*/
|
||||
HANumeric(const uint8_t value, const uint8_t precision);
|
||||
|
||||
/**
|
||||
* Converts the given uint16_t into number representation of the given precision.
|
||||
* If the precision is greater than zero the given value will be converted to float.
|
||||
*
|
||||
* @param value The value that should be used as a base.
|
||||
* @param precision The number of digits in the decimal part.
|
||||
*/
|
||||
HANumeric(const uint16_t value, const uint8_t precision);
|
||||
|
||||
/**
|
||||
* Converts the given uint32_t into number representation of the given precision.
|
||||
* If the precision is greater than zero the given value will be converted to float.
|
||||
*
|
||||
* @param value The value that should be used as a base.
|
||||
* @param precision The number of digits in the decimal part.
|
||||
*/
|
||||
HANumeric(const uint32_t value, const uint8_t precision);
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
/**
|
||||
* Converts the given int into number representation of the given precision.
|
||||
* If the precision is greater than zero the given value will be converted to float.
|
||||
*
|
||||
* @param value The value that should be used as a base.
|
||||
* @param precision The number of digits in the decimal part.
|
||||
*/
|
||||
HANumeric(const int value, const uint8_t precision);
|
||||
#endif
|
||||
|
||||
void operator= (const HANumeric& a) {
|
||||
if (!a.isSet()) {
|
||||
reset();
|
||||
} else {
|
||||
_isSet = a.isSet();
|
||||
_value = a.getBaseValue();
|
||||
_precision = a.getPrecision();
|
||||
}
|
||||
}
|
||||
|
||||
bool operator== (const HANumeric& a) const {
|
||||
return (
|
||||
isSet() == a.isSet() &&
|
||||
getBaseValue() == a.getBaseValue() &&
|
||||
getPrecision() == a.getPrecision()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns multiplier that used to generate base value based on the precision.
|
||||
* The multiplier is generated using the formula: `pow(precision, 10)`.
|
||||
*/
|
||||
uint32_t getPrecisionBase() const;
|
||||
|
||||
/**
|
||||
* Returns size of the number
|
||||
*/
|
||||
uint8_t calculateSize() const;
|
||||
|
||||
/**
|
||||
* Converts the number to the string.
|
||||
*
|
||||
* @param dst Destination where the number will be saved.
|
||||
* The null terminator is not added at the end.
|
||||
* @return The number of written characters.
|
||||
* @note The `dst` size should be calculated using HANumeric::calculateSize() method plus 1 extra byte for the null terminator.
|
||||
*/
|
||||
uint16_t toStr(char* dst) const;
|
||||
|
||||
/**
|
||||
* Returns true if the base value is set.
|
||||
*/
|
||||
inline bool isSet() const
|
||||
{ return _isSet; }
|
||||
|
||||
/**
|
||||
* Sets the base value without converting it to the proper precision.
|
||||
*/
|
||||
inline void setBaseValue(int64_t value)
|
||||
{ _isSet = true; _value = value; }
|
||||
|
||||
/**
|
||||
* Returns the base value of the number.
|
||||
*/
|
||||
inline int64_t getBaseValue() const
|
||||
{ return _value; }
|
||||
|
||||
/**
|
||||
* Sets the precision of the number (number of digits in the decimal part).
|
||||
*
|
||||
* @param precision The precision to use.
|
||||
*/
|
||||
inline void setPrecision(const uint8_t precision)
|
||||
{ _precision = precision; }
|
||||
|
||||
/**
|
||||
* Returns the precision of the number.
|
||||
*/
|
||||
inline uint8_t getPrecision() const
|
||||
{ return _precision; }
|
||||
|
||||
/**
|
||||
* Resets the number to the defaults.
|
||||
*/
|
||||
inline void reset()
|
||||
{ _isSet = false; _value = 0; _precision = 0; }
|
||||
|
||||
inline bool isUInt8() const
|
||||
{ return _isSet && _precision == 0 && _value >= 0 && _value <= UINT8_MAX; }
|
||||
|
||||
inline bool isUInt16() const
|
||||
{ return _isSet && _precision == 0 && _value >= 0 && _value <= UINT16_MAX; }
|
||||
|
||||
inline bool isUInt32() const
|
||||
{ return _isSet && _precision == 0 && _value >= 0 && _value <= UINT32_MAX; }
|
||||
|
||||
inline bool isInt8() const
|
||||
{ return _isSet && _precision == 0 && _value >= INT8_MIN && _value <= INT8_MAX; }
|
||||
|
||||
inline bool isInt16() const
|
||||
{ return _isSet && _precision == 0 && _value >= INT16_MIN && _value <= INT16_MAX; }
|
||||
|
||||
inline bool isInt32() const
|
||||
{ return _isSet && _precision == 0 && _value >= INT32_MIN && _value <= INT32_MAX; }
|
||||
|
||||
inline bool isFloat() const
|
||||
{ return _isSet && _precision > 0; }
|
||||
|
||||
inline uint8_t toUInt8() const
|
||||
{ return static_cast<uint8_t>(_value); }
|
||||
|
||||
inline uint16_t toUInt16() const
|
||||
{ return static_cast<uint16_t>(_value); }
|
||||
|
||||
inline uint32_t toUInt32() const
|
||||
{ return static_cast<uint32_t>(_value); }
|
||||
|
||||
inline int8_t toInt8() const
|
||||
{ return static_cast<int8_t>(_value); }
|
||||
|
||||
inline int16_t toInt16() const
|
||||
{ return static_cast<int16_t>(_value); }
|
||||
|
||||
inline int32_t toInt32() const
|
||||
{ return static_cast<int32_t>(_value); }
|
||||
|
||||
inline float toFloat() const
|
||||
{ return _value / (float)getPrecisionBase(); }
|
||||
|
||||
private:
|
||||
bool _isSet;
|
||||
int64_t _value;
|
||||
uint8_t _precision;
|
||||
|
||||
explicit HANumeric(const int64_t value);
|
||||
};
|
||||
|
||||
#endif
|
||||
526
lib/home-assistant-integration/src/utils/HASerializer.cpp
Normal file
526
lib/home-assistant-integration/src/utils/HASerializer.cpp
Normal file
@@ -0,0 +1,526 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
#include <avr/dtostrf.h>
|
||||
#endif
|
||||
|
||||
#include "HASerializer.h"
|
||||
#include "../ArduinoHADefines.h"
|
||||
#include "../HADevice.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
#include "../device-types/HABaseDeviceType.h"
|
||||
|
||||
uint16_t HASerializer::calculateConfigTopicLength(
|
||||
const __FlashStringHelper* componentName,
|
||||
const char* objectId
|
||||
)
|
||||
{
|
||||
const HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (
|
||||
!componentName ||
|
||||
!objectId ||
|
||||
!mqtt ||
|
||||
!mqtt->getDiscoveryPrefix() ||
|
||||
!mqtt->getDevice()
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return
|
||||
strlen(mqtt->getDiscoveryPrefix()) + 1 + // prefix with slash
|
||||
strlen_P(AHAFROMFSTR(componentName)) + 1 + // component name with slash
|
||||
strlen(mqtt->getDevice()->getUniqueId()) + 1 + // device ID with slash
|
||||
strlen(objectId) + 1 + // object ID with slash
|
||||
strlen_P(HAConfigTopic) + 1; // including null terminator
|
||||
}
|
||||
|
||||
bool HASerializer::generateConfigTopic(
|
||||
char* output,
|
||||
const __FlashStringHelper* componentName,
|
||||
const char* objectId
|
||||
)
|
||||
{
|
||||
const HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (
|
||||
!output ||
|
||||
!componentName ||
|
||||
!objectId ||
|
||||
!mqtt ||
|
||||
!mqtt->getDiscoveryPrefix() ||
|
||||
!mqtt->getDevice()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
strcpy(output, mqtt->getDiscoveryPrefix());
|
||||
strcat_P(output, HASerializerSlash);
|
||||
|
||||
strcat_P(output, AHAFROMFSTR(componentName));
|
||||
strcat_P(output, HASerializerSlash);
|
||||
|
||||
strcat(output, mqtt->getDevice()->getUniqueId());
|
||||
strcat_P(output, HASerializerSlash);
|
||||
|
||||
strcat(output, objectId);
|
||||
strcat_P(output, HASerializerSlash);
|
||||
|
||||
strcat_P(output, HAConfigTopic);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t HASerializer::calculateDataTopicLength(
|
||||
const char* objectId,
|
||||
const __FlashStringHelper* topic
|
||||
)
|
||||
{
|
||||
const HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (
|
||||
!topic ||
|
||||
!mqtt ||
|
||||
!mqtt->getDataPrefix() ||
|
||||
!mqtt->getDevice()
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t size =
|
||||
strlen(mqtt->getDataPrefix()) + 1 + // prefix with slash
|
||||
strlen(mqtt->getDevice()->getUniqueId()) + 1 + // device ID with slash
|
||||
strlen_P(AHAFROMFSTR(topic));
|
||||
|
||||
if (objectId) {
|
||||
size += strlen(objectId) + 1; // object ID with slash;
|
||||
}
|
||||
|
||||
return size + 1; // including null terminator
|
||||
}
|
||||
|
||||
bool HASerializer::generateDataTopic(
|
||||
char* output,
|
||||
const char* objectId,
|
||||
const __FlashStringHelper* topic
|
||||
)
|
||||
{
|
||||
const HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (
|
||||
!output ||
|
||||
!topic ||
|
||||
!mqtt ||
|
||||
!mqtt->getDataPrefix() ||
|
||||
!mqtt->getDevice()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
strcpy(output, mqtt->getDataPrefix());
|
||||
strcat_P(output, HASerializerSlash);
|
||||
|
||||
strcat(output, mqtt->getDevice()->getUniqueId());
|
||||
strcat_P(output, HASerializerSlash);
|
||||
|
||||
if (objectId) {
|
||||
strcat(output, objectId);
|
||||
strcat_P(output, HASerializerSlash);
|
||||
}
|
||||
|
||||
strcat_P(output, AHAFROMFSTR(topic));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HASerializer::compareDataTopics(
|
||||
const char* actualTopic,
|
||||
const char* objectId,
|
||||
const __FlashStringHelper* topic
|
||||
)
|
||||
{
|
||||
if (!actualTopic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t topicLength = calculateDataTopicLength(objectId, topic);
|
||||
if (topicLength == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char expectedTopic[topicLength];
|
||||
if (!generateDataTopic(expectedTopic, objectId, topic)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return memcmp(actualTopic, expectedTopic, topicLength) == 0;
|
||||
}
|
||||
|
||||
HASerializer::HASerializer(
|
||||
HABaseDeviceType* deviceType,
|
||||
const uint8_t maxEntriesNb
|
||||
) :
|
||||
_deviceType(deviceType),
|
||||
_entriesNb(0),
|
||||
_maxEntriesNb(maxEntriesNb),
|
||||
_entries(new SerializerEntry[maxEntriesNb])
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HASerializer::~HASerializer()
|
||||
{
|
||||
delete[] _entries;
|
||||
}
|
||||
|
||||
void HASerializer::set(
|
||||
const __FlashStringHelper* property,
|
||||
const void* value,
|
||||
PropertyValueType valueType
|
||||
)
|
||||
{
|
||||
if (!property || !value) {
|
||||
return;
|
||||
}
|
||||
|
||||
SerializerEntry* entry = addEntry();
|
||||
entry->type = PropertyEntryType;
|
||||
entry->subtype = static_cast<uint8_t>(valueType);
|
||||
entry->property = property;
|
||||
entry->value = value;
|
||||
}
|
||||
|
||||
void HASerializer::set(const FlagType flag)
|
||||
{
|
||||
if (flag == WithDevice) {
|
||||
SerializerEntry* entry = addEntry();
|
||||
entry->type = FlagEntryType;
|
||||
entry->subtype = static_cast<uint8_t>(WithDevice);
|
||||
entry->property = nullptr;
|
||||
entry->value = nullptr;
|
||||
} else if (flag == WithAvailability) {
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
const bool isSharedAvailability = mqtt->getDevice()->isSharedAvailabilityEnabled();
|
||||
const bool isAvailabilityConfigured = _deviceType->isAvailabilityConfigured();
|
||||
|
||||
if (!isSharedAvailability && !isAvailabilityConfigured) {
|
||||
return; // not configured
|
||||
}
|
||||
|
||||
SerializerEntry* entry = addEntry();
|
||||
entry->type = TopicEntryType;
|
||||
entry->property = AHATOFSTR(HAAvailabilityTopic);
|
||||
entry->value = isSharedAvailability
|
||||
? mqtt->getDevice()->getAvailabilityTopic()
|
||||
: nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HASerializer::topic(const __FlashStringHelper* topic)
|
||||
{
|
||||
if (!_deviceType || !topic) {
|
||||
return;
|
||||
}
|
||||
|
||||
SerializerEntry* entry = addEntry();
|
||||
entry->type = TopicEntryType;
|
||||
entry->property = topic;
|
||||
}
|
||||
|
||||
HASerializer::SerializerEntry* HASerializer::addEntry()
|
||||
{
|
||||
return &_entries[_entriesNb++]; // intentional lack of protection against overflow
|
||||
}
|
||||
|
||||
uint16_t HASerializer::calculateSize() const
|
||||
{
|
||||
uint16_t size =
|
||||
strlen_P(HASerializerJsonDataPrefix) +
|
||||
strlen_P(HASerializerJsonDataSuffix);
|
||||
|
||||
for (uint8_t i = 0; i < _entriesNb; i++) {
|
||||
const uint16_t entrySize = calculateEntrySize(&_entries[i]);
|
||||
if (entrySize == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size += entrySize;
|
||||
|
||||
// items separator
|
||||
if (i > 0) {
|
||||
size += strlen_P(HASerializerJsonPropertiesSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
bool HASerializer::flush() const
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (!mqtt || (_deviceType && !mqtt->getDevice())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonDataPrefix));
|
||||
|
||||
for (uint8_t i = 0; i < _entriesNb; i++) {
|
||||
if (i > 0) {
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonPropertiesSeparator));
|
||||
}
|
||||
|
||||
if (!flushEntry(&_entries[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonDataSuffix));
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t HASerializer::calculateEntrySize(const SerializerEntry* entry) const
|
||||
{
|
||||
switch (entry->type) {
|
||||
case PropertyEntryType:
|
||||
return
|
||||
// property name
|
||||
strlen_P(HASerializerJsonPropertyPrefix) +
|
||||
strlen_P(AHAFROMFSTR(entry->property)) +
|
||||
strlen_P(HASerializerJsonPropertySuffix) +
|
||||
// property value
|
||||
calculatePropertyValueSize(entry);
|
||||
|
||||
case TopicEntryType:
|
||||
return calculateTopicEntrySize(entry);
|
||||
|
||||
case FlagEntryType:
|
||||
return calculateFlagSize(
|
||||
static_cast<FlagType>(entry->subtype)
|
||||
);
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t HASerializer::calculateTopicEntrySize(
|
||||
const SerializerEntry* entry
|
||||
) const
|
||||
{
|
||||
uint16_t size = 0;
|
||||
|
||||
// property name
|
||||
size +=
|
||||
strlen_P(HASerializerJsonPropertyPrefix) +
|
||||
strlen_P(AHAFROMFSTR(entry->property)) +
|
||||
strlen_P(HASerializerJsonPropertySuffix);
|
||||
|
||||
// topic escape
|
||||
size += 2 * strlen_P(HASerializerJsonEscapeChar);
|
||||
|
||||
// topic
|
||||
if (entry->value) {
|
||||
size += strlen(static_cast<const char*>(entry->value));
|
||||
} else {
|
||||
if (!_deviceType) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size += calculateDataTopicLength(
|
||||
_deviceType->uniqueId(),
|
||||
entry->property
|
||||
) - 1; // exclude null terminator
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint16_t HASerializer::calculateFlagSize(const FlagType flag) const
|
||||
{
|
||||
const HAMqtt* mqtt = HAMqtt::instance();
|
||||
const HADevice* device = mqtt->getDevice();
|
||||
|
||||
if (flag == WithDevice && device->getSerializer()) {
|
||||
const uint16_t deviceLength = device->getSerializer()->calculateSize();
|
||||
if (deviceLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return
|
||||
strlen_P(HASerializerJsonPropertyPrefix) +
|
||||
strlen_P(HADeviceProperty) +
|
||||
strlen_P(HASerializerJsonPropertySuffix) +
|
||||
deviceLength;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t HASerializer::calculatePropertyValueSize(
|
||||
const SerializerEntry* entry
|
||||
) const
|
||||
{
|
||||
switch (entry->subtype) {
|
||||
case ConstCharPropertyValue:
|
||||
case ProgmemPropertyValue: {
|
||||
const char* value = static_cast<const char*>(entry->value);
|
||||
const uint16_t len =
|
||||
entry->subtype == ConstCharPropertyValue ? strlen(value) : strlen_P(value);
|
||||
return 2 * strlen_P(HASerializerJsonEscapeChar) + len;
|
||||
}
|
||||
|
||||
case BoolPropertyType: {
|
||||
const bool value = *static_cast<const bool*>(entry->value);
|
||||
return value ? strlen_P(HATrue) : strlen_P(HAFalse);
|
||||
}
|
||||
|
||||
case NumberPropertyType: {
|
||||
const HANumeric* value = static_cast<const HANumeric*>(
|
||||
entry->value
|
||||
);
|
||||
return value->calculateSize();
|
||||
}
|
||||
|
||||
case ArrayPropertyType: {
|
||||
const HASerializerArray* array = static_cast<const HASerializerArray*>(
|
||||
entry->value
|
||||
);
|
||||
return array->calculateSize();
|
||||
}
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool HASerializer::flushEntry(const SerializerEntry* entry) const
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
|
||||
switch (entry->type) {
|
||||
case PropertyEntryType: {
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonPropertyPrefix));
|
||||
mqtt->writePayload(entry->property);
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonPropertySuffix));
|
||||
|
||||
return flushEntryValue(entry);
|
||||
}
|
||||
|
||||
case TopicEntryType:
|
||||
return flushTopic(entry);
|
||||
|
||||
case FlagEntryType:
|
||||
return flushFlag(entry);
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool HASerializer::flushEntryValue(const SerializerEntry* entry) const
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
|
||||
switch (entry->subtype) {
|
||||
case ConstCharPropertyValue:
|
||||
case ProgmemPropertyValue: {
|
||||
const char* value = static_cast<const char*>(entry->value);
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonEscapeChar));
|
||||
|
||||
if (entry->subtype == ConstCharPropertyValue) {
|
||||
mqtt->writePayload(value, strlen(value));
|
||||
} else {
|
||||
mqtt->writePayload(AHATOFSTR(value));
|
||||
}
|
||||
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonEscapeChar));
|
||||
return true;
|
||||
}
|
||||
|
||||
case BoolPropertyType: {
|
||||
const bool value = *static_cast<const bool*>(entry->value);
|
||||
mqtt->writePayload(AHATOFSTR(value ? HATrue : HAFalse));
|
||||
return true;
|
||||
}
|
||||
|
||||
case NumberPropertyType: {
|
||||
const HANumeric* value = static_cast<const HANumeric*>(
|
||||
entry->value
|
||||
);
|
||||
|
||||
char tmp[HANumeric::MaxDigitsNb + 1];
|
||||
const uint16_t length = value->toStr(tmp);
|
||||
|
||||
mqtt->writePayload(tmp, length);
|
||||
return true;
|
||||
}
|
||||
|
||||
case ArrayPropertyType: {
|
||||
const HASerializerArray* array = static_cast<const HASerializerArray*>(
|
||||
entry->value
|
||||
);
|
||||
const uint16_t size = array->calculateSize();
|
||||
char tmp[size + 1]; // including null terminator
|
||||
tmp[0] = 0;
|
||||
array->serialize(tmp);
|
||||
mqtt->writePayload(tmp, size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HASerializer::flushTopic(const SerializerEntry* entry) const
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
|
||||
// property name
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonPropertyPrefix));
|
||||
mqtt->writePayload(entry->property);
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonPropertySuffix));
|
||||
|
||||
// value (escaped)
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonEscapeChar));
|
||||
|
||||
if (entry->value) {
|
||||
const char* topic = static_cast<const char*>(entry->value);
|
||||
mqtt->writePayload(topic, strlen(topic));
|
||||
} else {
|
||||
const uint16_t length = calculateDataTopicLength(
|
||||
_deviceType->uniqueId(),
|
||||
entry->property
|
||||
);
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char topic[length];
|
||||
generateDataTopic(
|
||||
topic,
|
||||
_deviceType->uniqueId(),
|
||||
entry->property
|
||||
);
|
||||
|
||||
mqtt->writePayload(topic, length - 1);
|
||||
}
|
||||
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonEscapeChar));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HASerializer::flushFlag(const SerializerEntry* entry) const
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
const HADevice* device = mqtt->getDevice();
|
||||
const FlagType flag = static_cast<FlagType>(entry->subtype);
|
||||
|
||||
if (flag == WithDevice && device) {
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonPropertyPrefix));
|
||||
mqtt->writePayload(AHATOFSTR(HADeviceProperty));
|
||||
mqtt->writePayload(AHATOFSTR(HASerializerJsonPropertySuffix));
|
||||
|
||||
return device->getSerializer()->flush();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
269
lib/home-assistant-integration/src/utils/HASerializer.h
Normal file
269
lib/home-assistant-integration/src/utils/HASerializer.h
Normal file
@@ -0,0 +1,269 @@
|
||||
|
||||
#ifndef AHA_SERIALIZER_H
|
||||
#define AHA_SERIALIZER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "HADictionary.h"
|
||||
#include "HASerializerArray.h"
|
||||
|
||||
class HAMqtt;
|
||||
class HABaseDeviceType;
|
||||
|
||||
/**
|
||||
* This class allows to create JSON objects easily.
|
||||
* Its main purpose is to handle configuration of a device type that's going to
|
||||
* be published to the MQTT broker.
|
||||
*/
|
||||
class HASerializer
|
||||
{
|
||||
public:
|
||||
/// Type of the object's entry.
|
||||
enum EntryType {
|
||||
UnknownEntryType = 0,
|
||||
PropertyEntryType,
|
||||
TopicEntryType,
|
||||
FlagEntryType
|
||||
};
|
||||
|
||||
/// The type of a flag for a FlagEntryType.
|
||||
enum FlagType {
|
||||
WithDevice = 1,
|
||||
WithAvailability
|
||||
};
|
||||
|
||||
/// Available data types of entries.
|
||||
enum PropertyValueType {
|
||||
UnknownPropertyValueType = 0,
|
||||
ConstCharPropertyValue,
|
||||
ProgmemPropertyValue,
|
||||
BoolPropertyType,
|
||||
NumberPropertyType,
|
||||
ArrayPropertyType
|
||||
};
|
||||
|
||||
/// Representation of a single entry in the object.
|
||||
struct SerializerEntry {
|
||||
/// Type of the entry.
|
||||
EntryType type;
|
||||
|
||||
/// Subtype of the entry. It can be `FlagType`, `PropertyValueType` or `TopicType`.
|
||||
uint8_t subtype;
|
||||
|
||||
/// Pointer to the property name (progmem string).
|
||||
const __FlashStringHelper* property;
|
||||
|
||||
/// Pointer to the property value. The value type is determined by `subtype`.
|
||||
const void* value;
|
||||
|
||||
SerializerEntry():
|
||||
type(UnknownEntryType),
|
||||
subtype(0),
|
||||
property(nullptr),
|
||||
value(nullptr)
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the size of a configuration topic for the given component and object ID.
|
||||
* The configuration topic has structure as follows: `[discovery prefix]/[component]/[device ID]_[objectId]/config`
|
||||
*
|
||||
* @param component The name of the HA component (e.g. `binary_sensor`).
|
||||
* @param objectId The unique ID of a device type that's going to publish the config.
|
||||
*/
|
||||
static uint16_t calculateConfigTopicLength(
|
||||
const __FlashStringHelper* component,
|
||||
const char* objectId
|
||||
);
|
||||
|
||||
/**
|
||||
* Generates the configuration topic for the given component and object ID.
|
||||
* The topic will be stored in the `output` variable.
|
||||
*
|
||||
* @param output Buffer where the topic will be written.
|
||||
* @param component The name of the HA component (e.g. `binary_sensor`).
|
||||
* @param objectId The unique ID of a device type that's going to publish the config.
|
||||
*/
|
||||
static bool generateConfigTopic(
|
||||
char* output,
|
||||
const __FlashStringHelper* component,
|
||||
const char* objectId
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculates the size of the given data topic for the given objectId.
|
||||
* The data topic has structure as follows: `[data prefix]/[device ID]_[objectId]/[topic]`
|
||||
*
|
||||
* @param objectId The unique ID of a device type that's going to publish the data.
|
||||
* @param topic The topic name (progmem string).
|
||||
*/
|
||||
static uint16_t calculateDataTopicLength(
|
||||
const char* objectId,
|
||||
const __FlashStringHelper* topic
|
||||
);
|
||||
|
||||
/**
|
||||
* Generates the data topic for the given object ID.
|
||||
* The topic will be stored in the `output` variable.
|
||||
*
|
||||
* @param output Buffer where the topic will be written.
|
||||
* @param objectId The unique ID of a device type that's going to publish the data.
|
||||
* @param topic The topic name (progmem string).
|
||||
*/
|
||||
static bool generateDataTopic(
|
||||
char* output,
|
||||
const char* objectId,
|
||||
const __FlashStringHelper* topic
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks whether the given topic matches the data topic that can be generated
|
||||
* using the given objectId and topicP.
|
||||
* This method can be used to check if the received message matches some data topic.
|
||||
*
|
||||
* @param actualTopic The actual topic to compare.
|
||||
* @param objectId The unique ID of a device type that may be the owner of the topic.
|
||||
* @param topic The topic name (progmem string).
|
||||
*/
|
||||
static bool compareDataTopics(
|
||||
const char* actualTopic,
|
||||
const char* objectId,
|
||||
const __FlashStringHelper* topic
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates instance of the serializer for the given device type.
|
||||
* Please note that the number JSON object's entries needs to be known upfront.
|
||||
* This approach reduces number of memory allocations.
|
||||
*
|
||||
* @param deviceType The device type that owns the serializer.
|
||||
* @param maxEntriesNb Maximum number of the output object entries.
|
||||
*/
|
||||
HASerializer(HABaseDeviceType* deviceType, const uint8_t maxEntriesNb);
|
||||
|
||||
/**
|
||||
* Frees the dynamic memory allocated by the class.
|
||||
*/
|
||||
~HASerializer();
|
||||
|
||||
/**
|
||||
* Returns the number of items that were added to the serializer.
|
||||
*/
|
||||
inline uint8_t getEntriesNb() const
|
||||
{ return _entriesNb; }
|
||||
|
||||
/**
|
||||
* Returns pointer to the serializer's entries.
|
||||
*/
|
||||
inline SerializerEntry* getEntries() const
|
||||
{ return _entries; }
|
||||
|
||||
/**
|
||||
* Adds a new entry to the serialized with a type of `PropertyEntryType`.
|
||||
*
|
||||
* @param property Pointer to the name of the property (progmem string).
|
||||
* @param value Pointer to the value that's being set.
|
||||
* @param valueType The type of the value that's passed to the method.
|
||||
*/
|
||||
void set(
|
||||
const __FlashStringHelper* property,
|
||||
const void* value,
|
||||
PropertyValueType valueType = ConstCharPropertyValue
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds a new entry to the serializer with a type of `FlagEntryType`.
|
||||
*
|
||||
* @param flag Flag to add.
|
||||
*/
|
||||
void set(const FlagType flag);
|
||||
|
||||
/**
|
||||
* Adds a new entry to the serialize with a type of `TopicEntryType`.
|
||||
*
|
||||
* @param topic The topic name to add (progmem string).
|
||||
*/
|
||||
void topic(const __FlashStringHelper* topic);
|
||||
|
||||
/**
|
||||
* Calculates the output size of the serialized JSON object.
|
||||
*/
|
||||
uint16_t calculateSize() const;
|
||||
|
||||
/**
|
||||
* Flushes the JSON object to the MQTT stream.
|
||||
* Please note that this method only writes the MQTT payload.
|
||||
* The MQTT session needs to be opened before.
|
||||
*/
|
||||
bool flush() const;
|
||||
|
||||
private:
|
||||
/// Pointer to the device type that owns the serializer.
|
||||
HABaseDeviceType* _deviceType;
|
||||
|
||||
/// The number of entries added to the serializer.
|
||||
uint8_t _entriesNb;
|
||||
|
||||
/// Maximum number of entries that can be added to the serializer.
|
||||
uint8_t _maxEntriesNb;
|
||||
|
||||
/// Pointer to the serializer entries.
|
||||
SerializerEntry* _entries;
|
||||
|
||||
/**
|
||||
* Creates a new entry in the serializer's memory.
|
||||
* If the limit of entries is hit, the nullptr is returned.
|
||||
*/
|
||||
SerializerEntry* addEntry();
|
||||
|
||||
/**
|
||||
* Calculates the serialized size of the given entry.
|
||||
* Internally, this method recognizes the type of the entry and calls
|
||||
* a proper calculate method listed below.
|
||||
*/
|
||||
uint16_t calculateEntrySize(const SerializerEntry* entry) const;
|
||||
|
||||
/**
|
||||
* Calculates the size of the entry of type `TopicEntryType`.
|
||||
*/
|
||||
uint16_t calculateTopicEntrySize(const SerializerEntry* entry) const;
|
||||
|
||||
/**
|
||||
* Calculates the size of the entry of type `FlagEntryType`.
|
||||
*/
|
||||
uint16_t calculateFlagSize(const FlagType flag) const;
|
||||
|
||||
/**
|
||||
* Calculates the size of the entry's value if the entry is `PropertyEntryType`.
|
||||
*/
|
||||
uint16_t calculatePropertyValueSize(const SerializerEntry* entry) const;
|
||||
|
||||
/**
|
||||
* Calculates the size of the array if the property's value is a type of `ArrayPropertyType`.
|
||||
*/
|
||||
uint16_t calculateArraySize(const HASerializerArray* array) const;
|
||||
|
||||
/**
|
||||
* Flushes the given entry to the MQTT.
|
||||
* Internally this method recognizes the type of the entry and calls
|
||||
* a proper flush method listed below.
|
||||
*/
|
||||
bool flushEntry(const SerializerEntry* entry) const;
|
||||
|
||||
/**
|
||||
* Flushes the value of the `PropertyEntryType` entry.
|
||||
*/
|
||||
bool flushEntryValue(const SerializerEntry* entry) const;
|
||||
|
||||
/**
|
||||
* Flushes the entry of type `TopicEntryType` to the MQTT.
|
||||
*/
|
||||
bool flushTopic(const SerializerEntry* entry) const;
|
||||
|
||||
/**
|
||||
* Flushes the entry of type `FlagEntryType` to the MQTT.
|
||||
*/
|
||||
bool flushFlag(const SerializerEntry* entry) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "HASerializerArray.h"
|
||||
#include "HADictionary.h"
|
||||
|
||||
HASerializerArray::HASerializerArray(const uint8_t size, const bool progmemItems) :
|
||||
_progmemItems(progmemItems),
|
||||
_size(size),
|
||||
_itemsNb(0),
|
||||
_items(new ItemType[size])
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HASerializerArray::~HASerializerArray()
|
||||
{
|
||||
delete[] _items;
|
||||
}
|
||||
|
||||
bool HASerializerArray::add(ItemType item)
|
||||
{
|
||||
if (_itemsNb >= _size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_items[_itemsNb++] = item;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t HASerializerArray::calculateSize() const
|
||||
{
|
||||
uint16_t size =
|
||||
strlen_P(HASerializerJsonArrayPrefix) +
|
||||
strlen_P(HASerializerJsonArraySuffix);
|
||||
|
||||
if (_itemsNb == 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// separators between elements
|
||||
size += (_itemsNb - 1) * strlen_P(HASerializerJsonPropertiesSeparator);
|
||||
|
||||
for (uint8_t i = 0; i < _itemsNb; i++) {
|
||||
size +=
|
||||
2 * strlen_P(HASerializerJsonEscapeChar)
|
||||
+ (_progmemItems ? strlen_P(_items[i]) : strlen(_items[i]));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
bool HASerializerArray::serialize(char* output) const
|
||||
{
|
||||
if (!output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
strcat_P(output, HASerializerJsonArrayPrefix);
|
||||
|
||||
for (uint8_t i = 0; i < _itemsNb; i++) {
|
||||
if (i > 0) {
|
||||
strcat_P(output, HASerializerJsonPropertiesSeparator);
|
||||
}
|
||||
|
||||
strcat_P(output, HASerializerJsonEscapeChar);
|
||||
|
||||
if (_progmemItems) {
|
||||
strcat_P(output, _items[i]);
|
||||
} else {
|
||||
strcat(output, _items[i]);
|
||||
}
|
||||
|
||||
strcat_P(output, HASerializerJsonEscapeChar);
|
||||
}
|
||||
|
||||
strcat_P(output, HASerializerJsonArraySuffix);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HASerializerArray::clear()
|
||||
{
|
||||
_itemsNb = 0;
|
||||
}
|
||||
75
lib/home-assistant-integration/src/utils/HASerializerArray.h
Normal file
75
lib/home-assistant-integration/src/utils/HASerializerArray.h
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
#ifndef AHA_SERIALIZERARRAY_H
|
||||
#define AHA_SERIALIZERARRAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* HASerializerArray represents array of items that can be used as a HASerializer property.
|
||||
*/
|
||||
class HASerializerArray
|
||||
{
|
||||
public:
|
||||
typedef const char* ItemType;
|
||||
|
||||
/**
|
||||
* Constructs HASerializerArray with the static size (number of elements).
|
||||
* The array is allocated dynamically in the memory based on the given size.
|
||||
*
|
||||
* @param size The desired number of elements that will be stored in the array.
|
||||
* @param progmemItems Specifies whether items are going to be stored in the flash memory.
|
||||
*/
|
||||
HASerializerArray(const uint8_t size, const bool progmemItems = true);
|
||||
~HASerializerArray();
|
||||
|
||||
/**
|
||||
* Returns the number of elements that were added to the array.
|
||||
* It can be lower than size of the array.
|
||||
*/
|
||||
inline uint8_t getItemsNb() const
|
||||
{ return _itemsNb; }
|
||||
|
||||
/**
|
||||
* Returns pointer to the array.
|
||||
*/
|
||||
inline ItemType* getItems() const
|
||||
{ return _items; }
|
||||
|
||||
/**
|
||||
* Adds a new element to the array.
|
||||
*
|
||||
* @param itemP Item to add (string).
|
||||
* @returns Returns `true` if item has been added to the array successfully.
|
||||
*/
|
||||
bool add(ItemType item);
|
||||
|
||||
/**
|
||||
* Calculates the size of the serialized array (JSON representation).
|
||||
*/
|
||||
uint16_t calculateSize() const;
|
||||
|
||||
/**
|
||||
* Serializes array as JSON to the given output.
|
||||
*/
|
||||
bool serialize(char* output) const;
|
||||
|
||||
/**
|
||||
* Clears the array.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
/// Specifies whether items are stored in the flash memory.
|
||||
const bool _progmemItems;
|
||||
|
||||
/// The maximum size of the array.
|
||||
const uint8_t _size;
|
||||
|
||||
/// The number of items that were added to the array.
|
||||
uint8_t _itemsNb;
|
||||
|
||||
/// Pointer to the array elements.
|
||||
ItemType* _items;
|
||||
};
|
||||
|
||||
#endif
|
||||
48
lib/home-assistant-integration/src/utils/HAUtils.cpp
Normal file
48
lib/home-assistant-integration/src/utils/HAUtils.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
#include <avr/dtostrf.h>
|
||||
#endif
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "HAUtils.h"
|
||||
#include "HADictionary.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 || lenstr == 0 || lensuffix == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0);
|
||||
}
|
||||
|
||||
void HAUtils::byteArrayToStr(
|
||||
char* dst,
|
||||
const byte* src,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
dst[i*2] = pgm_read_byte(&HAHexMap[((char)src[i] & 0XF0) >> 4]);
|
||||
dst[i*2+1] = pgm_read_byte(&HAHexMap[((char)src[i] & 0x0F)]);
|
||||
}
|
||||
|
||||
dst[length * 2] = 0;
|
||||
}
|
||||
|
||||
char* HAUtils::byteArrayToStr(
|
||||
const byte* src,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
char* dst = new char[(length * 2) + 1]; // include null terminator
|
||||
byteArrayToStr(dst, src, length);
|
||||
|
||||
return dst;
|
||||
}
|
||||
52
lib/home-assistant-integration/src/utils/HAUtils.h
Normal file
52
lib/home-assistant-integration/src/utils/HAUtils.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef AHA_HAUTILS_H
|
||||
#define AHA_HAUTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* This class provides some useful methods to make life easier.
|
||||
*/
|
||||
class HAUtils
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Checks whether the given `str` ends with the given `suffix`.
|
||||
*
|
||||
* @param str Input string to check.
|
||||
* @param suffix Suffix to find
|
||||
* @returns True if the given suffix is present at the end of the given string.
|
||||
*/
|
||||
static bool endsWith(
|
||||
const char* str,
|
||||
const char* suffix
|
||||
);
|
||||
|
||||
/**
|
||||
* Converts the given byte array into hex string.
|
||||
* Each byte will be represented by two bytes, so the output size will be `length * 2`
|
||||
*
|
||||
* @param dst Destination where the string will be saved.
|
||||
* @param src Bytes array to convert.
|
||||
* @param length Length of the bytes array.
|
||||
*/
|
||||
static void byteArrayToStr(
|
||||
char* dst,
|
||||
const byte* src,
|
||||
const uint16_t length
|
||||
);
|
||||
|
||||
/**
|
||||
* Converts the given byte array into hex string.
|
||||
* This method allocates a new memory.
|
||||
*
|
||||
* @param src Bytes array to convert.
|
||||
* @param length Length of the bytes array.
|
||||
* @returns Newly allocated string containing the hex representation.
|
||||
*/
|
||||
static char* byteArrayToStr(
|
||||
const byte* src,
|
||||
const uint16_t length
|
||||
);
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user