Release 1.2.0 (#21)

This commit is contained in:
Dawid Chyrzyński
2021-03-23 13:23:56 +01:00
committed by GitHub
parent 70ccd26c75
commit 15562fa74c
42 changed files with 3371 additions and 971 deletions
+23
View File
@@ -0,0 +1,23 @@
# Changelog
## 1.2.0
**Breaking changes:**
* Refactored HASensor implementation. Please take a look at updated example in `examples/sensor/sensor.ino`
**New features:**
* Added support for HVAC
* Added support for excluding devices types from the compilation using defines (see `src/ArduinoHADefines.h`)
* Added support for setting icon in HASwitch and HASensor
* Added support for setting retain flag in HASwitch
* Added support for text (const char*) payload in HASensor
* Added support for fans (HAFan)
* Added support for connecting to the MQTT broker using hostname
* Added `onConnected()` method in the HAMqtt
* Added `onConnectionFailed()` method in the HAMqtt
* Added support for MQTT LWT (see `examples/advanced-availability/advanced-availability.ino`)
**Updates:**
* Optimized codebase and logic in all devices types
* Updated all examples
* Fixed compilation warnings in all classes
+17 -1
View File
@@ -8,18 +8,23 @@ but I successfully use it on ESP8266/ESP8255 boards in my projects.
## Features
* MQTT discovery (device is added to the Home Assistant panel automatically)
* MQTT Last Will and Testament
* Auto reconnect with MQTT broker
## Examples
* [Binary Sensor](examples/binary-sensor/binary-sensor.ino)
* [Fan](examples/fan/fan.ino)
* [LED switch](examples/led-switch/led-switch.ino)
* [MQTT with credentials](examples/mqtt-with-credentials/mqtt-with-credentials.ino)
* [Multi-state button](examples/multi-state-button/multi-state-button.ino)
* [Sensor (temperature, humidity, etc.)](examples/sensor/sensor.ino)
* [HVAC](examples/hvac/hvac.ino)
* [NodeMCU Wi-Fi](examples/nodemcu/nodemcu.ino)
* [Arduino Nano 33 IoT Wi-Fi (SAMD)](examples/nano33iot/nano33iot.ino)
* [Availability feature](examples/availability)
* [Advanced availability (MQTT LWT)](examples/advanced-availability/advanced-availability.ino)
* [MQTT with credentials](examples/mqtt-with-credentials/mqtt-with-credentials.ino)
* [MQTT events](examples/mqtt-events/mqtt-events.ino)
## Tested boards
@@ -39,10 +44,21 @@ but I successfully use it on ESP8266/ESP8255 boards in my projects.
## Supported HA types
* Binary sensors
* Fans
* Device triggers
* Switches
* Sensors
* Tag scanner
* HVACs *(side note: HVACs requires more flash size than other HA types. It's not suitable for Arduino Nano/Uno)*
## Roadmap
* FAQ + Home Assistant setup instructions
* Documentation of the library
* Unit tests
* Reduce flash memory usage
* Add support for HA covers
* Add support for HA lights
## Unsupported features
@@ -0,0 +1,64 @@
#include <Ethernet.h>
#include <ArduinoHA.h>
#define INPUT_PIN 9
#define BROKER_ADDR IPAddress(192,168,0,17)
byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
unsigned long lastReadAt = millis();
unsigned long lastAvailabilityToggleAt = millis();
bool lastInputState = false;
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
// "input" may be anything you want to be displayed in HA panel
// "door" is device class (based on the class HA displays different icons in the panel)
// "true" is initial state of the sensor. In this example it's "true" as we use pullup resistor
HABinarySensor sensor("input", "door", true);
void setup() {
pinMode(INPUT_PIN, INPUT_PULLUP);
lastInputState = digitalRead(INPUT_PIN);
// you don't need to verify return status
Ethernet.begin(mac);
lastReadAt = millis();
lastAvailabilityToggleAt = millis();
// set device's details (optional)
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
// This method enables availability for all device types registered on the device.
// For example, if you have 5 sensors on the same device, you can enable
// shared availability and change availability state of all sensors using
// single method call "device.setAvailability(false|true)"
device.enableSharedAvailability();
// Optionally, you can enable MQTT LWT feature. If device will lose connection
// to the broker, all device types related to it will be marked as offline in
// the Home Assistant Panel.
device.enableLastWill();
mqtt.begin(BROKER_ADDR);
}
void loop() {
Ethernet.maintain();
mqtt.loop();
if ((millis() - lastReadAt) > 30) { // read in 30ms interval
// library produces MQTT message if a new state is different than the previous one
sensor.setState(digitalRead(INPUT_PIN));
lastInputState = sensor.getState();
lastReadAt = millis();
}
if ((millis() - lastAvailabilityToggleAt) > 5000) {
device.setAvailability(!device.isOnline());
lastAvailabilityToggleAt = millis();
}
}
+2
View File
@@ -5,6 +5,8 @@ It's supported only by following device types:
* Binary sensor
* Sensor
* Switch
* HVACs
* Fans
## Initialization and usage
+2 -1
View File
@@ -16,7 +16,7 @@ HAMqtt mqtt(client, device);
// "input" may be anything you want to be displayed in HA panel
// "door" is device class (based on the class HA displays different icons in the panel)
// "true" is initial state of the sensor. In this example it's "true" as we use pullup resistor
HABinarySensor sensor("input", "door", true, mqtt);
HABinarySensor sensor("input", "door", true);
void setup() {
pinMode(INPUT_PIN, INPUT_PULLUP);
@@ -26,6 +26,7 @@ void setup() {
Ethernet.begin(mac);
// turn on "availability" feature
// this method also sets initial availability so you can use "true" or "false"
sensor.setAvailability(false);
lastReadAt = millis();
+1 -1
View File
@@ -15,7 +15,7 @@ HAMqtt mqtt(client, device);
// "input" may be anything you want to be displayed in HA panel
// "door" is device class (based on the class HA displays different icons in the panel)
// "true" is initial state of the sensor. In this example it's "true" as we use pullup resistor
HABinarySensor sensor("input", "door", true, mqtt);
HABinarySensor sensor("input", "door", true);
void setup() {
pinMode(INPUT_PIN, INPUT_PULLUP);
+58
View File
@@ -0,0 +1,58 @@
#include <Ethernet.h>
#include <ArduinoHA.h>
#define BROKER_ADDR IPAddress(192,168,0,17)
byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
// HAFan::SpeedsFeature enables support for setting different speeds of fan.
// You can skip this argument if you don't need speed management.
HAFan fan("ventilation", HAFan::SpeedsFeature);
void onStateChanged(bool state) {
Serial.print("State: ");
Serial.println(state);
}
void onSpeedChanged(HAFan::Speed speed) {
Serial.print("Speed: ");
if (speed == HAFan::OffSpeed) {
Serial.print("off");
} else if (speed == HAFan::LowSpeed) {
Serial.print("low");
} else if (speed == HAFan::MediumSpeed) {
Serial.print("medium");
} else if (speed == HAFan::HighSpeed) {
Serial.print("high");
}
}
void setup() {
// you don't need to verify return status
Ethernet.begin(mac);
// set device's details (optional)
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
// configure fan (optional)
// default speeds are: Off | Low | Medium | High
fan.setSpeeds(HAFan::OffSpeed | HAFan::LowSpeed | HAFan::HighSpeed);
fan.setName("Bathroom");
fan.setRetain(true);
// handle fan states
fan.onStateChanged(onStateChanged);
fan.onSpeedChanged(onSpeedChanged);
mqtt.begin(BROKER_ADDR);
}
void loop() {
Ethernet.maintain();
mqtt.loop();
}
+102
View File
@@ -0,0 +1,102 @@
#include <ESP8266WiFi.h>
#include <ArduinoHA.h>
#define BROKER_ADDR IPAddress(192,168,0,17)
#define WIFI_SSID "MyNetwork"
#define WIFI_PASSWORD "MyPassword"
WiFiClient client;
HADevice device;
HAMqtt mqtt(client, device);
// see src/device-types/HAHVAC.h header for more details
HAHVAC hvac("my_name", HAHVAC::AuxHeatingFeature | HAHVAC::AwayModeFeature | HAHVAC::HoldFeature);
unsigned long lastTempPublishAt = 0;
double lastTemp = 0;
void onAuxHeatingStateChanged(bool state) {
Serial.print("Aux heating: ");
Serial.println(state);
}
void onAwayStateChanged(bool state) {
Serial.print("Away state: ");
Serial.println(state);
}
void onHoldStateChanged(bool state) {
Serial.print("Hold state: ");
Serial.println(state);
}
void onTargetTemperatureChanged(double temp) {
Serial.print("Target temperature: ");
Serial.println(temp);
}
void onModeChanged(HAHVAC::Mode mode) {
Serial.print("Mode: ");
if (mode == HAHVAC::OffMode) {
Serial.println("off");
} else if (mode == HAHVAC::AutoMode) {
Serial.println("auto");
} else if (mode == HAHVAC::CoolMode) {
Serial.println("cool");
} else if (mode == HAHVAC::HeatMode) {
Serial.println("heat");
} else if (mode == HAHVAC::DryMode) {
Serial.println("dry");
} else if (mode == HAHVAC::FanOnlyMode) {
Serial.println("fan only");
}
}
void setup() {
Serial.begin(9600);
Serial.println("Starting...");
// Unique ID must be set!
byte mac[WL_MAC_ADDR_LENGTH];
WiFi.macAddress(mac);
device.setUniqueId(mac, sizeof(mac));
// connect to wifi
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500); // waiting for the connection
}
Serial.println();
Serial.println("Connected to the network");
// set device's details (optional)
device.setName("NodeMCU");
device.setSoftwareVersion("1.0.0");
// assign callbacks (optional)
hvac.onAuxHeatingStateChanged(onAuxHeatingStateChanged);
hvac.onAwayStateChanged(onAwayStateChanged);
hvac.onHoldStateChanged(onHoldStateChanged);
hvac.onTargetTemperatureChanged(onTargetTemperatureChanged);
hvac.onModeChanged(onModeChanged);
// configure HVAC (optional)
hvac.setName("My HVAC");
hvac.setMinTemp(10);
hvac.setMaxTemp(30);
hvac.setTempStep(0.5);
hvac.setRetain(true);
mqtt.begin(BROKER_ADDR);
}
void loop() {
mqtt.loop();
if ((millis() - lastTempPublishAt) > 3000) {
hvac.setCurrentTemperature(lastTemp);
lastTempPublishAt = millis();
lastTemp += 0.5;
}
}
+4 -1
View File
@@ -9,7 +9,7 @@ byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
HASwitch led("led", false, mqtt); // you can use custom name in place of "led"
HASwitch led("led", false); // you can use custom name in place of "led"
void onSwitchStateChanged(bool state, HASwitch* s)
{
@@ -27,6 +27,9 @@ void setup() {
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
// set icon (optional)
led.setIcon("mdi:lightbulb");
// handle switch state
led.onStateChanged(onSwitchStateChanged);
+32
View File
@@ -0,0 +1,32 @@
#include <Ethernet.h>
#include <ArduinoHA.h>
#define BROKER_ADDR IPAddress(192,168,0,17)
byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
void onMqttConnected() {
Serial.println("Connected to the broker!");
}
void onMqttConnectionFailed() {
Serial.println("Failed to connect to the broker!");
}
void setup() {
Serial.begin(9600);
Ethernet.begin(mac);
mqtt.onConnected(onMqttConnected);
mqtt.onConnectionFailed(onMqttConnectionFailed);
mqtt.begin(BROKER_ADDR);
}
void loop() {
Ethernet.maintain();
mqtt.loop();
}
@@ -11,7 +11,7 @@ byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
HASwitch led("led", false, mqtt); // you can use custom name in place of "led"
HASwitch led("led", false); // you can use custom name in place of "led"
void onSwitchStateChanged(bool state, HASwitch* s)
{
@@ -14,7 +14,7 @@ byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
HATriggers triggers(mqtt);
HATriggers triggers;
Button btn(BUTTON_PIN);
bool holdingBtn = false;
+1 -1
View File
@@ -9,7 +9,7 @@
WiFiClient client;
HADevice device;
HAMqtt mqtt(client, device);
HASwitch led("led", false, mqtt); // you can use custom name in place of "led"
HASwitch led("led", false); // you can use custom name in place of "led"
void onSwitchStateChanged(bool state, HASwitch* s)
{
+1 -1
View File
@@ -9,7 +9,7 @@
WiFiClient client;
HADevice device;
HAMqtt mqtt(client, device);
HASwitch led("led", false, mqtt); // you can use custom name in place of "led"
HASwitch led("led", false); // you can use custom name in place of "led"
void onSwitchStateChanged(bool state, HASwitch* s)
{
+11 -16
View File
@@ -10,21 +10,7 @@ double lastValue = 0;
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
/**
* Supported data types:
* - uint8_t
* - uint16_t
* - uint32_t
* - int8_t
* - int16_t
* - int32_t
* - double
* - float
*/
// you can use custom name in place of "temp"
// "0" is initial value of the sensor
HASensor<double> temp("temp", 0, mqtt);
HASensor temp("temp");
void setup() {
// you don't need to verify return status
@@ -34,8 +20,10 @@ void setup() {
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
// you can set custom units for the sensor (optional)
// configure sensor (optional)
temp.setUnitOfMeasurement("°C");
temp.setDeviceClass("temperature");
temp.setIcon("mdi:home");
mqtt.begin(BROKER_ADDR);
}
@@ -48,5 +36,12 @@ void loop() {
lastSentAt = millis();
lastValue = lastValue + 0.5;
temp.setValue(lastValue);
// Supported data types:
// uint32_t (uint16_t, uint8_t)
// int32_t (int16_t, int8_t)
// double
// float
// const char*
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
name=home-assistant-integration
version=1.1.1
version=1.2.0
author=Dawid Chyrzynski <dawid.chyrzynski@gmail.com>
maintainer=Dawid Chyrzynski <dawid.chyrzynski@gmail.com>
sentence=Home Assistant MQTT integration for Arduino
+2 -1
View File
@@ -5,8 +5,9 @@
#include "HAMqtt.h"
#include "HAUtils.h"
#include "device-types/HABinarySensor.h"
#include "device-types/HAFan.h"
#include "device-types/HAHVAC.h"
#include "device-types/HASensor.h"
#include "device-types/HASensor.cpp"
#include "device-types/HASwitch.h"
#include "device-types/HATagScanner.h"
#include "device-types/HATriggers.h"
+9
View File
@@ -2,3 +2,12 @@
// Please note that you need to initialize serial interface manually
// by calling Serial.begin([baudRate]) before initializing ArduinoHA.
// #define ARDUINOHA_DEBUG
// You can reduce Flash size of the compiled library by commenting unused components below
#define ARDUINOHA_BINARY_SENSOR
#define ARDUINOHA_FAN
#define ARDUINOHA_HVAC
#define ARDUINOHA_SENSOR
#define ARDUINOHA_SWITCH
#define ARDUINOHA_TAG_SCANNER
#define ARDUINOHA_TRIGGERS
+81 -1
View File
@@ -1,13 +1,19 @@
#include <Arduino.h>
#include "ArduinoHADefines.h"
#include "HADevice.h"
#include "HAUtils.h"
#include "HAMqtt.h"
#include "device-types/DeviceTypeSerializer.h"
#define HADEVICE_INIT \
_manufacturer(nullptr), \
_model(nullptr), \
_name(nullptr), \
_softwareVersion(nullptr)
_softwareVersion(nullptr), \
_sharedAvailability(false), \
_availabilityTopic(nullptr), \
_available(true) // device will be available by default
HADevice::HADevice() :
_uniqueId(nullptr),
@@ -30,6 +36,58 @@ HADevice::HADevice(const byte* uniqueId, const uint16_t& length) :
}
void HADevice::setAvailability(bool online)
{
_available = online;
publishAvailability();
}
bool HADevice::enableSharedAvailability()
{
if (_sharedAvailability) {
return false;
}
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Enabling shared availability in the device"));
#endif
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
nullptr,
nullptr,
DeviceTypeSerializer::AvailabilityTopic
);
if (topicSize == 0) {
return false;
}
_availabilityTopic = (char*)malloc(topicSize);
_sharedAvailability = true;
DeviceTypeSerializer::generateTopic(
_availabilityTopic,
nullptr,
nullptr,
DeviceTypeSerializer::AvailabilityTopic
);
return true;
}
bool HADevice::enableLastWill()
{
HAMqtt* mqtt = HAMqtt::instance();
if (mqtt == nullptr || _availabilityTopic == nullptr) {
return false;
}
mqtt->setLastWill(
_availabilityTopic,
DeviceTypeSerializer::Offline,
false
);
return true;
}
bool HADevice::setUniqueId(const byte* uniqueId, const uint16_t& length)
{
if (_uniqueId != nullptr) {
@@ -40,6 +98,28 @@ bool HADevice::setUniqueId(const byte* uniqueId, const uint16_t& length)
return true;
}
void HADevice::publishAvailability()
{
if (!_sharedAvailability) {
return;
}
HAMqtt* mqtt = HAMqtt::instance();
if (mqtt == nullptr) {
return;
}
mqtt->publish(
_availabilityTopic,
(
_available ?
DeviceTypeSerializer::Online :
DeviceTypeSerializer::Offline
),
true
);
}
uint16_t HADevice::calculateSerializedLength() const
{
uint16_t size =
+13
View File
@@ -25,7 +25,17 @@ public:
inline void setSoftwareVersion(const char* softwareVersion)
{ _softwareVersion = softwareVersion; }
inline bool isSharedAvailabilityEnabled() const
{ return _sharedAvailability; }
inline bool isOnline() const
{ return _available; }
void setAvailability(bool online);
bool enableSharedAvailability();
bool enableLastWill();
bool setUniqueId(const byte* uniqueId, const uint16_t& length);
void publishAvailability();
uint16_t calculateSerializedLength() const;
uint16_t serialize(char* dst) const;
@@ -35,6 +45,9 @@ private:
const char* _model;
const char* _name;
const char* _softwareVersion;
bool _sharedAvailability;
char* _availabilityTopic;
bool _available;
};
#endif
+118 -42
View File
@@ -5,43 +5,36 @@
#include "ArduinoHADefines.h"
#include "device-types/BaseDeviceType.h"
#define HAMQTT_INIT \
_netClient(netClient), \
_device(device), \
_hasDevice(true), \
_initialized(false), \
_discoveryPrefix(DefaultDiscoveryPrefix), \
_mqtt(new PubSubClient(netClient)), \
_serverIp(new IPAddress()), \
_serverPort(0), \
_username(nullptr), \
_password(nullptr), \
_lastConnectionAttemptAt(0), \
_devicesTypesNb(0), \
_devicesTypes(nullptr)
static const char* DefaultDiscoveryPrefix = "homeassistant";
static HAMqtt* instance = nullptr;
HAMqtt* HAMqtt::_instance = nullptr;
void onMessageReceived(char* topic, uint8_t* payload, unsigned int length)
{
if (instance == nullptr || length > UINT16_MAX) {
if (HAMqtt::instance() == nullptr || length > UINT16_MAX) {
return;
}
instance->processMessage(topic, payload, static_cast<uint16_t>(length));
HAMqtt::instance()->processMessage(topic, payload, static_cast<uint16_t>(length));
}
HAMqtt::HAMqtt(Client& netClient, HADevice& device) :
HAMQTT_INIT
_netClient(netClient),
_device(device),
_connectedCallback(nullptr),
_connectionFailedCallback(nullptr),
_initialized(false),
_discoveryPrefix(DefaultDiscoveryPrefix),
_mqtt(new PubSubClient(netClient)),
_username(nullptr),
_password(nullptr),
_lastConnectionAttemptAt(0),
_devicesTypesNb(0),
_devicesTypes(nullptr),
_lastWillTopic(nullptr),
_lastWillMessage(nullptr),
_lastWillRetain(false)
{
instance = this;
}
HAMqtt::HAMqtt(const char* clientId, Client& netClient, HADevice& device) :
HAMQTT_INIT
{
instance = this;
_instance = this;
}
bool HAMqtt::begin(
@@ -76,13 +69,11 @@ bool HAMqtt::begin(
return false;
}
*_serverIp = serverIp;
_serverPort = serverPort;
_username = username;
_password = password;
_initialized = true;
_mqtt->setServer(*_serverIp, _serverPort);
_mqtt->setServer(serverIp, serverPort);
_mqtt->setCallback(onMessageReceived);
return true;
@@ -94,16 +85,86 @@ bool HAMqtt::begin(
const char* password
)
{
return begin(serverIp, 1883, username, password);
return begin(serverIp, HAMQTT_DEFAULT_PORT, username, password);
}
bool HAMqtt::begin(
const char* hostname,
const uint16_t& serverPort,
const char* username,
const char* password
)
{
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Initializing ArduinoHA"));
Serial.print(F("Server address: "));
Serial.print(hostname);
Serial.print(F(":"));
Serial.print(serverPort);
Serial.println();
#endif
if (_device.getUniqueId() == nullptr) {
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Failed to initialize ArduinoHA. Missing device's unique ID."));
#endif
return false;
}
if (_initialized) {
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("ArduinoHA is already initialized"));
#endif
return false;
}
_username = username;
_password = password;
_initialized = true;
_mqtt->setServer(hostname, serverPort);
_mqtt->setCallback(onMessageReceived);
return true;
}
bool HAMqtt::begin(
const char* hostname,
const char* username,
const char* password
)
{
return begin(hostname, HAMQTT_DEFAULT_PORT, username, password);
}
bool HAMqtt::disconnect(bool sendLastWill)
{
if (!_initialized) {
return false;
}
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Closing connection with MQTT broker"));
#endif
if (sendLastWill &&
_lastWillTopic != nullptr &&
_lastWillMessage != nullptr) {
publish(_lastWillTopic, _lastWillMessage, _lastWillRetain);
}
_initialized = false;
_lastConnectionAttemptAt = 0;
_mqtt->disconnect();
return true;
}
void HAMqtt::loop()
{
if (!_initialized) {
return;
}
if (!_mqtt->loop()) {
if (_initialized && !_mqtt->loop()) {
connectToServer();
}
}
@@ -222,27 +283,42 @@ void HAMqtt::connectToServer()
Serial.println();
#endif
if (_username == nullptr || _password == nullptr) {
_mqtt->connect(_device.getUniqueId());
} else {
_mqtt->connect(_device.getUniqueId(), _username, _password);
}
_mqtt->connect(
_device.getUniqueId(),
_username,
_password,
_lastWillTopic,
0,
_lastWillRetain,
_lastWillMessage,
true
);
if (isConnected()) {
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Connected to the broker"));
#endif
onConnected();
onConnectedLogic();
} else {
#if defined(ARDUINOHA_DEBUG)
Serial.println(F("Failed to connect to the broker"));
if (_connectionFailedCallback) {
_connectionFailedCallback();
}
#endif
}
}
void HAMqtt::onConnected()
void HAMqtt::onConnectedLogic()
{
if (_connectedCallback) {
_connectedCallback();
}
_device.publishAvailability();
for (uint8_t i = 0; i < _devicesTypesNb; i++) {
_devicesTypes[i]->onMqttConnected();
}
+78 -18
View File
@@ -4,6 +4,9 @@
#include <Client.h>
#include <IPAddress.h>
#define HAMQTT_CALLBACK(name) void (*name)()
#define HAMQTT_DEFAULT_PORT 1883
class PubSubClient;
class HADevice;
class BaseDeviceType;
@@ -13,8 +16,10 @@ class HAMqtt
public:
static const uint16_t ReconnectInterval = 5000; // ms
inline static HAMqtt* instance()
{ return _instance; }
HAMqtt(Client& netClient, HADevice& device);
HAMqtt(const char* clientId, Client& netClient, HADevice& device);
/**
* Sets prefix for Home Assistant discovery.
@@ -34,7 +39,23 @@ public:
* Returns instance of the device assigned to the HAMqtt class.
*/
inline HADevice const* getDevice() const
{ return (_hasDevice ? &_device : nullptr); }
{ return &_device; }
/**
* Given callback will be called each time the connection with broker is acquired.
*
* @param callback
*/
inline void onConnected(HAMQTT_CALLBACK(callback))
{ _connectedCallback = callback; }
/**
* Given callback will be called each time the library fails to connect to the broker.
*
* @param callback
*/
inline void onConnectionFailed(HAMQTT_CALLBACK(callback))
{ _connectionFailedCallback = callback; }
/**
* Sets parameters of the connection to the MQTT broker.
@@ -48,27 +69,43 @@ public:
*/
bool begin(
const IPAddress& serverIp,
const uint16_t& serverPort = 1883,
const uint16_t& serverPort = HAMQTT_DEFAULT_PORT,
const char* username = nullptr,
const char* password = nullptr
);
/**
* Sets parameters of the connection to the MQTT broker on default 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.
* @param password Password for authentication.
*/
bool begin(
const IPAddress& serverIp,
const char* username,
const char* password
);
/**
* Connects to the MQTT broker using hostname.
*
* @param hostname Hostname of the MQTT broker.
* @param serverPort Port of the MQTT broker.
* @param username Username for authentication.
* @param password Password for authentication.
*/
bool begin(
const char* hostname,
const uint16_t& serverPort = HAMQTT_DEFAULT_PORT,
const char* username = nullptr,
const char* password = nullptr
);
bool begin(
const char* hostname,
const char* username,
const char* password
);
/**
* Closes connection with the MQTT broker.
*
* @param sendLastWill Set to true if you want to publish device unavailability before closing the connection.
*/
bool disconnect(bool sendLastWill = true);
/**
* ArduinoHA's ticker.
*/
@@ -116,6 +153,25 @@ public:
*/
bool subscribe(const char* topic);
/**
* Enables last will message that will be produced when device disconnects from the broker.
* If you want to change availability of the device in Home Assistant panel
* please use enableLastWill() method in the HADevice object instead.
*
* @param lastWillTopic
* @param lastWillMessage
* @param lastWillRetain
*/
inline void setLastWill(
const char* lastWillTopic,
const char* lastWillMessage,
bool lastWillRetain
) {
_lastWillTopic = lastWillTopic;
_lastWillMessage = lastWillMessage;
_lastWillRetain = lastWillRetain;
}
/**
* Processes MQTT message received from the broker (subscription).
*
@@ -126,6 +182,8 @@ public:
void processMessage(char* topic, uint8_t* payload, uint16_t length);
private:
static HAMqtt* _instance;
/**
* Attempts to connect to the MQTT broker.
* The method uses properties passed to the "begin" method.
@@ -135,21 +193,23 @@ private:
/**
* This method is called each time the connection with MQTT broker is acquired.
*/
void onConnected();
void onConnectedLogic();
Client& _netClient;
HADevice& _device;
bool _hasDevice;
HAMQTT_CALLBACK(_connectedCallback);
HAMQTT_CALLBACK(_connectionFailedCallback);
bool _initialized;
const char* _discoveryPrefix;
PubSubClient* _mqtt;
IPAddress* _serverIp;
uint16_t _serverPort;
const char* _username;
const char* _password;
uint32_t _lastConnectionAttemptAt;
uint8_t _devicesTypesNb;
BaseDeviceType** _devicesTypes;
const char* _lastWillTopic;
const char* _lastWillMessage;
bool _lastWillRetain;
};
#endif
+17 -29
View File
@@ -1,3 +1,7 @@
#ifdef ARDUINO_ARCH_SAMD
#include <avr/dtostrf.h>
#endif
#include <Arduino.h>
#include "HAUtils.h"
@@ -46,34 +50,18 @@ char* HAUtils::byteArrayToStr(
return dst;
}
uint8_t HAUtils::getValueTypeLength(const ValueType& type)
void HAUtils::tempToStr(
char* dst,
const double& temp
)
{
switch(type) {
case ValueTypeUint8:
return sizeof(uint8_t);
case ValueTypeUint16:
return sizeof(uint16_t);
case ValueTypeUint32:
return sizeof(uint32_t);
case ValueTypeInt8:
return sizeof(int8_t);
case ValueTypeInt16:
return sizeof(int16_t);
case ValueTypeInt32:
return sizeof(int32_t);
case ValueTypeDouble:
return sizeof(double);
case ValueTypeFloat:
return sizeof(float);
default:
return 0;
}
memset(dst, 0, sizeof(AHA_SERIALIZED_TEMP_SIZE));
dtostrf(temp, 0, 2, dst);
}
double HAUtils::strToTemp(
const char* src
)
{
return atof(src);
}
+10 -35
View File
@@ -3,24 +3,14 @@
#include <Arduino.h>
#define AHA_SERIALIZED_TEMP_SIZE 8
class HAUtils
{
public:
enum ValueType {
ValueTypeUnknown = -1,
ValueTypeUint8,
ValueTypeUint16,
ValueTypeUint32,
ValueTypeInt8,
ValueTypeInt16,
ValueTypeInt32,
ValueTypeDouble,
ValueTypeFloat
};
static bool endsWith(
const char* str,
const char* suffi
const char* suffix
);
static void byteArrayToStr(
char* dst,
@@ -31,28 +21,13 @@ public:
const byte* src,
const uint16_t& length
);
template <typename A, typename B >
static bool compareType(A a, B b) { return false; }
template <typename A, typename B >
static bool compareType(A a, A b) { return true; }
template <typename T>
static ValueType determineValueType() {
if (compareType<T, uint8_t>(0, (uint8_t)0)) return ValueTypeUint8;
else if (compareType<T, uint16_t>(0, (uint16_t)0)) return ValueTypeUint16;
else if (compareType<T, uint32_t>(0, (uint32_t)0)) return ValueTypeUint32;
else if (compareType<T, int8_t>(0, (int8_t)0)) return ValueTypeInt8;
else if (compareType<T, int16_t>(0, (int16_t)0)) return ValueTypeInt16;
else if (compareType<T, int32_t>(0, (int32_t)0)) return ValueTypeInt32;
else if (compareType<T, double>(0, (double)0)) return ValueTypeDouble;
else if (compareType<T, float>(0, (float)0)) return ValueTypeFloat;
return ValueTypeUnknown;
}
static uint8_t getValueTypeLength(const ValueType& type);
static void tempToStr(
char* dst,
const double& temp
);
static double strToTemp(
const char* src
);
};
#endif
+96 -7
View File
@@ -1,18 +1,17 @@
#include "BaseDeviceType.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
#include "../HAUtils.h"
BaseDeviceType::BaseDeviceType(
HAMqtt& mqtt,
const char* componentName,
const char* name
) :
_mqtt(mqtt),
_componentName(componentName),
_name(name),
_availability(AvailabilityDefault)
{
_mqtt.addDeviceType(this);
mqtt()->addDeviceType(this);
}
BaseDeviceType::~BaseDeviceType()
@@ -26,17 +25,84 @@ void BaseDeviceType::setAvailability(bool online)
publishAvailability();
}
HAMqtt* BaseDeviceType::mqtt() const
{
return HAMqtt::instance();
}
void BaseDeviceType::onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
)
{
(void)topic;
(void)payload;
(void)length;
}
void BaseDeviceType::publishConfig()
{
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return;
}
const uint16_t& deviceLength = device->calculateSerializedLength();
if (deviceLength == 0) {
return;
}
char serializedDevice[deviceLength];
if (device->serialize(serializedDevice) == 0) {
return;
}
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
return;
}
char topic[topicLength];
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
void BaseDeviceType::publishAvailability()
{
const HADevice* device = HAMqtt::instance()->getDevice();
if (device == nullptr) {
return;
}
if (_availability == AvailabilityDefault ||
!_mqtt.isConnected() ||
!mqtt()->isConnected() ||
strlen(_name) == 0 ||
strlen(_componentName) == 0) {
strlen(_componentName) == 0 ||
device->isSharedAvailabilityEnabled()) {
return;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
_componentName,
_name,
DeviceTypeSerializer::AvailabilityTopic
@@ -47,7 +113,6 @@ void BaseDeviceType::publishAvailability()
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
_componentName,
_name,
@@ -68,3 +133,27 @@ void BaseDeviceType::publishAvailability()
true
);
}
bool BaseDeviceType::isMyTopic(const char* topic, const char* expectedTopic)
{
if (topic == nullptr || expectedTopic == nullptr) {
return false;
}
if (strlen(name()) == 0) {
return false;
}
static const char Slash[] PROGMEM = {"/"};
// name + cmd topic + two slashes + null terminator
uint8_t suffixLength = strlen(name()) + strlen(expectedTopic) + 3;
char suffix[suffixLength];
strcpy_P(suffix, Slash);
strcat(suffix, name());
strcat_P(suffix, Slash);
strcat(suffix, expectedTopic);
return HAUtils::endsWith(topic, suffix);
}
+14 -12
View File
@@ -3,6 +3,7 @@
#include <stdint.h>
#include "../ArduinoHADefines.h"
#include "DeviceTypeSerializer.h"
class HAMqtt;
@@ -11,21 +12,11 @@ class BaseDeviceType
{
public:
BaseDeviceType(
HAMqtt& mqtt,
const char* componentName,
const char* name
);
virtual ~BaseDeviceType();
inline bool isOnline() const
{ return (_availability == AvailabilityOnline); }
virtual void setAvailability(bool online);
protected:
inline HAMqtt* mqtt() const
{ return &_mqtt; }
inline const char* name() const
{ return _name; }
@@ -35,14 +26,26 @@ protected:
inline bool isAvailabilityConfigured() const
{ return (_availability != AvailabilityDefault); }
inline bool isOnline() const
{ return (_availability == AvailabilityOnline); }
virtual void setAvailability(bool online);
protected:
HAMqtt* mqtt() const;
virtual void onMqttConnected() = 0;
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
) { };
);
virtual void publishConfig();
virtual void publishAvailability();
virtual bool isMyTopic(const char* topic, const char* expectedTopic);
virtual uint16_t calculateSerializedLength(const char* serializedDevice) const = 0;
virtual bool writeSerializedData(const char* serializedDevice) const = 0;
const char* const _componentName;
const char* const _name;
@@ -54,7 +57,6 @@ private:
AvailabilityOffline
};
HAMqtt& _mqtt;
Availability _availability;
friend class HAMqtt;
+208 -67
View File
@@ -1,6 +1,7 @@
#include <Arduino.h>
#include "DeviceTypeSerializer.h"
#include "BaseDeviceType.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
@@ -19,31 +20,31 @@ const char* DeviceTypeSerializer::StateOn = "ON";
const char* DeviceTypeSerializer::StateOff = "OFF";
uint16_t DeviceTypeSerializer::calculateTopicLength(
const HAMqtt* mqtt,
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator
)
{
const char* prefix = mqtt->getDiscoveryPrefix();
const char* prefix = HAMqtt::instance()->getDiscoveryPrefix();
if (prefix == nullptr) {
return 0;
}
uint16_t size =
strlen(prefix) + 1 + // with slash
strlen(component) + 1 + // with slash
strlen(prefix) + 1 + // prefix with slash
strlen(suffix);
if (component != nullptr) {
size += strlen(component) + 1; // component with slash
}
if (HAMqtt::instance()->getDevice() != nullptr) {
size += strlen(HAMqtt::instance()->getDevice()->getUniqueId()) + 1; // device ID with slash
}
if (objectId != nullptr) {
size += strlen(objectId) + 1; // with slash
} else {
size += 1; // slash
}
if (mqtt->getDevice() != nullptr) {
size += strlen(mqtt->getDevice()->getUniqueId()) + 1; // with slash
}
if (includeNullTerminator) {
@@ -54,26 +55,35 @@ uint16_t DeviceTypeSerializer::calculateTopicLength(
}
uint16_t DeviceTypeSerializer::generateTopic(
const HAMqtt* mqtt,
char* output,
const char* component,
const char* objectId,
const char* suffix
)
{
const char* prefix = mqtt->getDiscoveryPrefix();
const char* prefix = HAMqtt::instance()->getDiscoveryPrefix();
if (prefix == nullptr) {
return 0;
}
strcpy(output, prefix);
strcat_P(output, CharSlash);
strcat(output, component);
strcat_P(output, CharSlash);
if (mqtt->getDevice() != nullptr) {
strcat(output, mqtt->getDevice()->getUniqueId());
if (component != nullptr) {
strcat(output, component);
strcat_P(output, CharSlash);
}
if (HAMqtt::instance()->getDevice() != nullptr) {
strcat(output, HAMqtt::instance()->getDevice()->getUniqueId());
strcat_P(output, CharSlash);
}
if (objectId != nullptr) {
strcat(output, objectId);
strcat_P(output, CharSlash);
}
strcat(output, objectId);
strcat_P(output, CharSlash);
strcat(output, suffix);
return strlen(output) + 1; // size with null terminator
}
@@ -94,10 +104,10 @@ uint16_t DeviceTypeSerializer::calculateNameFieldSize(const char* name)
}
uint16_t DeviceTypeSerializer::calculateUniqueIdFieldSize(
const HADevice* device,
const char* name
)
{
HADevice const* device = HAMqtt::instance()->getDevice();
if (device == nullptr || name == nullptr) {
return 0;
}
@@ -111,19 +121,22 @@ uint16_t DeviceTypeSerializer::calculateUniqueIdFieldSize(
}
uint16_t DeviceTypeSerializer::calculateAvailabilityFieldSize(
const HAMqtt* mqtt,
const char* componentName,
const char* name
const BaseDeviceType* const dt
)
{
if (mqtt == nullptr || componentName == nullptr || name == nullptr) {
const HADevice* device = HAMqtt::instance()->getDevice();
if (device == nullptr) {
return 0;
}
const bool& sharedAvailability = device->isSharedAvailabilityEnabled();
if (!sharedAvailability && !dt->isAvailabilityConfigured()) {
return 0;
}
const uint16_t& availabilityTopicLength = calculateTopicLength(
mqtt,
componentName,
name,
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->name()),
AvailabilityTopic,
false
);
@@ -136,6 +149,16 @@ uint16_t DeviceTypeSerializer::calculateAvailabilityFieldSize(
return availabilityTopicLength + 12; // 12 - length of the JSON decorators for this field
}
uint16_t DeviceTypeSerializer::calculateRetainFieldSize(bool retain)
{
if (!retain) {
return 0;
}
// Field format: ,"ret":true
return 11;
}
uint16_t DeviceTypeSerializer::calculateDeviceFieldSize(
const char* serializedDevice
)
@@ -148,85 +171,87 @@ uint16_t DeviceTypeSerializer::calculateDeviceFieldSize(
return strlen(serializedDevice) + 7; // 7 - length of the JSON decorators for this field
}
void DeviceTypeSerializer::mqttWriteBeginningJson(HAMqtt* mqtt)
void DeviceTypeSerializer::mqttWriteBeginningJson()
{
if (mqtt == nullptr) {
return;
}
static const char Data[] PROGMEM = {"{"};
mqtt->writePayload_P(Data);
HAMqtt::instance()->writePayload_P(Data);
}
void DeviceTypeSerializer::mqttWriteEndJson(HAMqtt* mqtt)
void DeviceTypeSerializer::mqttWriteEndJson()
{
if (mqtt == nullptr) {
return;
}
static const char Data[] PROGMEM = {"}"};
mqtt->writePayload_P(Data);
HAMqtt::instance()->writePayload_P(Data);
}
void DeviceTypeSerializer::mqttWriteConstCharField(
HAMqtt* mqtt,
const char* prefix,
const char* value
const char* value,
bool quoteSuffix
)
{
if (mqtt == nullptr || prefix == nullptr || value == nullptr) {
if (prefix == nullptr || value == nullptr) {
return;
}
mqtt->writePayload_P(prefix);
mqtt->writePayload(value, strlen(value));
mqtt->writePayload_P(CharQuotation);
HAMqtt::instance()->writePayload_P(prefix);
HAMqtt::instance()->writePayload(value, strlen(value));
if (quoteSuffix) {
HAMqtt::instance()->writePayload_P(CharQuotation);
}
}
void DeviceTypeSerializer::mqttWriteNameField(HAMqtt* mqtt, const char* name)
void DeviceTypeSerializer::mqttWriteNameField(const char* name)
{
if (mqtt == nullptr || name == nullptr) {
if (name == nullptr) {
return;
}
static const char Prefix[] PROGMEM = {",\"name\":\""};
mqttWriteConstCharField(mqtt, Prefix, name);
mqttWriteConstCharField(Prefix, name);
}
void DeviceTypeSerializer::mqttWriteUniqueIdField(
HAMqtt* mqtt,
const char* name
)
{
if (mqtt == nullptr || name == nullptr) {
if (name == nullptr) {
return;
}
HADevice const* device = HAMqtt::instance()->getDevice();
if (device == nullptr) {
return;
}
static const char Prefix[] PROGMEM = {",\"uniq_id\":\""};
uint8_t uniqueIdLength = strlen(name) + strlen(mqtt->getDevice()->getUniqueId()) + 2; // underscore and null temrinator
uint8_t uniqueIdLength = strlen(name) + strlen(device->getUniqueId()) + 2; // underscore and null temrinator
char uniqueId[uniqueIdLength];
strcpy(uniqueId, name);
strcat_P(uniqueId, CharUnderscore);
strcat(uniqueId, mqtt->getDevice()->getUniqueId());
strcat(uniqueId, device->getUniqueId());
mqttWriteConstCharField(mqtt, Prefix, uniqueId);
mqttWriteConstCharField(Prefix, uniqueId);
}
void DeviceTypeSerializer::mqttWriteAvailabilityField(
HAMqtt* mqtt,
const char* componentName,
const char* name
const BaseDeviceType* const dt
)
{
if (mqtt == nullptr || componentName == nullptr || name == nullptr) {
const HADevice* device = HAMqtt::instance()->getDevice();
if (device == nullptr) {
return;
}
const bool& sharedAvailability = device->isSharedAvailabilityEnabled();
if (!sharedAvailability && !dt->isAvailabilityConfigured()) {
return;
}
const uint16_t& topicSize = calculateTopicLength(
mqtt,
componentName,
name,
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->name()),
AvailabilityTopic
);
if (topicSize == 0) {
@@ -235,10 +260,9 @@ void DeviceTypeSerializer::mqttWriteAvailabilityField(
char availabilityTopic[topicSize];
generateTopic(
mqtt,
availabilityTopic,
componentName,
name,
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->name()),
AvailabilityTopic
);
@@ -247,20 +271,137 @@ void DeviceTypeSerializer::mqttWriteAvailabilityField(
}
static const char Prefix[] PROGMEM = {",\"avty_t\":\""};
mqttWriteConstCharField(mqtt, Prefix, availabilityTopic);
mqttWriteConstCharField(Prefix, availabilityTopic);
}
void DeviceTypeSerializer::mqttWriteRetainField(
bool retain
)
{
if (!retain) {
return;
}
static const char Prefix[] PROGMEM = {",\"ret\":"};
mqttWriteConstCharField(
Prefix,
"true",
false
);
}
void DeviceTypeSerializer::mqttWriteDeviceField(
HAMqtt* mqtt,
const char* serializedDevice
)
{
if (mqtt == nullptr || serializedDevice == nullptr) {
if (serializedDevice == nullptr) {
return;
}
static const char Data[] PROGMEM = {",\"dev\":"};
mqtt->writePayload_P(Data);
mqtt->writePayload(serializedDevice, strlen(serializedDevice));
HAMqtt::instance()->writePayload_P(Data);
HAMqtt::instance()->writePayload(serializedDevice, strlen(serializedDevice));
}
bool DeviceTypeSerializer::mqttWriteTopicField(
const BaseDeviceType* const dt,
const char* jsonPrefix,
const char* topicSuffix
)
{
if (jsonPrefix == nullptr || topicSuffix == nullptr) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
dt->componentName(),
dt->name() ,
topicSuffix
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
topic,
dt->componentName(),
dt->name(),
topicSuffix
);
if (strlen(topic) == 0) {
return false;
}
DeviceTypeSerializer::mqttWriteConstCharField(jsonPrefix, topic);
return true;
}
bool DeviceTypeSerializer::mqttPublishMessage(
const BaseDeviceType* const dt,
const char* topicSuffix,
const char* data
)
{
if (topicSuffix == nullptr || data == nullptr) {
return false;
}
const uint16_t& topicSize = calculateTopicLength(
dt->componentName(),
dt->name(),
topicSuffix
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
topic,
dt->componentName(),
dt->name(),
topicSuffix
);
if (strlen(topic) == 0) {
return false;
}
return HAMqtt::instance()->publish(
topic,
data,
true
);
}
bool DeviceTypeSerializer::mqttSubscribeTopic(
const BaseDeviceType* const dt,
const char* topicSuffix
)
{
const uint16_t& topicSize = calculateTopicLength(
dt->componentName(),
dt->name(),
topicSuffix
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
topic,
dt->componentName(),
dt->name(),
topicSuffix
);
if (strlen(topic) == 0) {
return false;
}
return HAMqtt::instance()->subscribe(topic);
}
+32 -19
View File
@@ -3,8 +3,8 @@
#include <stdint.h>
class HAMqtt;
class HADevice;
class BaseDeviceType;
class DeviceTypeSerializer
{
@@ -21,7 +21,7 @@ public:
/**
* Calculates length of the topic with given parameters.
* Topic format: [discovery prefix]/[component]/[objectId]/[suffix]
* Topic format: [discovery prefix]/[component]/[deviceId]/[objectId]/[suffix]
*
* @param component
* @param objectId
@@ -29,7 +29,6 @@ public:
* @param includeNullTerminator
*/
static uint16_t calculateTopicLength(
const HAMqtt* mqtt,
const char* component,
const char* objectId,
const char* suffix,
@@ -39,7 +38,7 @@ public:
/**
* Generates topic and saves it to the given buffer.
* Please note that size of the buffer must be calculated by `calculateTopicLength` method first.
* Topic format: [discovery prefix]/[component]/[objectId]/[suffix]
* Topic format: [discovery prefix]/[component]/[deviceId]/[objectId]/[suffix]
*
* @param output
* @param component
@@ -48,7 +47,6 @@ public:
* @param includeNullTerminator
*/
static uint16_t generateTopic(
const HAMqtt* mqtt,
char* output,
const char* component,
const char* objectId,
@@ -60,39 +58,54 @@ public:
const char* name
);
static uint16_t calculateUniqueIdFieldSize(
const HADevice* device,
const char* name
);
static uint16_t calculateAvailabilityFieldSize(
const HAMqtt* mqtt,
const char* componentName,
const char* name
const BaseDeviceType* const dt
);
static uint16_t calculateRetainFieldSize(
bool retain
);
static uint16_t calculateDeviceFieldSize(
const char* serializedDevice
);
static void mqttWriteBeginningJson(HAMqtt* mqtt);
static void mqttWriteEndJson(HAMqtt* mqtt);
static void mqttWriteBeginningJson();
static void mqttWriteEndJson();
static void mqttWriteConstCharField(
HAMqtt* mqtt,
const char* prefix,
const char* value
const char* value,
bool quoteSuffix = true
);
static void mqttWriteNameField(
const char* name
);
static void mqttWriteNameField(HAMqtt* mqtt, const char* name);
static void mqttWriteUniqueIdField(
HAMqtt* mqtt,
const char* name
);
static void mqttWriteAvailabilityField(
HAMqtt* mqtt,
const char* componentName,
const char* name
const BaseDeviceType* const dt
);
static void mqttWriteRetainField(
bool retain
);
static void mqttWriteDeviceField(
HAMqtt* mqtt,
const char* serializedDevice
);
static bool mqttWriteTopicField(
const BaseDeviceType* const dt,
const char* jsonPrefix,
const char* topicSuffix
);
static bool mqttPublishMessage(
const BaseDeviceType* const dt,
const char* topicSuffix,
const char* data
);
static bool mqttSubscribeTopic(
const BaseDeviceType* const dt,
const char* topicSuffix
);
};
#endif
+43 -110
View File
@@ -1,16 +1,38 @@
#include "HABinarySensor.h"
#ifdef ARDUINOHA_BINARY_SENSOR
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
#include "../HAUtils.h"
HABinarySensor::HABinarySensor(
const char* name,
bool initialState
) :
BaseDeviceType("binary_sensor", name),
_class(nullptr),
_currentState(initialState)
{
}
HABinarySensor::HABinarySensor(
const char* name,
bool initialState,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt, "binary_sensor", name),
_class(nullptr),
HABinarySensor(name, initialState)
{
(void)mqtt;
}
HABinarySensor::HABinarySensor(
const char* name,
const char* deviceClass,
bool initialState
) :
BaseDeviceType("binary_sensor", name),
_class(deviceClass),
_currentState(initialState)
{
@@ -22,11 +44,9 @@ HABinarySensor::HABinarySensor(
bool initialState,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt, "binary_sensor", name),
_class(deviceClass),
_currentState(initialState)
HABinarySensor(name, deviceClass, initialState)
{
(void)mqtt;
}
void HABinarySensor::onMqttConnected()
@@ -46,10 +66,6 @@ bool HABinarySensor::setState(bool state)
return true;
}
if (strlen(name()) == 0) {
return false;
}
if (publishState(state)) {
_currentState = state;
return true;
@@ -58,54 +74,6 @@ bool HABinarySensor::setState(bool state)
return false;
}
void HABinarySensor::publishConfig()
{
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return;
}
const uint16_t& deviceLength = device->calculateSerializedLength();
if (deviceLength == 0) {
return;
}
char serializedDevice[deviceLength];
if (device->serialize(serializedDevice) == 0) {
return;
}
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
return;
}
char topic[topicLength];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
bool HABinarySensor::publishState(bool state)
{
if (strlen(name()) == 0) {
@@ -113,7 +81,6 @@ bool HABinarySensor::publishState(bool state)
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
@@ -124,7 +91,6 @@ bool HABinarySensor::publishState(bool state)
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
@@ -162,21 +128,13 @@ uint16_t HABinarySensor::calculateSerializedLength(
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(name());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
if (isAvailabilityConfigured()) {
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(
mqtt(),
componentName(),
name()
);
}
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
// state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic,
@@ -206,56 +164,31 @@ bool HABinarySensor::writeSerializedData(const char* serializedDevice) const
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
DeviceTypeSerializer::mqttWriteBeginningJson();
// state topic
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
return false;
}
static const char Prefix[] PROGMEM = {"\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// device class
if (_class != nullptr) {
static const char Prefix[] PROGMEM = {",\"dev_cla\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _class);
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _class);
}
DeviceTypeSerializer::mqttWriteNameField(mqtt(), name());
DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name());
if (isAvailabilityConfigured()) {
DeviceTypeSerializer::mqttWriteAvailabilityField(
mqtt(),
componentName(),
name()
);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
DeviceTypeSerializer::mqttWriteNameField(name());
DeviceTypeSerializer::mqttWriteUniqueIdField(name());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
+16 -5
View File
@@ -3,21 +3,27 @@
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_BINARY_SENSOR
class HABinarySensor : public BaseDeviceType
{
public:
/**
* Initializes binary sensor.
*
* @param name Name of the sensor. Recommendes characters: [a-z0-9\-_]
* @param name Name of the sensor. Recommended characters: [a-z0-9\-_]
* @param initialState Initial state of the sensor.
It will be published right after "config" message in order to update HA state.
*/
HABinarySensor(
const char* name,
bool initialState
);
HABinarySensor(
const char* name,
bool initialState,
HAMqtt& mqtt
);
); // legacy constructor
/**
* Initializes binary sensor with the specified class.
@@ -28,6 +34,11 @@ public:
* @param initialState Initial state of the sensor.
It will be published right after "config" message in order to update HA state.
*/
HABinarySensor(
const char* name,
const char* deviceClass,
bool initialState
);
HABinarySensor(
const char* name,
const char* deviceClass,
@@ -58,13 +69,13 @@ public:
{ return _currentState; }
private:
void publishConfig();
bool publishState(bool state);
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedData(const char* serializedDevice) const;
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
const char* _class;
bool _currentState;
};
#endif
#endif
+418
View File
@@ -0,0 +1,418 @@
#include "HAFan.h"
#ifdef ARDUINOHA_FAN
#include "../HAMqtt.h"
#include "../HADevice.h"
const char* HAFan::SpeedCommandTopic = {"sct"};
const char* HAFan::SpeedStateTopic = {"sst"};
static const char OffStr[] PROGMEM = {"off"};
static const char LowStr[] PROGMEM = {"low"};
static const char MediumStr[] PROGMEM = {"medium"};
static const char HighStr[] PROGMEM = {"high"};
HAFan::HAFan(const char* uniqueId, uint8_t features) :
BaseDeviceType("fan", uniqueId),
_features(features),
_speeds(OffSpeed | LowSpeed | MediumSpeed | HighSpeed),
_currentState(false),
_stateCallback(nullptr),
_currentSpeed(OffSpeed),
_speedCallback(nullptr),
_label(nullptr),
_retain(false)
{
}
HAFan::HAFan(const char* uniqueId, uint8_t features, HAMqtt& mqtt) :
HAFan(uniqueId, features)
{
(void)mqtt;
}
void HAFan::onMqttConnected()
{
if (strlen(name()) == 0) {
return;
}
publishConfig();
publishAvailability();
DeviceTypeSerializer::mqttSubscribeTopic(
this,
DeviceTypeSerializer::CommandTopic
);
if (_features & SpeedsFeature) {
DeviceTypeSerializer::mqttSubscribeTopic(
this,
SpeedCommandTopic
);
}
if (!_retain) {
publishState(_currentState);
publishSpeed(_currentSpeed);
}
}
void HAFan::onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
)
{
(void)payload;
if (isMyTopic(topic, DeviceTypeSerializer::CommandTopic)) {
bool state = (length == strlen(DeviceTypeSerializer::StateOn));
setState(state, true);
} else if (isMyTopic(topic, SpeedCommandTopic)) {
char speed[length + 1];
memset(speed, 0, sizeof(speed));
memcpy(speed, payload, length);
setSpeedFromStr(speed);
}
}
bool HAFan::setState(bool state, bool force)
{
if (!force && _currentState == state) {
return true;
}
if (publishState(state)) {
_currentState = state;
if (_stateCallback) {
_stateCallback(state);
}
return true;
}
return false;
}
bool HAFan::setSpeed(Speed speed)
{
if (speed == UnknownSpeed) {
return false;
}
if (publishSpeed(speed)) {
_currentSpeed = speed;
if (_speedCallback) {
_speedCallback(_currentSpeed);
}
return true;
}
return false;
}
bool HAFan::setSpeedFromStr(const char* speed)
{
if (strcmp_P(speed, OffStr) == 0) {
return setSpeed(OffSpeed);
} else if (strcmp_P(speed, LowStr) == 0) {
return setSpeed(LowSpeed);
} else if (strcmp_P(speed, MediumStr) == 0) {
return setSpeed(MediumSpeed);
} else if (strcmp_P(speed, HighStr) == 0) {
return setSpeed(HighSpeed);
}
return false;
}
bool HAFan::publishState(bool state)
{
if (strlen(name()) == 0) {
return false;
}
return DeviceTypeSerializer::mqttPublishMessage(
this,
DeviceTypeSerializer::StateTopic,
(
state ?
DeviceTypeSerializer::StateOn :
DeviceTypeSerializer::StateOff
)
);
}
bool HAFan::publishSpeed(Speed speed)
{
if (strlen(name()) == 0 || speed == UnknownSpeed) {
return false;
}
char speedStr[7];
switch (speed) {
case OffSpeed:
strcpy_P(speedStr, OffStr);
break;
case LowSpeed:
strcpy_P(speedStr, LowStr);
break;
case MediumSpeed:
strcpy_P(speedStr, MediumStr);
break;
case HighSpeed:
strcpy_P(speedStr, HighStr);
break;
default:
return false;
}
return DeviceTypeSerializer::mqttPublishMessage(
this,
SpeedStateTopic,
speedStr
);
}
uint16_t HAFan::calculateSerializedLength(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(name());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
size += DeviceTypeSerializer::calculateNameFieldSize(_label);
size += DeviceTypeSerializer::calculateRetainFieldSize(_retain);
// command topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
DeviceTypeSerializer::CommandTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: "cmd_t":"[TOPIC]"
size += topicLength + 10; // 10 - length of the JSON decorators for this field
}
// state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
DeviceTypeSerializer::StateTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: ,"stat_t":"[TOPIC]"
size += topicLength + 12; // 12 - length of the JSON decorators for this field
}
// speeds
if (_features & SpeedsFeature) {
// command topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
SpeedCommandTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: ,"spd_cmd_t":"[TOPIC]"
size += topicLength + 15; // 15 - length of the JSON decorators for this field
}
// state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
SpeedStateTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: ,"spd_stat_t":"[TOPIC]"
size += topicLength + 16; // 16 - length of the JSON decorators for this field
}
// speeds list
// Format: ,"spds":[SPEEDS]
size += calculateSpeedsLength() + 8; // 8 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
uint16_t HAFan::calculateSpeedsLength() const
{
uint16_t length = 2; // opening and closing bracket
if (_speeds & OffSpeed) {
// escape + comma
length += 3 + strlen_P(OffStr);
}
if (_speeds & LowSpeed) {
// escape + comma
length += 3 + strlen_P(LowStr);
}
if (_speeds & MediumSpeed) {
// escape + comma
length += 3 + strlen_P(MediumStr);
}
if (_speeds & HighSpeed) {
// escape + comma
length += 3 + strlen_P(HighStr);
}
if (length > 2) {
length--; // remove trailing comma
}
return length; // excludes null terminator
}
bool HAFan::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// command topic
{
static const char Prefix[] PROGMEM = {"\"cmd_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::CommandTopic
);
}
// state topic
{
static const char Prefix[] PROGMEM = {",\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// speeds
if (_features & SpeedsFeature) {
// command topic
{
static const char Prefix[] PROGMEM = {",\"spd_cmd_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
SpeedCommandTopic
);
}
// state topic
{
static const char Prefix[] PROGMEM = {",\"spd_stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
SpeedStateTopic
);
}
// supported speeds
{
static const char CharQuotation[] PROGMEM = {"\""};
static const char CharQuotationComma[] PROGMEM = {"\","};
static const char Prefix[] PROGMEM = {",\"spds\":"};
char speedStr[calculateSpeedsLength() + 1]; // plus null terminator
memset(speedStr, 0, sizeof(speedStr));
strcpy(speedStr, "[");
if (_speeds != 0) {
if (_speeds & OffSpeed) {
strcat_P(speedStr, CharQuotation);
strcat_P(speedStr, OffStr);
strcat_P(speedStr, CharQuotationComma);
}
if (_speeds & LowSpeed) {
strcat_P(speedStr, CharQuotation);
strcat_P(speedStr, LowStr);
strcat_P(speedStr, CharQuotationComma);
}
if (_speeds & MediumSpeed) {
strcat_P(speedStr, CharQuotation);
strcat_P(speedStr, MediumStr);
strcat_P(speedStr, CharQuotationComma);
}
if (_speeds & HighSpeed) {
strcat_P(speedStr, CharQuotation);
strcat_P(speedStr, HighStr);
strcat_P(speedStr, CharQuotationComma);
}
speedStr[strlen(speedStr) - 1] = ']';
} else {
strcat(speedStr, "]");
}
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
speedStr,
false
);
}
}
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteNameField(_label);
DeviceTypeSerializer::mqttWriteUniqueIdField(name());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
+151
View File
@@ -0,0 +1,151 @@
#ifndef AHA_FAN_H
#define AHA_FAN_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_FAN
#define HAFAN_STATE_CALLBACK_BOOL(name) void (*name)(bool)
#define HAFAN_STATE_CALLBACK_SPEED(name) void (*name)(Speed)
class HAFan : public BaseDeviceType
{
public:
static const char* SpeedCommandTopic;
static const char* SpeedStateTopic;
enum Features {
DefaultFeatures = 0,
SpeedsFeature = 1
};
enum Speed {
UnknownSpeed = 0,
OffSpeed = 1,
LowSpeed = 2,
MediumSpeed = 4,
HighSpeed = 8
};
HAFan(const char* uniqueId, uint8_t features = DefaultFeatures);
HAFan(const char* uniqueId, uint8_t features, HAMqtt& mqtt); // legacy constructor
virtual void onMqttConnected() override;
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
) override;
/**
* Changes state of the fan and publishes MQTT message.
* Please note that if a new value is the same as previous one,
* the MQTT message won't be published.
*
* @param state New state of the fan (on - true, off - false).
* @param force Forces to update state without comparing it to previous known state.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setState(bool state, bool force = false);
/**
* Alias for setState(true).
*/
inline bool turnOn()
{ return setState(true); }
/**
* Alias for setState(false).
*/
inline bool turnOff()
{ return setState(false); }
/**
* Returns last known state of the fan.
* If setState method wasn't called the initial value will be returned.
*/
inline bool getState() const
{ return _currentState; }
/**
* Registers callback that will be called each time the state of the fan changes.
* Please note that it's not possible to register multiple callbacks for the same fan.
*
* @param callback
*/
inline void onStateChanged(HAFAN_STATE_CALLBACK_BOOL(callback))
{ _stateCallback = callback; }
/**
* Sets the list of supported fan's speeds.
*
* @param speeds
*/
inline void setSpeeds(uint8_t speeds)
{ _speeds = speeds; }
/**
* Sets speed of the fan.
*
* @param speed
*/
bool setSpeed(Speed speed);
/**
* Sets speed of the fan based on the name of the mode.
*
* @param speed
*/
bool setSpeedFromStr(const char* speed);
/**
* Returns current speed of the fan.
*/
inline Speed getSpeed() const
{ return _currentSpeed; }
/**
* Registers callback that will be called each time the speed of the fan changes.
* Please note that it's not possible to register multiple callbacks for the same fan.
*
* @param callback
*/
inline void onSpeedChanged(HAFAN_STATE_CALLBACK_SPEED(callback))
{ _speedCallback = callback; }
/**
* Sets name that wil be displayed in the Home Assistant panel.
*
* @param name
*/
inline void setName(const char* name)
{ _label = name; } // it needs to be called "label" as "_name" is already in use
/**
* Sets `retain` flag for commands published by Home Assistant.
* By default it's set to false.
*
* @param retain
*/
inline void setRetain(bool retain)
{ _retain = retain; }
protected:
bool publishState(bool state);
bool publishSpeed(Speed speed);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
uint16_t calculateSpeedsLength() const;
bool writeSerializedData(const char* serializedDevice) const override;
const uint8_t _features;
uint8_t _speeds;
bool _currentState;
HAFAN_STATE_CALLBACK_BOOL(_stateCallback);
Speed _currentSpeed;
HAFAN_STATE_CALLBACK_SPEED(_speedCallback);
const char* _label;
bool _retain;
};
#endif
#endif
File diff suppressed because it is too large Load Diff
+345
View File
@@ -0,0 +1,345 @@
#ifndef AHA_HVAC_H
#define AHA_HVAC_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_HVAC
#define HAHVAC_STATE_CALLBACK_BOOL(name) void (*name)(bool)
#define HAHVAC_STATE_CALLBACK_DOUBLE(name) void (*name)(double)
#define HAHVAC_STATE_CALLBACK_MODE(name) void (*name)(HAHVAC::Mode)
class HAHVAC : public BaseDeviceType
{
public:
static const char* ActionTopic;
static const char* AuxCommandTopic;
static const char* AuxStateTopic;
static const char* AwayCommandTopic;
static const char* AwayStateTopic;
static const char* HoldCommandTopic;
static const char* HoldStateTopic;
static const char* TargetTemperatureCommandTopic;
static const char* TargetTemperatureStateTopic;
static const char* CurrentTemperatureTopic;
static const char* ModeCommandTopic;
static const char* ModeStateTopic;
enum Features {
DefaultFeatures = 0,
ActionFeature = 1,
AuxHeatingFeature = 2,
AwayModeFeature = 4,
HoldFeature = 8
};
enum Action {
UnknownAction = 0,
OffAction,
HeatingAction,
CoolingAction,
DryingAction,
IdleAction,
FanAction
};
enum Mode {
UnknownMode = 0,
OffMode = 1,
AutoMode = 2,
CoolMode = 4,
HeatMode = 8,
DryMode = 16,
FanOnlyMode = 32
};
enum TemperatureUnit {
DefaultUnit = 1,
CelsiusUnit,
FahrenheitUnit
};
/**
* Initializes HVAC.
*
* @param uniqueId Unique ID of the HVAC. Recommendes characters: [a-z0-9\-_]
* @param features The list of additional features (flag). For example: `HAHVAC::AuxHeatingFeature | HAHVAC::AwayModeFeature`
*/
HAHVAC(
const char* uniqueId,
uint8_t features = 0
);
HAHVAC(
const char* uniqueId,
uint8_t features,
HAMqtt& mqtt
); // legacy constructor
virtual void onMqttConnected() override;
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
) override;
/**
* Changes action of the HVAC (it's displayed in the Home Assistant panel).
*
* @param action New action.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setAction(Action action);
/**
* Returns action that was previously sent to MQTT.
*/
inline Action getAction() const
{ return _action; }
/**
* Changes default temperature unit.
* Please note that this method must be called before `mqtt.begin(...)`.
*
* @param unit See TemperatureUnit enum above
*/
inline void setTemperatureUnit(TemperatureUnit unit)
{ _temperatureUnit = unit; }
/**
* Publishes aux heating state.
* Please note that HAHVAC::AuxHeatingFeature must be set in the constructor.
*
* @param state ON (true) / OFF (false)
* @returns Returns true if MQTT message has been published successfully.
*/
bool setAuxHeatingState(bool state);
/**
* Registers callback that will be called each time the aux heating's state changes.
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onAuxHeatingStateChanged(HAHVAC_STATE_CALLBACK_BOOL(callback))
{ _auxHeatingCallback = callback; }
/**
* Returns state of the aux heating.
*
* @returns ON (true) / OFF (false)
*/
inline bool getAuxHeatingState() const
{ return _auxHeatingState; }
/**
* Publishes away state.
* Please note that HAHVAC::AwayModeFeature must be set in the constructor.
*
* @param state ON (true) / OFF (false)
* @returns Returns true if MQTT message has been published successfully.
*/
bool setAwayState(bool state);
/**
* Registers callback that will be called each time the away's state changes.
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onAwayStateChanged(HAHVAC_STATE_CALLBACK_BOOL(callback))
{ _awayCallback = callback; }
/**
* Returns away's state.
*
* @returns ON (true) / OFF (false)
*/
inline bool getAwayState() const
{ return _awayState; }
/**
* Publishes hold state.
* Please note that HAHVAC::HoldFeature must be set in the constructor.
*
* @param state ON (true) / OFF (false)
* @returns Returns true if MQTT message has been published successfully.
*/
bool setHoldState(bool state);
/**
* Registers callback that will be called each time the hold's state changes.
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onHoldStateChanged(HAHVAC_STATE_CALLBACK_BOOL(callback))
{ _holdCallback = callback; }
/**
* Returns hold's state.
*
* @returns ON (true) / OFF (false)
*/
inline bool getHoldState() const
{ return _holdState; }
/**
* Publishes given target temperature.
*
* @param targetTemperature
* @returns Returns true if MQTT message has been published successfully.
*/
bool setTargetTemperature(double targetTemperature);
/**
* Registers callback that will be called each time the target temperature changes
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onTargetTemperatureChanged(HAHVAC_STATE_CALLBACK_DOUBLE(callback))
{ _targetTempCallback = callback; }
/**
* Returns target temperature.
*
* @returns Target temperature or __DBL_MAX__ if temperature is not set.
*/
inline double getTargetTemperature() const
{ return _targetTemperature; }
/**
* Publishes current temperature.
*
* @param temperature
* @returns Returns true if MQTT message has been published successfully.
*/
bool setCurrentTemperature(double temperature);
/**
* Return temperature that was previously set by `setCurrentTemperature` method.
*
* @returns Temperature or __DBL_MAX__ if temperature is not set.
*/
inline double getCurrentTemperature() const
{ return _currentTemperature; }
/**
* Publishes working mode of the HVAC.
*
* @param mode
* @returns Returns true if MQTT message has been published successfully.
*/
bool setMode(Mode mode);
/**
* Same as above but input is the string representation of the mode.
*
* @param mode
* @returns Returns true if MQTT message has been published successfully.
*/
bool setModeFromStr(const char* mode);
/**
* Registers callback that will be called each time the mode changes.
* Please note that it's not possible to register multiple callbacks.
*
* @param callback
*/
inline void onModeChanged(HAHVAC_STATE_CALLBACK_MODE(callback))
{ _modeChangedCallback = callback; }
/**
* Returns HVAC's mode.
*
* @returns It may be UnknownMode if it's not set.
*/
inline Mode getMode() const
{ return _currentMode; }
/**
* Sets name that wil be displayed in the Home Assistant panel.
*
* @param name
*/
inline void setName(const char* name)
{ _label = name; } // it needs to be called "label" as "_name" is already in use
/**
* Sets the list of supported modes. By default the list contains all available modes.
* You can merge multiple modes as following: `setModes(HAHVAC::OffMode | HAHVAC::CoolMode)`
*
* @param modes
*/
inline void setModes(uint8_t modes)
{ _modes = modes; }
/**
* Sets `retain` flag for commands published by Home Assistant.
* By default it's set to false.
*
* @param retain
*/
inline void setRetain(bool retain)
{ _retain = retain; }
/**
* Sets the minimum temperature that user will be able to select in Home Assistant panel.
*
* @param minTemp
*/
bool setMinTemp(double minTemp);
/**
* Sets the maximum temperature that user will be able to select in Home Assistant panel.
*
* @param minTemp
*/
bool setMaxTemp(double maxTemp);
/**
* Sets the step of the temperature's picker in the Home Assistant panel.
*
* @param tempStep
*/
bool setTempStep(double tempStep);
private:
bool publishAction(Action action);
bool publishAuxHeatingState(bool state);
bool publishAwayState(bool state);
bool publishHoldState(bool state);
bool publishCurrentTemperature(double temperature);
bool publishTargetTemperature(double temperature);
bool publishMode(Mode mode);
void subscribeTopics();
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
uint16_t calculateModesLength() const;
bool writeSerializedData(const char* serializedDevice) const override;
const char* _uniqueId;
const uint8_t _features;
TemperatureUnit _temperatureUnit;
Action _action;
HAHVAC_STATE_CALLBACK_BOOL(_auxHeatingCallback);
bool _auxHeatingState;
HAHVAC_STATE_CALLBACK_BOOL(_awayCallback);
bool _awayState;
HAHVAC_STATE_CALLBACK_BOOL(_holdCallback);
bool _holdState;
double _currentTemperature;
double _minTemp;
double _maxTemp;
double _tempStep;
HAHVAC_STATE_CALLBACK_DOUBLE(_targetTempCallback);
double _targetTemperature;
const char* _label;
uint8_t _modes;
HAHVAC_STATE_CALLBACK_MODE(_modeChangedCallback);
Mode _currentMode;
bool _retain;
};
#endif
#endif
+89 -239
View File
@@ -3,138 +3,90 @@
#endif
#include "HASensor.h"
#include "../ArduinoHADefines.h"
#ifdef ARDUINOHA_SENSOR
#include "../HAMqtt.h"
#include "../HADevice.h"
#include "../HAUtils.h"
template <typename T>
HASensor<T>::HASensor(
const char* name,
T initialValue,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt, "sensor", name),
HASensor::HASensor(const char* name) :
BaseDeviceType("sensor", name),
_class(nullptr),
_units(nullptr),
_valueType(HAUtils::determineValueType<T>()),
_currentValue(initialValue)
_icon(nullptr)
{
}
template <typename T>
HASensor<T>::HASensor(
const char* name,
const char* deviceClass,
T initialValue,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt, "sensor", name),
_class(deviceClass),
_units(nullptr),
_valueType(HAUtils::determineValueType<T>()),
_currentValue(initialValue)
HASensor::HASensor(const char* name, HAMqtt& mqtt) :
HASensor(name)
{
(void)mqtt;
}
template <typename T>
void HASensor<T>::onMqttConnected()
void HASensor::onMqttConnected()
{
if (strlen(name()) == 0 ||
_valueType == HAUtils::ValueTypeUnknown) {
if (strlen(name()) == 0) {
return;
}
publishConfig();
publishValue(_currentValue);
publishAvailability();
}
template <typename T>
bool HASensor<T>::setValue(T value)
bool HASensor::setValue(const char* value)
{
if (strlen(name()) == 0 ||
_valueType == HAUtils::ValueTypeUnknown) {
return publishValue(value);
}
bool HASensor::setValue(uint32_t value)
{
uint8_t digitsNb = floor(log10(value)) + 1;
char str[digitsNb + 1]; // + null terminator
memset(str, 0, sizeof(str));
itoa(value, str, 10);
return publishValue(str);
}
bool HASensor::setValue(int32_t value)
{
uint8_t digitsNb = floor(log10(value)) + 1;
char str[digitsNb + 2]; // + null terminator and minus sign
memset(str, 0, sizeof(str));
itoa(value, str, 10);
return publishValue(str);
}
bool HASensor::setValue(double value, uint8_t precision)
{
uint8_t digitsNb = floor(log10(floor(value))) + 1;
char str[digitsNb + 3 + precision]; // null terminator, dot, minus sign
dtostrf(value, 0, precision, str);
return publishValue(str);
}
bool HASensor::setValue(float value, uint8_t precision)
{
uint8_t digitsNb = floor(log10(floor(value))) + 1;
char str[digitsNb + 3 + precision]; // null terminator, dot, minus sign
dtostrf(value, 0, precision, str);
return publishValue(str);
}
bool HASensor::publishValue(const char* value)
{
if (strlen(name()) == 0 || value == nullptr) {
return false;
}
if (_currentValue == value) {
return true;
}
if (publishValue(value)) {
_currentValue = value;
return true;
}
return false;
}
template <typename T>
void HASensor<T>::publishConfig()
{
if (_valueType == HAUtils::ValueTypeUnknown) {
return;
}
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return;
}
const uint16_t& deviceLength = device->calculateSerializedLength();
if (deviceLength == 0) {
return;
}
char serializedDevice[deviceLength];
if (device->serialize(serializedDevice) == 0) {
return;
}
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
return;
}
char topic[topicLength];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
template <typename T>
bool HASensor<T>::publishValue(T value)
{
if (strlen(name()) == 0 ||
_valueType == HAUtils::ValueTypeUnknown) {
if (!mqtt()->isConnected()) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
@@ -145,7 +97,6 @@ bool HASensor<T>::publishValue(T value)
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
@@ -156,26 +107,14 @@ bool HASensor<T>::publishValue(T value)
return false;
}
const uint16_t& valueLength = calculateValueLength();
if (valueLength == 0) {
return false;
}
char valueStr[valueLength];
if (!valueToStr(valueStr, value)) {
return false;
}
return mqtt()->publish(topic, valueStr, true);
return mqtt()->publish(topic, value, true);
}
template <typename T>
uint16_t HASensor<T>::calculateSerializedLength(
uint16_t HASensor::calculateSerializedLength(
const char* serializedDevice
) const
{
if (serializedDevice == nullptr ||
_valueType == HAUtils::ValueTypeUnknown) {
if (serializedDevice == nullptr) {
return 0;
}
@@ -187,20 +126,12 @@ uint16_t HASensor<T>::calculateSerializedLength(
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(name());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
if (isAvailabilityConfigured()) {
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(
mqtt(),
componentName(),
name()
);
}
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic,
@@ -227,142 +158,61 @@ uint16_t HASensor<T>::calculateSerializedLength(
size += strlen(_units) + 18; // 18 - length of the JSON decorators for this field
}
// icon
if (_icon != nullptr) {
// Field format: ,"ic":"[ICON]"
size += strlen(_icon) + 8; // 8 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
template <typename T>
bool HASensor<T>::writeSerializedData(const char* serializedDevice) const
bool HASensor::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr ||
_valueType == HAUtils::ValueTypeUnknown) {
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
DeviceTypeSerializer::mqttWriteBeginningJson();
// state topic
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
return false;
}
static const char Prefix[] PROGMEM = {"\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// device class
if (_class != nullptr) {
static const char Prefix[] PROGMEM = {",\"dev_cla\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _class);
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _class);
}
// units of measurement
if (_units != nullptr) {
static const char Prefix[] PROGMEM = {",\"unit_of_meas\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _units);
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _units);
}
DeviceTypeSerializer::mqttWriteNameField(mqtt(), name());
DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name());
if (isAvailabilityConfigured()) {
DeviceTypeSerializer::mqttWriteAvailabilityField(
mqtt(),
componentName(),
name()
// icon
if (_icon != nullptr) {
static const char Prefix[] PROGMEM = {",\"ic\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
_icon
);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
DeviceTypeSerializer::mqttWriteNameField(name());
DeviceTypeSerializer::mqttWriteUniqueIdField(name());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
template <typename T>
uint16_t HASensor<T>::calculateValueLength() const
{
if (_valueType == HAUtils::ValueTypeUnknown) {
return 0;
}
uint16_t size = 0;
switch (_valueType) {
case HAUtils::ValueTypeUint8:
size = 3; // from 0 to 255
break;
case HAUtils::ValueTypeUint16:
size = 5; // from 0 to 65535
break;
case HAUtils::ValueTypeUint32:
size = 10; // from 0 to 4294967295
break;
case HAUtils::ValueTypeInt8:
size = 4; // from -128 to 127
break;
case HAUtils::ValueTypeInt16:
size = 6; // from -32768 to 32767
break;
case HAUtils::ValueTypeInt32:
size = 11; // from -2147483648 to 2147483647
break;
case HAUtils::ValueTypeDouble:
case HAUtils::ValueTypeFloat:
// 3 digits per byte + dot separator + 2 precision digits
return (HAUtils::getValueTypeLength(_valueType) * 3) + 3;
}
return (size > 0 ? (size + 1) : 0);
}
template <typename T>
bool HASensor<T>::valueToStr(char* dst, T value) const
{
if (_valueType == HAUtils::ValueTypeUnknown) {
return false;
}
switch (_valueType) {
case HAUtils::ValueTypeUint8:
case HAUtils::ValueTypeUint16:
case HAUtils::ValueTypeUint32:
case HAUtils::ValueTypeInt8:
case HAUtils::ValueTypeInt16:
case HAUtils::ValueTypeInt32:
itoa(value, dst, 10);
break;
case HAUtils::ValueTypeDouble:
case HAUtils::ValueTypeFloat:
dtostrf(value, 0, 2, dst);
break;
}
return true;
}
#endif
+43 -37
View File
@@ -2,9 +2,9 @@
#define AHA_HASENSOR_H
#include "BaseDeviceType.h"
#include "../HAUtils.h"
template <typename T>
#ifdef ARDUINOHA_SENSOR
class HASensor : public BaseDeviceType
{
public:
@@ -12,30 +12,14 @@ public:
* Initializes binary sensor.
*
* @param name Name of the sensor. Recommendes characters: [a-z0-9\-_]
* @param initialValue Initial value of the sensor.
It will be published right after "config" message in order to update HA state.
*/
HASensor(
const char* name,
T initialValue,
HAMqtt& mqtt
const char* name
);
/**
* Initializes binary sensor with the specified class.
* You can find list of available values here: https://www.home-assistant.io/integrations/binary_sensor/#device-class
*
* @param name Name of the sensor. Recommendes characters: [a-z0-9\-_]
* @param deviceClass Name of the class (lower case).
* @param initialValue Initial value of the sensor.
It will be published right after "config" message in order to update HA state.
*/
HASensor(
const char* name,
const char* deviceClass,
T initialValue,
HAMqtt& mqtt
);
); // legacy constructor
/**
* Publishes configuration of the sensor to the MQTT.
@@ -43,21 +27,38 @@ public:
virtual void onMqttConnected() override;
/**
* Changes state of the sensor and publishes MQTT message.
* Please note that if a new value is the same as previous one,
* the MQTT message won't be published.
* Publishes new value of the sensor.
* Please note that connection to MQTT broker must be acquired.
* Otherwise method will return false.
*
* @param state New state of the sensor.
* @param state Value to publish.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setValue(T value);
bool setValue(const char* value);
bool setValue(uint32_t value);
bool setValue(int32_t value);
bool setValue(double value, uint8_t precision = 2);
bool setValue(float value, uint8_t precision = 2);
inline bool setValue(uint8_t value)
{ return setValue(static_cast<uint32_t>(value)); }
inline bool setValue(uint16_t value)
{ return setValue(static_cast<uint32_t>(value)); }
inline bool setValue(int8_t value)
{ return setValue(static_cast<int32_t>(value)); }
inline bool setValue(int16_t value)
{ return setValue(static_cast<int32_t>(value)); }
/**
* Returns last known state of the sensor.
* If setState method wasn't called the initial value will be returned.
* The type/class of the sensor to set the icon in the frontend.
*
* @param className https://www.home-assistant.io/integrations/sensor/#device-class
*/
inline T getValue() const
{ return _currentValue; }
inline void setDeviceClass(const char* className)
{ _class = className; }
/**
* Defines the units of measurement of the sensor, if any.
@@ -67,18 +68,23 @@ public:
inline void setUnitOfMeasurement(const char* units)
{ _units = units; }
/**
* Sets icon of the sensor, e.g. `mdi:home`.
*
* @param icon Material Design Icon name with mdi: prefix.
*/
inline void setIcon(const char* icon)
{ _icon = icon; }
private:
void publishConfig();
bool publishValue(T value);
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedData(const char* serializedDevice) const;
uint16_t calculateValueLength() const;
bool valueToStr(char* dst, T value) const;
bool publishValue(const char* value);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
const char* _class;
const char* _units;
HAUtils::ValueType _valueType;
T _currentValue;
const char* _icon;
};
#endif
#endif
+77 -213
View File
@@ -1,17 +1,30 @@
#include "HASwitch.h"
#ifdef ARDUINOHA_SWITCH
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
#include "../HAUtils.h"
HASwitch::HASwitch(const char* name, bool initialState, HAMqtt& mqtt) :
BaseDeviceType(mqtt, "switch", name),
HASwitch::HASwitch(const char* name, bool initialState) :
BaseDeviceType("switch", name),
_stateCallback(nullptr),
_currentState(initialState)
_currentState(initialState),
_icon(nullptr),
_retain(false)
{
}
HASwitch::HASwitch(
const char* name,
bool initialState,
HAMqtt& mqtt
) :
HASwitch(name, initialState)
{
(void)mqtt;
}
void HASwitch::onMqttConnected()
{
if (strlen(name()) == 0) {
@@ -19,9 +32,16 @@ void HASwitch::onMqttConnected()
}
publishConfig();
publishState(_currentState);
subscribeCommandTopic();
publishAvailability();
if (!_retain) {
publishState(_currentState);
}
DeviceTypeSerializer::mqttSubscribeTopic(
this,
DeviceTypeSerializer::CommandTopic
);
}
void HASwitch::onMqttMessage(
@@ -30,170 +50,50 @@ void HASwitch::onMqttMessage(
const uint16_t& length
)
{
if (strlen(name()) == 0) {
return;
}
(void)payload;
static const char Slash[] PROGMEM = {"/"};
// name + cmd topic + two slashes + null terminator
uint8_t suffixLength = strlen(name()) + strlen(DeviceTypeSerializer::CommandTopic) + 3;
char suffix[suffixLength];
strcpy_P(suffix, Slash);
strcat(suffix, name());
strcat_P(suffix, Slash);
strcat(suffix, DeviceTypeSerializer::CommandTopic);
if (HAUtils::endsWith(topic, suffix)) {
bool onState = (length == strlen(DeviceTypeSerializer::StateOn));
setState(onState);
if (isMyTopic(topic, DeviceTypeSerializer::CommandTopic)) {
bool state = (length == strlen(DeviceTypeSerializer::StateOn));
setState(state, true);
}
}
bool HASwitch::setState(bool state)
bool HASwitch::setState(bool state, bool force)
{
if (_currentState == state) {
if (!force && _currentState == state) {
return true;
}
if (strlen(name()) == 0) {
return false;
}
if (publishState(state)) {
_currentState = state;
triggerCallback(_currentState);
if (_stateCallback) {
_stateCallback(state, this);
}
return true;
}
return false;
}
void HASwitch::triggerCallback(bool state)
{
if (_stateCallback == nullptr) {
return;
}
_stateCallback(state, this);
}
void HASwitch::publishConfig()
{
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return;
}
const uint16_t& deviceLength = device->calculateSerializedLength();
if (deviceLength == 0) {
return;
}
char serializedDevice[deviceLength];
if (device->serialize(serializedDevice) == 0) {
return;
}
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
return;
}
char topic[topicLength];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
bool HASwitch::publishState(bool state)
{
if (strlen(name()) == 0) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
return false;
}
return mqtt()->publish(
topic,
return DeviceTypeSerializer::mqttPublishMessage(
this,
DeviceTypeSerializer::StateTopic,
(
state ?
DeviceTypeSerializer::StateOn :
DeviceTypeSerializer::StateOff
),
true
)
);
}
void HASwitch::subscribeCommandTopic()
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::CommandTopic
);
if (topicSize == 0) {
return;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::CommandTopic
);
if (strlen(topic) == 0) {
return;
}
mqtt()->subscribe(topic);
}
uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
@@ -208,21 +108,14 @@ uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(name());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
if (isAvailabilityConfigured()) {
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(
mqtt(),
componentName(),
name()
);
}
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
size += DeviceTypeSerializer::calculateRetainFieldSize(_retain);
// cmd topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::CommandTopic,
@@ -240,7 +133,6 @@ uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
// state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic,
@@ -255,6 +147,12 @@ uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
size += topicLength + 12; // 12 - length of the JSON decorators for this field
}
// icon
if (_icon != nullptr) {
// Field format: ,"ic":"[ICON]"
size += strlen(_icon) + 8; // 8 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
@@ -264,79 +162,45 @@ bool HASwitch::writeSerializedData(const char* serializedDevice) const
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
DeviceTypeSerializer::mqttWriteBeginningJson();
// command topic
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::CommandTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::CommandTopic
);
if (strlen(topic) == 0) {
return false;
}
static const char Prefix[] PROGMEM = {"\"cmd_t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::CommandTopic
);
}
// state topic
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
return false;
}
static const char Prefix[] PROGMEM = {",\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
}
DeviceTypeSerializer::mqttWriteNameField(mqtt(), name());
DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name());
if (isAvailabilityConfigured()) {
DeviceTypeSerializer::mqttWriteAvailabilityField(
mqtt(),
componentName(),
name()
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
// icon
if (_icon != nullptr) {
static const char Prefix[] PROGMEM = {",\"ic\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
_icon
);
}
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteNameField(name());
DeviceTypeSerializer::mqttWriteUniqueIdField(name());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
+36 -12
View File
@@ -3,7 +3,9 @@
#include "BaseDeviceType.h"
#define HASWITCH_CALLBACK void (*callback)(bool, HASwitch*)
#ifdef ARDUINOHA_SWITCH
#define HASWITCH_CALLBACK(name) void (*name)(bool, HASwitch*)
class HASwitch : public BaseDeviceType
{
@@ -15,11 +17,15 @@ public:
* @param initialState Initial state of the switch.
It will be published right after "config" message in order to update HA state.
*/
HASwitch(
const char* name,
bool initialState
);
HASwitch(
const char* name,
bool initialState,
HAMqtt& mqtt
);
); // legacy constructor
/**
* Publishes configuration of the sensor to the MQTT.
@@ -48,9 +54,10 @@ public:
* the MQTT message won't be published.
*
* @param state New state of the switch.
* @param force Forces to update state without comparing it to previous known state.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setState(bool state);
bool setState(bool state, bool force = false);
/**
* Alias for setState(true).
@@ -77,19 +84,36 @@ public:
*
* @param callback
*/
inline void onStateChanged(HASWITCH_CALLBACK)
inline void onStateChanged(HASWITCH_CALLBACK(callback))
{ _stateCallback = callback; }
private:
void triggerCallback(bool state);
void publishConfig();
bool publishState(bool state);
void subscribeCommandTopic();
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedData(const char* serializedDevice) const;
/**
* Sets icon of the switch, e.g. `mdi:home`.
*
* @param icon Material Design Icon name with mdi: prefix.
*/
inline void setIcon(const char* icon)
{ _icon = icon; }
void (*_stateCallback)(bool, HASwitch*);
/**
* Sets `retain` flag for commands published by Home Assistant.
* By default it's set to false.
*
* @param retain
*/
inline void setRetain(bool retain)
{ _retain = retain; }
private:
bool publishState(bool state);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
HASWITCH_CALLBACK(_stateCallback);
bool _currentState;
const char* _icon;
bool _retain;
};
#endif
#endif
+21 -74
View File
@@ -1,15 +1,22 @@
#include "HATagScanner.h"
#ifdef ARDUINOHA_TAG_SCANNER
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
#include "../HAUtils.h"
HATagScanner::HATagScanner(const char* name, HAMqtt& mqtt) :
BaseDeviceType(mqtt, "tag", name)
HATagScanner::HATagScanner(const char* name) :
BaseDeviceType("tag", name)
{
}
HATagScanner::HATagScanner(const char* name, HAMqtt& mqtt) :
HATagScanner(name)
{
(void)mqtt;
}
void HATagScanner::onMqttConnected()
{
if (strlen(name()) == 0) {
@@ -21,12 +28,11 @@ void HATagScanner::onMqttConnected()
bool HATagScanner::tagScanned(const char* tag)
{
if (tag == nullptr || strlen(tag) == 0) {
if (tag == nullptr || strlen(tag) == 0 || strlen(name()) == 0) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::EventTopic
@@ -37,7 +43,6 @@ bool HATagScanner::tagScanned(const char* tag)
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
@@ -51,54 +56,6 @@ bool HATagScanner::tagScanned(const char* tag)
return mqtt()->publish(topic, tag);
}
void HATagScanner::publishConfig()
{
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return;
}
const uint16_t& deviceLength = device->calculateSerializedLength();
if (deviceLength == 0) {
return;
}
char serializedDevice[deviceLength];
if (device->serialize(serializedDevice) == 0) {
return;
}
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
return;
}
char topic[topicLength];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
uint16_t HATagScanner::calculateSerializedLength(
const char* serializedDevice
) const
@@ -114,7 +71,6 @@ uint16_t HATagScanner::calculateSerializedLength(
// event topic
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::EventTopic,
@@ -134,31 +90,22 @@ bool HATagScanner::writeSerializedData(const char* serializedDevice) const
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
DeviceTypeSerializer::mqttWriteBeginningJson();
// topic
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::EventTopic
);
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::EventTopic
);
static const char Prefix[] PROGMEM = {"\"t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::EventTopic
);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
+8 -5
View File
@@ -3,6 +3,8 @@
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_TAG_SCANNER
class HATagScanner : public BaseDeviceType
{
public:
@@ -11,7 +13,8 @@ public:
*
* @param name Name of the scanner. Recommendes characters: [a-z0-9\-_]
*/
HATagScanner(const char* name, HAMqtt& mqtt);
HATagScanner(const char* name);
HATagScanner(const char* name, HAMqtt& mqtt); // legacy constructor
/**
* Publishes configuration of the sensor to the MQTT.
@@ -21,7 +24,7 @@ public:
/**
* Tag scanner doesn't support availability. Nothing to do here.
*/
virtual void setAvailability(bool online) override { }
virtual void setAvailability(bool online) override { (void)online; }
/**
* Sends "tag scanned" event to the MQTT (Home Assistant).
@@ -32,9 +35,9 @@ public:
bool tagScanned(const char* tag);
private:
void publishConfig();
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedData(const char* serializedDevice) const;
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
};
#endif
#endif
+30 -16
View File
@@ -1,16 +1,24 @@
#include "HATriggers.h"
#ifdef ARDUINOHA_TRIGGERS
#include "../ArduinoHADefines.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
HATriggers::HATriggers(HAMqtt& mqtt) :
BaseDeviceType(mqtt, "device_automation", nullptr),
HATriggers::HATriggers() :
BaseDeviceType("device_automation", nullptr),
_triggers(nullptr),
_triggersNb(0)
{
}
HATriggers::HATriggers(HAMqtt& mqtt) :
HATriggers()
{
(void)mqtt;
}
HATriggers::~HATriggers()
{
if (_triggers != nullptr) {
@@ -87,6 +95,14 @@ bool HATriggers::trigger(const char* type, const char* subtype)
return false;
}
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Triggering HATrigger: "));
Serial.print(type);
Serial.print(F(" "));
Serial.print(subtype);
Serial.println();
#endif
return mqtt()->publish(topic, "");
}
@@ -122,7 +138,6 @@ void HATriggers::publishConfig()
trigger,
serializedDevice
);
if (topicLength == 0 || dataLength == 0) {
continue;
}
@@ -153,9 +168,8 @@ uint16_t HATriggers::calculateTopicLength(
bool includeNullTerminator
) const
{
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 1; // + underscore
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // + underscore and slash
return DeviceTypeSerializer::calculateTopicLength(
mqtt(),
component,
nullptr,
suffix,
@@ -179,7 +193,6 @@ uint16_t HATriggers::generateTopic(
strcat(objectId, trigger->type);
return DeviceTypeSerializer::generateTopic(
mqtt(),
output,
component,
objectId,
@@ -246,7 +259,7 @@ bool HATriggers::writeSerializedTrigger(
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
DeviceTypeSerializer::mqttWriteBeginningJson();
// automation type
{
@@ -273,28 +286,29 @@ bool HATriggers::writeSerializedTrigger(
DeviceTypeSerializer::EventTopic
);
if (strlen(topic) == 0) {
return false;
}
static const char Prefix[] PROGMEM = {",\"t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
topic
);
}
// type
{
static const char Prefix[] PROGMEM = {",\"type\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, trigger->type);
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, trigger->type);
}
// subtype
{
static const char Prefix[] PROGMEM = {",\"stype\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, trigger->subtype);
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, trigger->subtype);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
#endif
+16 -4
View File
@@ -3,6 +3,8 @@
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_TRIGGERS
struct HATrigger {
const char* type;
const char* subtype;
@@ -11,7 +13,8 @@ struct HATrigger {
class HATriggers : public BaseDeviceType
{
public:
HATriggers(HAMqtt& mqtt);
HATriggers();
HATriggers(HAMqtt& mqtt); // legacy constructor
virtual ~HATriggers();
virtual void onMqttConnected() override;
@@ -19,12 +22,22 @@ public:
/**
* Triggers dont't support availability. Nothing to do here.
*/
virtual void setAvailability(bool online) override { }
virtual void setAvailability(bool online) override { (void)online; }
bool add(const char* type, const char* subtype);
bool trigger(const char* type, const char* subtype);
protected:
void publishConfig() override;
uint16_t calculateSerializedLength(
const char* serializedDevice
) const override { (void)serializedDevice; return 0; }
bool writeSerializedData(
const char* serializedDevice
) const override { (void)serializedDevice; return false; }
uint16_t calculateTopicLength(
const char* component,
const HATrigger *trigger,
@@ -40,8 +53,6 @@ protected:
) const;
private:
void publishConfig();
uint16_t calculateSerializedLength(
const HATrigger* trigger,
const char* serializedDevice
@@ -57,3 +68,4 @@ private:
};
#endif
#endif