Add support for "availability" for sensors and switch (#4)

* hide onMqttConnected and onMqttMessage methods in BaseDeviceType class

* bump up version

* replace HAComponentName with initialization inside constructor

* add missing documentation to begin() method

* add support for availability in BaseDeviceType

* add support for availability in HABinarySensor, HASensor and HASwitch

* move serialization to separate class

* wip: DeviceTypeSerializer

* update device types

* refactored all devices types to use common serializer

* update examples

* update availability example README.md

* bugs fixes

* fix build for NodeMCU

* cleanup
This commit is contained in:
Dawid Chyrzyński
2021-02-06 20:41:10 +01:00
committed by GitHub
parent 35a7ea39be
commit 4135d0991f
24 changed files with 1094 additions and 594 deletions
+31
View File
@@ -0,0 +1,31 @@
# Home Assistant availability example
This example shows how to use "availability" feature of the Home Assistant.
It's supported only by following device types:
* Binary sensor
* Sensor
* Switch
## Initialization and usage
Availability feature is turned off by default. In order to turn it on for the specific
sensor/switch you need to set initial state of the unit by calling `setAvailability` method.
Example:
```cpp
...
HABinarySensor sensor("input", "door", true, mqtt);
void setup() {
// ...
// method must be called before `mqtt.begin(...)`
sensor.setAvailability(false); // offline
mqtt.begin(BROKER_ADDR);
}
...
```
After availability is set for the very first time you can call `setAvailability` in any time.
Each time the method is called, the device will publish MQTT message to the broker.
+56
View File
@@ -0,0 +1,56 @@
#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, mqtt);
void setup() {
pinMode(INPUT_PIN, INPUT_PULLUP);
lastInputState = digitalRead(INPUT_PIN);
// you don't need to verify return status
Ethernet.begin(mac);
// turn on "availability" feature
sensor.setAvailability(false);
lastReadAt = millis();
lastAvailabilityToggleAt = millis();
// set device's details (optional)
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
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) {
sensor.setAvailability(!sensor.isOnline());
lastAvailabilityToggleAt = millis();
}
}
+2 -1
View File
@@ -14,7 +14,8 @@ 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)
HABinarySensor sensor("input", "door", false, mqtt);
// "true" is initial state of the sensor. In this example it's "true" as we use pullup resistor
HABinarySensor sensor("input", "door", true, mqtt);
void setup() {
pinMode(INPUT_PIN, INPUT_PULLUP);
+5 -2
View File
@@ -6,9 +6,8 @@
#define WIFI_SSID "MyNetwork"
#define WIFI_PASSWORD "MyPassword"
byte mac[6];
WiFiClient client;
HADevice device(mac, sizeof(mac));
HADevice device;
HAMqtt mqtt(client, device);
HASwitch led("led", false, mqtt); // you can use custom name in place of "led"
@@ -21,7 +20,11 @@ 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));
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
+3 -1
View File
@@ -22,7 +22,9 @@ HAMqtt mqtt(client, device);
* - double
* - float
*/
HASensor<double> temp("temp", 0, mqtt); // you can use custom name in place of "temp"
// you can use custom name in place of "temp"
// "0" is initial value of the sensor
HASensor<double> temp("temp", 0, mqtt);
void setup() {
// you don't need to verify return status
+1 -1
View File
@@ -1,5 +1,5 @@
name=home-assistant-integration
version=1.0.1
version=1.1.0
author=Dawid Chyrzynski <dawid.chyrzynski@gmail.com>
maintainer=Dawid Chyrzynski <dawid.chyrzynski@gmail.com>
sentence=Home Assistant MQTT integration for Arduino
+17
View File
@@ -9,6 +9,13 @@
_name(nullptr), \
_softwareVersion(nullptr)
HADevice::HADevice() :
_uniqueId(nullptr),
HADEVICE_INIT
{
}
HADevice::HADevice(const char* uniqueId) :
_uniqueId(uniqueId),
HADEVICE_INIT
@@ -23,6 +30,16 @@ HADevice::HADevice(const byte* uniqueId, const uint16_t& length) :
}
bool HADevice::setUniqueId(const byte* uniqueId, const uint16_t& length)
{
if (_uniqueId != nullptr) {
return false;
}
_uniqueId = HAUtils::byteArrayToStr(uniqueId, length);
return true;
}
uint16_t HADevice::calculateSerializedLength() const
{
uint16_t size =
+2
View File
@@ -6,6 +6,7 @@
class HADevice
{
public:
HADevice();
HADevice(const char* uniqueId);
HADevice(const byte* uniqueId, const uint16_t& length);
@@ -24,6 +25,7 @@ public:
inline void setSoftwareVersion(const char* softwareVersion)
{ _softwareVersion = softwareVersion; }
bool setUniqueId(const byte* uniqueId, const uint16_t& length);
uint16_t calculateSerializedLength() const;
uint16_t serialize(char* dst) const;
+11 -5
View File
@@ -34,14 +34,12 @@ void onMessageReceived(char* topic, uint8_t* payload, uint16_t length)
}
HAMqtt::HAMqtt(Client& netClient, HADevice& device) :
_clientId(device.getUniqueId()),
HAMQTT_INIT
{
instance = this;
}
HAMqtt::HAMqtt(const char* clientId, Client& netClient, HADevice& device) :
_clientId(clientId),
HAMQTT_INIT
{
instance = this;
@@ -63,6 +61,14 @@ bool HAMqtt::begin(
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"));
@@ -213,14 +219,14 @@ void HAMqtt::connectToServer()
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Connecting to the MQTT broker... Client ID: "));
Serial.print(_clientId);
Serial.print(_device.getUniqueId());
Serial.println();
#endif
if (_username == nullptr || _password == nullptr) {
_mqtt->connect(_clientId);
_mqtt->connect(_device.getUniqueId());
} else {
_mqtt->connect(_clientId, _username, _password);
_mqtt->connect(_device.getUniqueId(), _username, _password);
}
if (isConnected()) {
+10 -1
View File
@@ -54,6 +54,16 @@ public:
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,
@@ -128,7 +138,6 @@ private:
*/
void onConnected();
const char* _clientId;
Client& _netClient;
HADevice& _device;
bool _hasDevice;
+50 -58
View File
@@ -2,11 +2,15 @@
#include "../HAMqtt.h"
#include "../HADevice.h"
const char* BaseDeviceType::ConfigTopic = "config"; // todo: move to progmem
const char* BaseDeviceType::EventTopic = "event";
BaseDeviceType::BaseDeviceType(HAMqtt& mqtt) :
_mqtt(mqtt)
BaseDeviceType::BaseDeviceType(
HAMqtt& mqtt,
const char* componentName,
const char* name
) :
_mqtt(mqtt),
_componentName(componentName),
_name(name),
_availability(AvailabilityDefault)
{
_mqtt.addDeviceType(this);
}
@@ -16,63 +20,51 @@ BaseDeviceType::~BaseDeviceType()
}
uint16_t BaseDeviceType::calculateTopicLength(
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator
) const
void BaseDeviceType::setAvailability(bool online)
{
// [discovery prefix]/[namespace]/[device id - optional]/subtype_type/[suffix]
const char* prefix = _mqtt.getDiscoveryPrefix();
if (prefix == nullptr) {
return 0;
}
uint16_t size =
strlen(prefix) + 1 + // with slash
strlen(component) + 1 + // with slash
strlen(suffix); // with null terminator
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) {
size += 1;
}
return size;
_availability = (online ? AvailabilityOnline : AvailabilityOffline);
publishAvailability();
}
uint16_t BaseDeviceType::generateTopic(
char* output,
const char* component,
const char* objectId,
const char* suffix
) const
void BaseDeviceType::publishAvailability()
{
static const char Slash[] PROGMEM = {"/"};
const char* prefix = _mqtt.getDiscoveryPrefix();
strcpy(output, prefix);
strcat_P(output, Slash);
strcat(output, component);
strcat_P(output, Slash);
if (_mqtt.getDevice() != nullptr) {
strcat(output, _mqtt.getDevice()->getUniqueId());
strcat_P(output, Slash);
if (_availability == AvailabilityDefault ||
!_mqtt.isConnected() ||
strlen(_name) == 0 ||
strlen(_componentName) == 0) {
return;
}
strcat(output, objectId);
strcat_P(output, Slash);
strcat(output, suffix);
return strlen(output) + 1; // size with null terminator
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
_componentName,
_name,
DeviceTypeSerializer::AvailabilityTopic
);
if (topicSize == 0) {
return;
}
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
_componentName,
_name,
DeviceTypeSerializer::AvailabilityTopic
);
if (strlen(topic) == 0) {
return;
}
mqtt()->publish(
topic,
(
_availability == AvailabilityOnline ?
DeviceTypeSerializer::Online :
DeviceTypeSerializer::Offline
),
true
);
}
+37 -20
View File
@@ -3,17 +3,38 @@
#include <stdint.h>
#include "DeviceTypeSerializer.h"
class HAMqtt;
class BaseDeviceType
{
public:
static const char* ConfigTopic;
static const char* EventTopic;
BaseDeviceType(HAMqtt& mqtt);
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; }
inline const char* componentName() const
{ return _componentName; }
inline bool isAvailabilityConfigured() const
{ return (_availability != AvailabilityDefault); }
virtual void onMqttConnected() = 0;
virtual void onMqttMessage(
const char* topic,
@@ -21,26 +42,22 @@ public:
const uint16_t& length
) { };
protected:
inline HAMqtt* mqtt() const
{ return &_mqtt; }
virtual void publishAvailability();
virtual uint16_t calculateTopicLength(
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator = true
) const final;
virtual uint16_t generateTopic(
char* output,
const char* component,
const char* objectId,
const char* suffix
) const final;
const char* const _componentName;
const char* const _name;
private:
enum Availability {
AvailabilityDefault = 0,
AvailabilityOnline,
AvailabilityOffline
};
HAMqtt& _mqtt;
Availability _availability;
friend class HAMqtt;
};
#endif
+266
View File
@@ -0,0 +1,266 @@
#include <Arduino.h>
#include "DeviceTypeSerializer.h"
#include "../HAMqtt.h"
#include "../HADevice.h"
static const char CharSlash[] PROGMEM = {"/"};
static const char CharUnderscore[] PROGMEM = {"_"};
static const char CharQuotation[] PROGMEM = {"\""};
const char* DeviceTypeSerializer::ConfigTopic = "config";
const char* DeviceTypeSerializer::EventTopic = "event";
const char* DeviceTypeSerializer::AvailabilityTopic = "avail";
const char* DeviceTypeSerializer::StateTopic = "state";
const char* DeviceTypeSerializer::CommandTopic = "cmd";
const char* DeviceTypeSerializer::Online = "online";
const char* DeviceTypeSerializer::Offline = "offline";
const char* DeviceTypeSerializer::StateOn = "ON";
const char* DeviceTypeSerializer::StateOff = "OFF";
uint16_t DeviceTypeSerializer::calculateTopicLength(
const HAMqtt* mqtt,
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator
)
{
const char* prefix = mqtt->getDiscoveryPrefix();
if (prefix == nullptr) {
return 0;
}
uint16_t size =
strlen(prefix) + 1 + // with slash
strlen(component) + 1 + // with slash
strlen(suffix);
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) {
size += 1;
}
return size;
}
uint16_t DeviceTypeSerializer::generateTopic(
const HAMqtt* mqtt,
char* output,
const char* component,
const char* objectId,
const char* suffix
)
{
const char* prefix = mqtt->getDiscoveryPrefix();
strcpy(output, prefix);
strcat_P(output, CharSlash);
strcat(output, component);
strcat_P(output, CharSlash);
if (mqtt->getDevice() != nullptr) {
strcat(output, mqtt->getDevice()->getUniqueId());
strcat_P(output, CharSlash);
}
strcat(output, objectId);
strcat_P(output, CharSlash);
strcat(output, suffix);
return strlen(output) + 1; // size with null terminator
}
uint16_t DeviceTypeSerializer::calculateBaseJsonDataSize()
{
return 2; // opening and closing brackets of the JSON data
}
uint16_t DeviceTypeSerializer::calculateNameFieldSize(const char* name)
{
if (name == nullptr) {
return 0;
}
// Field format: ,"name":"[NAME]"
return strlen(name) + 10; // 10 - length of the JSON decorators for this field
}
uint16_t DeviceTypeSerializer::calculateUniqueIdFieldSize(
const HADevice* device,
const char* name
)
{
if (device == nullptr || name == nullptr) {
return 0;
}
// Field format: ,"uniq_id":"[DEVICE ID]_[NAME]"
return (
strlen(device->getUniqueId()) +
strlen(name) +
14 // 14 - length of the JSON decorators for this field
);
}
uint16_t DeviceTypeSerializer::calculateAvailabilityFieldSize(
const HAMqtt* mqtt,
const char* componentName,
const char* name
)
{
if (mqtt == nullptr || componentName == nullptr || name == nullptr) {
return 0;
}
const uint16_t& availabilityTopicLength = calculateTopicLength(
mqtt,
componentName,
name,
AvailabilityTopic,
false
);
if (availabilityTopicLength == 0) {
return 0;
}
// Field format: ,"avty_t":"[TOPIC]"
return availabilityTopicLength + 12; // 12 - length of the JSON decorators for this field
}
uint16_t DeviceTypeSerializer::calculateDeviceFieldSize(
const char* serializedDevice
)
{
if (serializedDevice == nullptr) {
return 0;
}
// Field format: ,"dev":[DEVICE]
return strlen(serializedDevice) + 7; // 7 - length of the JSON decorators for this field
}
void DeviceTypeSerializer::mqttWriteBeginningJson(HAMqtt* mqtt)
{
if (mqtt == nullptr) {
return;
}
static const char Data[] PROGMEM = {"{"};
mqtt->writePayload_P(Data);
}
void DeviceTypeSerializer::mqttWriteEndJson(HAMqtt* mqtt)
{
if (mqtt == nullptr) {
return;
}
static const char Data[] PROGMEM = {"}"};
mqtt->writePayload_P(Data);
}
void DeviceTypeSerializer::mqttWriteConstCharField(
HAMqtt* mqtt,
const char* prefix,
const char* value
)
{
if (mqtt == nullptr || prefix == nullptr || value == nullptr) {
return;
}
mqtt->writePayload_P(prefix);
mqtt->writePayload(value, strlen(value));
mqtt->writePayload_P(CharQuotation);
}
void DeviceTypeSerializer::mqttWriteNameField(HAMqtt* mqtt, const char* name)
{
if (mqtt == nullptr || name == nullptr) {
return;
}
static const char Prefix[] PROGMEM = {",\"name\":\""};
mqttWriteConstCharField(mqtt, Prefix, name);
}
void DeviceTypeSerializer::mqttWriteUniqueIdField(
HAMqtt* mqtt,
const char* name
)
{
if (mqtt == nullptr || name == nullptr) {
return;
}
static const char Prefix[] PROGMEM = {",\"uniq_id\":\""};
uint8_t uniqueIdLength = strlen(name) + strlen(mqtt->getDevice()->getUniqueId()) + 2; // underscore and null temrinator
char uniqueId[uniqueIdLength];
strcpy(uniqueId, name);
strcat_P(uniqueId, CharUnderscore);
strcat(uniqueId, mqtt->getDevice()->getUniqueId());
mqttWriteConstCharField(mqtt, Prefix, uniqueId);
}
void DeviceTypeSerializer::mqttWriteAvailabilityField(
HAMqtt* mqtt,
const char* componentName,
const char* name
)
{
if (mqtt == nullptr || componentName == nullptr || name == nullptr) {
return;
}
const uint16_t& topicSize = calculateTopicLength(
mqtt,
componentName,
name,
AvailabilityTopic
);
if (topicSize == 0) {
return;
}
char availabilityTopic[topicSize];
generateTopic(
mqtt,
availabilityTopic,
componentName,
name,
AvailabilityTopic
);
if (strlen(availabilityTopic) == 0) {
return;
}
static const char Prefix[] PROGMEM = {",\"avty_t\":\""};
mqttWriteConstCharField(mqtt, Prefix, availabilityTopic);
}
void DeviceTypeSerializer::mqttWriteDeviceField(
HAMqtt* mqtt,
const char* serializedDevice
)
{
if (mqtt == nullptr || serializedDevice == nullptr) {
return;
}
static const char Data[] PROGMEM = {",\"dev\":"};
mqtt->writePayload_P(Data);
mqtt->writePayload(serializedDevice, strlen(serializedDevice));
}
+98
View File
@@ -0,0 +1,98 @@
#ifndef AHA_DEVICETYPESERIALIZER_H
#define AHA_DEVICETYPESERIALIZER_H
#include <stdint.h>
class HAMqtt;
class HADevice;
class DeviceTypeSerializer
{
public:
static const char* ConfigTopic;
static const char* EventTopic;
static const char* AvailabilityTopic;
static const char* StateTopic;
static const char* CommandTopic;
static const char* Online;
static const char* Offline;
static const char* StateOn;
static const char* StateOff;
/**
* Calculates length of the topic with given parameters.
* Topic format: [discovery prefix]/[component]/[objectId]/[suffix]
*
* @param component
* @param objectId
* @param suffix
* @param includeNullTerminator
*/
static uint16_t calculateTopicLength(
const HAMqtt* mqtt,
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator = true
);
/**
* 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]
*
* @param output
* @param component
* @param objectId
* @param suffix
* @param includeNullTerminator
*/
static uint16_t generateTopic(
const HAMqtt* mqtt,
char* output,
const char* component,
const char* objectId,
const char* suffix
);
static uint16_t calculateBaseJsonDataSize();
static uint16_t calculateNameFieldSize(
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
);
static uint16_t calculateDeviceFieldSize(
const char* serializedDevice
);
static void mqttWriteBeginningJson(HAMqtt* mqtt);
static void mqttWriteEndJson(HAMqtt* mqtt);
static void mqttWriteConstCharField(
HAMqtt* mqtt,
const char* prefix,
const char* value
);
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
);
static void mqttWriteDeviceField(
HAMqtt* mqtt,
const char* serializedDevice
);
};
#endif
+105 -110
View File
@@ -4,19 +4,12 @@
#include "../HADevice.h"
#include "../HAUtils.h"
// todo: move all variables to progmem
static const char* HAComponentName = "binary_sensor";
static const char* StateTopic = "state";
static const char* StateOn = "ON";
static const char* StateOff = "OFF";
HABinarySensor::HABinarySensor(
const char* name,
bool initialState,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt),
_name(name),
BaseDeviceType(mqtt, "binary_sensor", name),
_class(nullptr),
_currentState(initialState)
{
@@ -29,27 +22,22 @@ HABinarySensor::HABinarySensor(
bool initialState,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt),
_name(name),
BaseDeviceType(mqtt, "binary_sensor", name),
_class(deviceClass),
_currentState(initialState)
{
}
HABinarySensor::~HABinarySensor()
{
}
void HABinarySensor::onMqttConnected()
{
if (strlen(_name) == 0) {
if (strlen(name()) == 0) {
return;
}
publishConfig();
publishState(_currentState);
publishAvailability();
}
bool HABinarySensor::setState(bool state)
@@ -58,7 +46,7 @@ bool HABinarySensor::setState(bool state)
return true;
}
if (strlen(_name) == 0) {
if (strlen(name()) == 0) {
return false;
}
@@ -87,7 +75,12 @@ void HABinarySensor::publishConfig()
return;
}
const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic);
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
@@ -95,46 +88,62 @@ void HABinarySensor::publishConfig()
}
char topic[topicLength];
generateTopic(topic, HAComponentName, _name, ConfigTopic);
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedTrigger(serializedDevice);
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
bool HABinarySensor::publishState(bool state)
{
if (strlen(_name) == 0) {
if (strlen(name()) == 0) {
return false;
}
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
StateTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
HAComponentName,
_name,
StateTopic
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
return false;
}
return mqtt()->publish(topic, (state ? StateOn : StateOff), true);
return mqtt()->publish(
topic,
(
state ?
DeviceTypeSerializer::StateOn :
DeviceTypeSerializer::StateOff
),
true
);
}
uint16_t HABinarySensor::calculateSerializedLength(
@@ -145,122 +154,108 @@ uint16_t HABinarySensor::calculateSerializedLength(
return 0;
}
const uint16_t& stateTopicLength = calculateTopicLength(
HAComponentName,
_name,
StateTopic,
false
);
if (stateTopicLength == 0) {
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return 0;
}
uint16_t size =
2 + // opening and closing bracket (without null terminator)
stateTopicLength + 11 + // 11 - length of the JSON data for this field
strlen(_name) + 10; // 10 - length of the JSON data for this field
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
// unique ID
size += strlen(mqtt()->getDevice()->getUniqueId());
size += strlen(_name) + 14; // 14 - length of the JSON data for this field
if (isAvailabilityConfigured()) {
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(
mqtt(),
componentName(),
name()
);
}
// state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: "stat_t":"[TOPIC]"
size += topicLength + 11; // 11 - length of the JSON decorators for this field
}
// device class
if (_class != nullptr) {
size += strlen(_class) + 13; // 13 - length of the JSON data for this field
// Field format: ,"dev_cla":"[CLASS]"
size += strlen(_class) + 13; // 13 - length of the JSON decorators for this field
}
// device
size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field
return size;
return size; // exludes null terminator
}
bool HABinarySensor::writeSerializedTrigger(const char* serializedDevice) const
bool HABinarySensor::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
static const char QuotationSign[] PROGMEM = {"\""};
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
// state topic
{
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
StateTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char stateTopic[topicSize];
generateTopic(
stateTopic,
HAComponentName,
_name,
StateTopic
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(stateTopic) == 0) {
if (strlen(topic) == 0) {
return false;
}
static const char DataBefore[] PROGMEM = {"{\"stat_t\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(stateTopic, strlen(stateTopic));
mqtt()->writePayload_P(QuotationSign);
}
// name
{
static const char DataBefore[] PROGMEM = {",\"name\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(_name, strlen(_name));
mqtt()->writePayload_P(QuotationSign);
}
// unique ID
{
static const char Underscore[] PROGMEM = {"_"};
static const char DataBefore[] PROGMEM = {",\"uniq_id\":\""};
const HADevice* device = mqtt()->getDevice();
uint8_t uniqueIdLength = strlen(_name) + strlen(device->getUniqueId()) + 2; // underscore and null temrinator
char uniqueId[uniqueIdLength];
strcpy(uniqueId, _name);
strcat_P(uniqueId, Underscore);
strcat(uniqueId, device->getUniqueId());
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(uniqueId, strlen(uniqueId));
mqtt()->writePayload_P(QuotationSign);
}
// device
{
static const char Data[] PROGMEM = {",\"dev\":"};
mqtt()->writePayload_P(Data);
mqtt()->writePayload(serializedDevice, strlen(serializedDevice));
static const char Prefix[] PROGMEM = {"\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
}
// device class
if (_class != nullptr) {
static const char DataBefore[] PROGMEM = {",\"dev_cla\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(_class, strlen(_class));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {",\"dev_cla\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _class);
}
{
static const char Data[] PROGMEM = {"}"};
mqtt()->writePayload_P(Data);
DeviceTypeSerializer::mqttWriteNameField(mqtt(), name());
DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name());
if (isAvailabilityConfigured()) {
DeviceTypeSerializer::mqttWriteAvailabilityField(
mqtt(),
componentName(),
name()
);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
return true;
}
+1 -4
View File
@@ -35,8 +35,6 @@ public:
HAMqtt& mqtt
);
virtual ~HABinarySensor();
/**
* Publishes configuration of the sensor to the MQTT.
*/
@@ -63,9 +61,8 @@ private:
void publishConfig();
bool publishState(bool state);
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedTrigger(const char* serializedDevice) const;
bool writeSerializedData(const char* serializedDevice) const;
const char* _name;
const char* _class;
bool _currentState;
};
+100 -114
View File
@@ -4,18 +4,13 @@
#include "../HADevice.h"
#include "../HAUtils.h"
// todo: move all variables to progmem
static const char* HAComponentName = "sensor";
static const char* ValueTopic = "value";
template <typename T>
HASensor<T>::HASensor(
const char* name,
T initialValue,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt),
_name(name),
BaseDeviceType(mqtt, "sensor", name),
_class(nullptr),
_units(nullptr),
_valueType(HAUtils::determineValueType<T>()),
@@ -31,8 +26,7 @@ HASensor<T>::HASensor(
T initialValue,
HAMqtt& mqtt
) :
BaseDeviceType(mqtt),
_name(name),
BaseDeviceType(mqtt, "sensor", name),
_class(deviceClass),
_units(nullptr),
_valueType(HAUtils::determineValueType<T>()),
@@ -41,27 +35,23 @@ HASensor<T>::HASensor(
}
template <typename T>
HASensor<T>::~HASensor()
{
}
template <typename T>
void HASensor<T>::onMqttConnected()
{
if (strlen(_name) == 0 || _valueType == HAUtils::ValueTypeUnknown) {
if (strlen(name()) == 0 ||
_valueType == HAUtils::ValueTypeUnknown) {
return;
}
publishConfig();
publishValue(_currentValue);
publishAvailability();
}
template <typename T>
bool HASensor<T>::setValue(T value)
{
if (strlen(_name) == 0 ||
if (strlen(name()) == 0 ||
_valueType == HAUtils::ValueTypeUnknown) {
return false;
}
@@ -100,7 +90,12 @@ void HASensor<T>::publishConfig()
return;
}
const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic);
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
@@ -108,14 +103,20 @@ void HASensor<T>::publishConfig()
}
char topic[topicLength];
generateTopic(topic, HAComponentName, _name, ConfigTopic);
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedTrigger(serializedDevice);
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
@@ -123,26 +124,28 @@ void HASensor<T>::publishConfig()
template <typename T>
bool HASensor<T>::publishValue(T value)
{
if (strlen(_name) == 0 ||
if (strlen(name()) == 0 ||
_valueType == HAUtils::ValueTypeUnknown) {
return false;
}
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
ValueTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
HAComponentName,
_name,
ValueTopic
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
@@ -172,139 +175,122 @@ uint16_t HASensor<T>::calculateSerializedLength(
return 0;
}
const uint16_t& valueTopicLength = calculateTopicLength(
HAComponentName,
_name,
ValueTopic,
false
);
if (valueTopicLength == 0) {
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return 0;
}
uint16_t size =
2 + // opening and closing bracket (without null terminator)
valueTopicLength + 11 + // 11 - length of the JSON data for this field
strlen(_name) + 10; // 10 - length of the JSON data for this field
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
// unique ID
size += strlen(mqtt()->getDevice()->getUniqueId());
size += strlen(_name) + 14; // 14 - length of the JSON data for this field
if (isAvailabilityConfigured()) {
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(
mqtt(),
componentName(),
name()
);
}
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic,
false
);
if (topicSize == 0) {
return 0;
}
// Format: "stat_t":"[TOPIC]"
size += topicSize + 11; // 11 - length of the JSON decorators for this field
}
// device class
if (_class != nullptr) {
size += strlen(_class) + 13; // 13 - length of the JSON data for this field
// Field format: ,"dev_cla":"[CLASS]"
size += strlen(_class) + 13; // 13 - length of the JSON decorators for this field
}
// units of measurement
if (_units != nullptr) {
size += strlen(_units) + 18;
// Format: ,"unit_of_meas":"[UNITS]"
size += strlen(_units) + 18; // 18 - length of the JSON decorators for this field
}
// device
size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field
return size;
return size; // exludes null terminator
}
template <typename T>
bool HASensor<T>::writeSerializedTrigger(const char* serializedDevice) const
bool HASensor<T>::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr ||
_valueType == HAUtils::ValueTypeUnknown) {
return false;
}
static const char QuotationSign[] PROGMEM = {"\""};
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
// state topic
{
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
ValueTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char stateTopic[topicSize];
generateTopic(
stateTopic,
HAComponentName,
_name,
ValueTopic
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(stateTopic) == 0) {
if (strlen(topic) == 0) {
return false;
}
static const char DataBefore[] PROGMEM = {"{\"stat_t\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(stateTopic, strlen(stateTopic));
mqtt()->writePayload_P(QuotationSign);
}
// name
{
static const char DataBefore[] PROGMEM = {",\"name\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(_name, strlen(_name));
mqtt()->writePayload_P(QuotationSign);
}
// unique ID
{
static const char Underscore[] PROGMEM = {"_"};
static const char DataBefore[] PROGMEM = {",\"uniq_id\":\""};
const HADevice* device = mqtt()->getDevice();
uint8_t uniqueIdLength = strlen(_name) + strlen(device->getUniqueId()) + 2; // underscore and null temrinator
char uniqueId[uniqueIdLength];
strcpy(uniqueId, _name);
strcat_P(uniqueId, Underscore);
strcat(uniqueId, device->getUniqueId());
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(uniqueId, strlen(uniqueId));
mqtt()->writePayload_P(QuotationSign);
}
// device
{
static const char Data[] PROGMEM = {",\"dev\":"};
mqtt()->writePayload_P(Data);
mqtt()->writePayload(serializedDevice, strlen(serializedDevice));
static const char Prefix[] PROGMEM = {"\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
}
// device class
if (_class != nullptr) {
static const char DataBefore[] PROGMEM = {",\"dev_cla\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(_class, strlen(_class));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {",\"dev_cla\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _class);
}
// units of measurement
if (_units != nullptr) {
static const char DataBefore[] PROGMEM = {",\"unit_of_meas\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(_units, strlen(_units));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {",\"unit_of_meas\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, _units);
}
{
static const char Data[] PROGMEM = {"}"};
mqtt()->writePayload_P(Data);
DeviceTypeSerializer::mqttWriteNameField(mqtt(), name());
DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name());
if (isAvailabilityConfigured()) {
DeviceTypeSerializer::mqttWriteAvailabilityField(
mqtt(),
componentName(),
name()
);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
return true;
}
+1 -4
View File
@@ -37,8 +37,6 @@ public:
HAMqtt& mqtt
);
virtual ~HASensor();
/**
* Publishes configuration of the sensor to the MQTT.
*/
@@ -73,11 +71,10 @@ private:
void publishConfig();
bool publishValue(T value);
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedTrigger(const char* serializedDevice) const;
bool writeSerializedData(const char* serializedDevice) const;
uint16_t calculateValueLength() const;
bool valueToStr(char* dst, T value) const;
const char* _name;
const char* _class;
const char* _units;
HAUtils::ValueType _valueType;
+146 -141
View File
@@ -4,36 +4,24 @@
#include "../HADevice.h"
#include "../HAUtils.h"
// todo: move all variables to progmem
static const char* HAComponentName = "switch";
static const char* CommandTopic = "cmd";
static const char* StateTopic = "state";
static const char* StateOn = "ON";
static const char* StateOff = "OFF";
HASwitch::HASwitch(const char* name, bool initialState, HAMqtt& mqtt) :
BaseDeviceType(mqtt),
_name(name),
BaseDeviceType(mqtt, "switch", name),
_stateCallback(nullptr),
_currentState(initialState)
{
}
HASwitch::~HASwitch()
{
}
void HASwitch::onMqttConnected()
{
if (strlen(_name) == 0) {
if (strlen(name()) == 0) {
return;
}
publishConfig();
publishState(_currentState);
subscribeCommandTopic();
publishAvailability();
}
void HASwitch::onMqttMessage(
@@ -42,21 +30,22 @@ void HASwitch::onMqttMessage(
const uint16_t& length
)
{
if (strlen(_name) == 0) {
if (strlen(name()) == 0) {
return;
}
static const char Slash[] PROGMEM = {"/"};
uint8_t suffixLength = strlen(_name) + strlen(CommandTopic) + 3; // two slashes + null terminator
// 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(suffix, name());
strcat_P(suffix, Slash);
strcat(suffix, CommandTopic);
strcat(suffix, DeviceTypeSerializer::CommandTopic);
if (HAUtils::endsWith(topic, suffix)) {
bool onState = (length == strlen(StateOn));
bool onState = (length == strlen(DeviceTypeSerializer::StateOn));
setState(onState);
}
}
@@ -67,7 +56,7 @@ bool HASwitch::setState(bool state)
return true;
}
if (strlen(_name) == 0) {
if (strlen(name()) == 0) {
return false;
}
@@ -80,11 +69,6 @@ bool HASwitch::setState(bool state)
return false;
}
void HASwitch::onStateChanged(HASWITCH_CALLBACK)
{
_stateCallback = callback;
}
void HASwitch::triggerCallback(bool state)
{
if (_stateCallback == nullptr) {
@@ -111,7 +95,12 @@ void HASwitch::publishConfig()
return;
}
const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic);
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
@@ -119,65 +108,83 @@ void HASwitch::publishConfig()
}
char topic[topicLength];
generateTopic(topic, HAComponentName, _name, ConfigTopic);
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedTrigger(serializedDevice);
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
bool HASwitch::publishState(bool state)
{
if (strlen(_name) == 0) {
if (strlen(name()) == 0) {
return false;
}
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
StateTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
HAComponentName,
_name,
StateTopic
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(topic) == 0) {
return false;
}
return mqtt()->publish(topic, (state ? StateOn : StateOff), true);
return mqtt()->publish(
topic,
(
state ?
DeviceTypeSerializer::StateOn :
DeviceTypeSerializer::StateOff
),
true
);
}
void HASwitch::subscribeCommandTopic()
{
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
CommandTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::CommandTopic
);
if (topicSize == 0) {
return;
}
char topic[topicSize];
generateTopic(
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
HAComponentName,
_name,
CommandTopic
componentName(),
name(),
DeviceTypeSerializer::CommandTopic
);
if (strlen(topic) == 0) {
@@ -193,145 +200,143 @@ uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
return 0;
}
const uint16_t& cmdTopicLength = calculateTopicLength(
HAComponentName,
_name,
CommandTopic,
false
);
const uint16_t& stateTopicLength = calculateTopicLength(
HAComponentName,
_name,
StateTopic,
false
);
if (cmdTopicLength == 0 || stateTopicLength == 0) {
const HADevice* device = mqtt()->getDevice();
if (device == nullptr) {
return 0;
}
uint16_t size =
2 + // opening and closing bracket (without null terminator)
cmdTopicLength + 10 + // 10 - length of the JSON data for this field
stateTopicLength + 12 + // 12 - length of the JSON data for this field
strlen(_name) + 10; // 10 - length of the JSON data for this field
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(device, name());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
// unique ID
size += strlen(mqtt()->getDevice()->getUniqueId());
size += strlen(_name) + 14; // 14 - length of the JSON data for this field
if (isAvailabilityConfigured()) {
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(
mqtt(),
componentName(),
name()
);
}
// device
size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field
// cmd topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::CommandTopic,
false
);
return size;
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(
mqtt(),
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
}
return size; // exludes null terminator
}
bool HASwitch::writeSerializedTrigger(const char* serializedDevice) const
bool HASwitch::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
static const char QuotationSign[] PROGMEM = {"\""};
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
// command topic
{
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
CommandTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::CommandTopic
);
if (topicSize == 0) {
return false;
}
char cmdTopic[topicSize];
generateTopic(
cmdTopic,
HAComponentName,
_name,
CommandTopic
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::CommandTopic
);
if (strlen(cmdTopic) == 0) {
if (strlen(topic) == 0) {
return false;
}
static const char DataBefore[] PROGMEM = {"{\"cmd_t\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(cmdTopic, strlen(cmdTopic));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {"\"cmd_t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
}
// state topic
{
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
StateTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
return false;
}
char stateTopic[topicSize];
generateTopic(
stateTopic,
HAComponentName,
_name,
StateTopic
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::StateTopic
);
if (strlen(stateTopic) == 0) {
if (strlen(topic) == 0) {
return false;
}
static const char DataBefore[] PROGMEM = {",\"stat_t\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(stateTopic, strlen(stateTopic));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {",\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
}
// name
{
static const char DataBefore[] PROGMEM = {",\"name\":\""};
DeviceTypeSerializer::mqttWriteNameField(mqtt(), name());
DeviceTypeSerializer::mqttWriteUniqueIdField(mqtt(), name());
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(_name, strlen(_name));
mqtt()->writePayload_P(QuotationSign);
if (isAvailabilityConfigured()) {
DeviceTypeSerializer::mqttWriteAvailabilityField(
mqtt(),
componentName(),
name()
);
}
// unique ID
{
static const char Underscore[] PROGMEM = {"_"};
static const char DataBefore[] PROGMEM = {",\"uniq_id\":\""};
const HADevice* device = mqtt()->getDevice();
uint8_t uniqueIdLength = strlen(_name) + strlen(device->getUniqueId()) + 2; // underscore and null temrinator
char uniqueId[uniqueIdLength];
strcpy(uniqueId, _name);
strcat_P(uniqueId, Underscore);
strcat(uniqueId, device->getUniqueId());
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(uniqueId, strlen(uniqueId));
mqtt()->writePayload_P(QuotationSign);
}
// device
{
static const char Data[] PROGMEM = {",\"dev\":"};
mqtt()->writePayload_P(Data);
mqtt()->writePayload(serializedDevice, strlen(serializedDevice));
}
{
static const char Data[] PROGMEM = {"}"};
mqtt()->writePayload_P(Data);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
return true;
}
+3 -4
View File
@@ -20,7 +20,6 @@ public:
bool initialState,
HAMqtt& mqtt
);
virtual ~HASwitch();
/**
* Publishes configuration of the sensor to the MQTT.
@@ -78,7 +77,8 @@ public:
*
* @param callback
*/
void onStateChanged(HASWITCH_CALLBACK);
inline void onStateChanged(HASWITCH_CALLBACK)
{ _stateCallback = callback; }
private:
void triggerCallback(bool state);
@@ -86,9 +86,8 @@ private:
bool publishState(bool state);
void subscribeCommandTopic();
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedTrigger(const char* serializedDevice) const;
bool writeSerializedData(const char* serializedDevice) const;
const char* _name;
void (*_stateCallback)(bool, HASwitch*);
bool _currentState;
};
+59 -60
View File
@@ -4,24 +4,15 @@
#include "../HADevice.h"
#include "../HAUtils.h"
// todo: move to progmem
static const char* HAComponentName = "tag";
HATagScanner::HATagScanner(const char* name, HAMqtt& mqtt) :
BaseDeviceType(mqtt),
_name(name)
{
}
HATagScanner::~HATagScanner()
BaseDeviceType(mqtt, "tag", name)
{
}
void HATagScanner::onMqttConnected()
{
if (strlen(_name) == 0) {
if (strlen(name()) == 0) {
return;
}
@@ -34,21 +25,23 @@ bool HATagScanner::tagScanned(const char* tag)
return false;
}
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
EventTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::EventTopic
);
if (topicSize == 0) {
return false;
}
char topic[topicSize];
generateTopic(
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
HAComponentName,
_name,
EventTopic
componentName(),
name(),
DeviceTypeSerializer::EventTopic
);
if (strlen(topic) == 0) {
@@ -75,7 +68,12 @@ void HATagScanner::publishConfig()
return;
}
const uint16_t& topicLength = calculateTopicLength(HAComponentName, _name, ConfigTopic);
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
if (topicLength == 0 || dataLength == 0) {
@@ -83,14 +81,20 @@ void HATagScanner::publishConfig()
}
char topic[topicLength];
generateTopic(topic, HAComponentName, _name, ConfigTopic);
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
return;
}
if (mqtt()->beginPublish(topic, dataLength, true)) {
writeSerializedTrigger(serializedDevice);
writeSerializedData(serializedDevice);
mqtt()->endPublish();
}
}
@@ -103,63 +107,58 @@ uint16_t HATagScanner::calculateSerializedLength(
return 0;
}
const uint16_t& eventTopicLength = calculateTopicLength(
HAComponentName,
_name,
EventTopic,
false
);
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
uint16_t size =
2 + // opening and closing bracket (without null terminator)
eventTopicLength + 6 + // 6 - length of the JSON data for this field
strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field
// event topic
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::EventTopic,
false
);
return size;
// Format: "t":"[TOPIC]"
size += topicSize + 6; // 6 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
bool HATagScanner::writeSerializedTrigger(const char* serializedDevice) const
bool HATagScanner::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
static const char QuotationSign[] PROGMEM = {"\""};
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
// topic
{
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
_name,
EventTopic
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
mqtt(),
componentName(),
name(),
DeviceTypeSerializer::EventTopic
);
char topic[topicSize];
generateTopic(
DeviceTypeSerializer::generateTopic(
mqtt(),
topic,
HAComponentName,
_name,
EventTopic
componentName(),
name(),
DeviceTypeSerializer::EventTopic
);
static const char DataBefore[] PROGMEM = {"{\"t\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(topic, strlen(topic));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {"\"t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
}
// device
{
static const char Data[] PROGMEM = {",\"dev\":"};
mqtt()->writePayload_P(Data);
mqtt()->writePayload(serializedDevice, strlen(serializedDevice));
}
{
static const char Data[] PROGMEM = {"}"};
mqtt()->writePayload_P(Data);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
return true;
}
+6 -4
View File
@@ -12,13 +12,17 @@ public:
* @param name Name of the scanner. Recommendes characters: [a-z0-9\-_]
*/
HATagScanner(const char* name, HAMqtt& mqtt);
virtual ~HATagScanner();
/**
* Publishes configuration of the sensor to the MQTT.
*/
virtual void onMqttConnected() override;
/**
* Tag scanner doesn't support availability. Nothing to do here.
*/
virtual void setAvailability(bool online) override { }
/**
* Sends "tag scanned" event to the MQTT (Home Assistant).
* Based on this event HA may perform user-defined automation.
@@ -30,9 +34,7 @@ public:
private:
void publishConfig();
uint16_t calculateSerializedLength(const char* serializedDevice) const;
bool writeSerializedTrigger(const char* serializedDevice) const;
const char* _name;
bool writeSerializedData(const char* serializedDevice) const;
};
#endif
+79 -64
View File
@@ -3,10 +3,8 @@
#include "../HAMqtt.h"
#include "../HADevice.h"
static const char* HAComponentName = "device_automation"; // todo: move to progmem
HATriggers::HATriggers(HAMqtt& mqtt) :
BaseDeviceType(mqtt),
BaseDeviceType(mqtt, "device_automation", nullptr),
_triggers(nullptr),
_triggersNb(0)
{
@@ -68,9 +66,9 @@ bool HATriggers::trigger(const char* type, const char* subtype)
}
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
componentName(),
trigger,
EventTopic
DeviceTypeSerializer::EventTopic
);
if (topicSize == 0) {
@@ -78,7 +76,12 @@ bool HATriggers::trigger(const char* type, const char* subtype)
}
char topic[topicSize];
generateTopic(topic, HAComponentName, trigger, EventTopic);
generateTopic(
topic,
componentName(),
trigger,
DeviceTypeSerializer::EventTopic
);
if (strlen(topic) == 0) {
return false;
@@ -110,15 +113,27 @@ void HATriggers::publishConfig()
continue;
}
const uint16_t& topicLength = calculateTopicLength(HAComponentName, trigger, ConfigTopic);
const uint16_t& dataLength = calculateSerializedLength(trigger, serializedDevice);
const uint16_t& topicLength = calculateTopicLength(
componentName(),
trigger,
DeviceTypeSerializer::ConfigTopic
);
const uint16_t& dataLength = calculateSerializedLength(
trigger,
serializedDevice
);
if (topicLength == 0 || dataLength == 0) {
continue;
}
char topic[topicLength];
generateTopic(topic, HAComponentName, trigger, ConfigTopic);
generateTopic(
topic,
componentName(),
trigger,
DeviceTypeSerializer::ConfigTopic
);
if (strlen(topic) == 0) {
continue;
@@ -139,7 +154,8 @@ uint16_t HATriggers::calculateTopicLength(
) const
{
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 1; // + underscore
return BaseDeviceType::calculateTopicLength(
return DeviceTypeSerializer::calculateTopicLength(
mqtt(),
component,
nullptr,
suffix,
@@ -162,7 +178,8 @@ uint16_t HATriggers::generateTopic(
strcat_P(objectId, Underscore);
strcat(objectId, trigger->type);
return BaseDeviceType::generateTopic(
return DeviceTypeSerializer::generateTopic(
mqtt(),
output,
component,
objectId,
@@ -179,28 +196,45 @@ uint16_t HATriggers::calculateSerializedLength(
return 0;
}
const uint16_t& topicLength = calculateTopicLength(
HAComponentName,
trigger,
EventTopic,
false
);
if (topicLength == 0) {
return 0;
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
// automation type
{
// Format: "atype":"trigger"
size += 17;
}
uint16_t size =
2 + // opening and closing bracket (without null terminator)
17 + // automation type
topicLength + 7 + // 7 - length of the JSON data for this field
strlen(trigger->type) + 10 + // 10 - length of the JSON data for this field
strlen(trigger->subtype) + 11; // 11 - length of the JSON data for this field
// topic
{
const uint16_t& topicSize = calculateTopicLength(
componentName(),
trigger,
DeviceTypeSerializer::EventTopic,
false
);
if (topicSize == 0) {
return 0;
}
if (serializedDevice != nullptr) {
size += strlen(serializedDevice) + 7; // 7 - length of the JSON data for this field
// Format: ,"t":"[TOPIC]"
size += topicSize + 7; // 7 - length of the JSON decorators for this field
}
return size;
// type
{
// Format: ,"type":"[TYPE]"
size += strlen(trigger->type) + 10; // 10 - length of the JSON decorators for this field
}
// subtype
{
// Format: ,"stype":"[SUBTYPE]"
size += strlen(trigger->subtype) + 11; // 11 - length of the JSON decorators for this field;
}
return size; // exludes null terminator
}
bool HATriggers::writeSerializedTrigger(
@@ -212,74 +246,55 @@ bool HATriggers::writeSerializedTrigger(
return false;
}
static const char QuotationSign[] PROGMEM = {"\""};
DeviceTypeSerializer::mqttWriteBeginningJson(mqtt());
// automation type
{
static const char Data[] PROGMEM = {"{\"atype\":\"trigger\""};
static const char Data[] PROGMEM = {"\"atype\":\"trigger\""};
mqtt()->writePayload_P(Data);
}
// topic
{
const uint16_t& topicSize = calculateTopicLength(
HAComponentName,
componentName(),
trigger,
EventTopic
DeviceTypeSerializer::EventTopic
);
if (topicSize == 0) {
return false;
}
char eventTopic[topicSize];
char topic[topicSize];
generateTopic(
eventTopic,
HAComponentName,
topic,
componentName(),
trigger,
EventTopic
DeviceTypeSerializer::EventTopic
);
if (strlen(eventTopic) == 0) {
if (strlen(topic) == 0) {
return false;
}
static const char DataBefore[] PROGMEM = {",\"t\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(eventTopic, strlen(eventTopic));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {",\"t\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, topic);
}
// type
{
static const char DataBefore[] PROGMEM = {",\"type\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(trigger->type, strlen(trigger->type));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {",\"type\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, trigger->type);
}
// subtype
{
static const char DataBefore[] PROGMEM = {",\"stype\":\""};
mqtt()->writePayload_P(DataBefore);
mqtt()->writePayload(trigger->subtype, strlen(trigger->subtype));
mqtt()->writePayload_P(QuotationSign);
static const char Prefix[] PROGMEM = {",\"stype\":\""};
DeviceTypeSerializer::mqttWriteConstCharField(mqtt(), Prefix, trigger->subtype);
}
// device
{
static const char Data[] PROGMEM = {",\"dev\":"};
mqtt()->writePayload_P(Data);
mqtt()->writePayload(serializedDevice, strlen(serializedDevice));
}
{
static const char Data[] PROGMEM = {"}"};
mqtt()->writePayload_P(Data);
}
DeviceTypeSerializer::mqttWriteDeviceField(mqtt(), serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson(mqtt());
return true;
}
+5
View File
@@ -16,6 +16,11 @@ public:
virtual void onMqttConnected() override;
/**
* Triggers dont't support availability. Nothing to do here.
*/
virtual void setAvailability(bool online) override { }
bool add(const char* type, const char* subtype);
bool trigger(const char* type, const char* subtype);