Release 1.3.0 (#36)

* Add support for HVAC (#15)

* remove HASwitch::triggerCallback method

* fix typo in docs

* HASwitch: simplify callback #define

* work in progress: HVAC

* replace local mqtt instances with singleton

* device types optimization

* cleanup

* finished aux heating and away mode

* wip: HVAC

* finished HVAC implementation

* HVAC implementation

* add ifdefs for all HA components

* wip: optimization

* update readme

* update examples

* remove unused constructor from HAMqtt

* update HAUtils

* remove mqtt from constructors, fix compilation warnings

* finished HVAC

* add HVAC example, add changelog

* update changelog, add documentation

* Add support for icons in HASwitch and HASensor (#16)

* add support for icon in HASwitch

* add support for icon in HASensor

* update changelog

* add support for setting retain flag in HASwitch

* Add support for text payload in HASensor (#17)

* refactored HASensor

* update changelog

* fix sensor example

* Add support for fans (HAFan) (#18)

* finished basic implementation of the HAFan

* add retain method to DeviceTypeSerializer

* finished HAFan implementation

* update HAHVAC

* device types cleanup

* add HAFan example

* update changelog

* update readme

* update fan example

* optimization, add support for ActionFeature in HVAC

* Add support for MQTT LWT (#19)

* add support for hostname in HAMqtt

* add support for LWT and shared availability

* minor fixes

* added advanced availability example

* update examples

* update changelog

* cleanup & bug fix

* bump up version

* minor improvements and fixes

* update readme

* add HAMqtt::onMessage method

* Add support for covers (#28)

* add HACovers

* add cover example, update implementation

* disable debug

* update mqtt-events example

* Add support for setting different prefix for non-discovery topics (#29)

* add support for data prefix

* cleanup

* update comments and logs

* update multi-state-button example

* rename rexample from mqtt-events to mqtt-advanced

* update changelog

* update links in changelog

* Home Assistant 2021.4.5 - MQTT upgrade (#33)

* updae HAFan implementation

* change lib version

* update changelog

* fix setSpeed method

* update HAFan example

* Separate uniqueId from name (#34)

* rename name to uniqueId

* remove legacy labels

* update examples

* rename isMyTopic to compareTopics

* update changelog, rename name to uniqueId

* Implement onBeforeStateChanged callback in HASwitch (#35)

* add low latency mode to HASwitch

* add onBeforeStateChanged callback to HASwitch

* update readme

* rename DEPRECATED macro

* update advanced MQTT example
This commit is contained in:
Dawid Chyrzyński
2021-04-17 20:03:32 +02:00
committed by GitHub
parent 15562fa74c
commit a4e50b7d04
40 changed files with 980 additions and 445 deletions
+17 -3
View File
@@ -1,13 +1,27 @@
# Changelog
## 1.3.0
**New features:**
* Added `onMessage()` method to HAMqtt class
* Added support for HA Covers
* Added support for setting different prefix for non-discovery topics (see [Advanced MQTT example](examples/mqtt-advanced/mqtt-advanced.ino))
* Added `setName` method to HASensor
* Added `setName` method to HASwitch
* Added `onBeforeStateChanged` callback to HASwitch
**Updates:**
* Removed legacy properties from HAFan (Home Assistant 2021.4.4). Deprecated methods will be removed after a quarter (2021.7)
* Separated `uniqueID` field from `name` in all devices types
## 1.2.0
**Breaking changes:**
* Refactored HASensor implementation. Please take a look at updated example in `examples/sensor/sensor.ino`
* Refactored HASensor implementation. Please take a look at [updated example](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 excluding devices types from the compilation using defines (see [src/ArduinoHADefines.h](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
@@ -15,7 +29,7 @@
* 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`)
* Added support for MQTT LWT (see [Advanced Availability example](examples/advanced-availability/advanced-availability.ino))
**Updates:**
* Optimized codebase and logic in all devices types
+25 -14
View File
@@ -9,11 +9,25 @@ but I successfully use it on ESP8266/ESP8255 boards in my projects.
* MQTT discovery (device is added to the Home Assistant panel automatically)
* MQTT Last Will and Testament
* Support for custom MQTT messages (publishing and subscribing)
* Auto reconnect with MQTT broker
* Reporting availability (online/offline states) of a device
## Supported HA types
* Binary sensors
* Covers
* 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)*
## Examples
* [Binary Sensor](examples/binary-sensor/binary-sensor.ino)
* [Cover](examples/cover/cover.ino)
* [Fan](examples/fan/fan.ino)
* [LED switch](examples/led-switch/led-switch.ino)
* [Multi-state button](examples/multi-state-button/multi-state-button.ino)
@@ -24,40 +38,37 @@ but I successfully use it on ESP8266/ESP8255 boards in my projects.
* [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)
* [MQTT advanced](examples/mqtt-advanced/mqtt-advanced.ino)
## Tested boards
* Arduino Uno
* Arduino Mega
* Controllino Maxi (standard/pure/automation/power)
* Controllino Mega (standard/pure)
* NodeMCU
* ESP-01
* Generic ESP8266/ESP8255
* Arduino Nano 33 IoT (SAMD)
## Tested devices
* Controllino Maxi (standard/pure/automation/power)
* Controllino Mega (standard/pure)
* Sonoff Dual R2
* Sonoff Basic
* Sonoff Mini
* Tuya Wi-Fi switch module
* Tuya Wi-Fi curtain module
## Tested Arduino Shields
* Arduino Ethernet Shield (WizNet W5100)
## 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
@@ -13,7 +13,7 @@ EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
// "input" may be anything you want to be displayed in HA panel
// "input" is unique ID of the sensor. You should define you own ID.
// "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);
@@ -32,6 +32,8 @@ void setup() {
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
sensor.setName("Door sensor"); // optional
// 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
+3 -1
View File
@@ -13,7 +13,7 @@ EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
// "input" may be anything you want to be displayed in HA panel
// "input" is unique ID of the sensor. You should define you own ID.
// "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);
@@ -36,6 +36,8 @@ void setup() {
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
sensor.setName("Door sensor"); // optional
mqtt.begin(BROKER_ADDR);
}
+3 -1
View File
@@ -12,7 +12,7 @@ EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
// "input" may be anything you want to be displayed in HA panel
// "input" is unique ID of the sensor. You should define you own ID.
// "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);
@@ -28,6 +28,8 @@ void setup() {
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
sensor.setName("Door sensor"); // optional
mqtt.begin(BROKER_ADDR);
}
+49
View File
@@ -0,0 +1,49 @@
#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);
HACover cover("my-cover"); // "my-cover" is unique ID of the cover. You should define your own ID.
void onCoverCommand(HACover::CoverCommand cmd) {
if (cmd == HACover::CommandOpen) {
Serial.println("Command: Open");
cover.setState(HACover::StateOpening);
} else if (cmd == HACover::CommandClose) {
Serial.println("Command: Close");
cover.setState(HACover::StateClosing);
} else if (cmd == HACover::CommandStop) {
Serial.println("Command: Stop");
cover.setState(HACover::StateStopped);
}
// Available states:
// HACover::StateClosed
// HACover::StateClosing
// HACover::StateOpen
// HACover::StateOpening
// HACover::StateStopped
// You can also report position using setPosition() method
}
void setup() {
Serial.begin(9600);
Ethernet.begin(mac);
cover.onCommand(onCoverCommand);
cover.setName("My cover"); // optional
// cover.setRetain(true); // optionally you can set retain flag
mqtt.begin(BROKER_ADDR);
}
void loop() {
Ethernet.maintain();
mqtt.loop();
}
+7 -12
View File
@@ -11,6 +11,7 @@ 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.
// "ventilation" is unique ID of the fan. You should define your own ID.
HAFan fan("ventilation", HAFan::SpeedsFeature);
void onStateChanged(bool state) {
@@ -18,20 +19,14 @@ void onStateChanged(bool state) {
Serial.println(state);
}
void onSpeedChanged(HAFan::Speed speed) {
void onSpeedChanged(uint16_t 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");
}
Serial.println(speed);
}
void setup() {
Serial.begin(9600);
// you don't need to verify return status
Ethernet.begin(mac);
@@ -40,10 +35,10 @@ void setup() {
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);
fan.setSpeedRangeMin(1);
fan.setSpeedRangeMax(100);
// handle fan states
fan.onStateChanged(onStateChanged);
+9 -1
View File
@@ -9,7 +9,13 @@ byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
HASwitch led("led", false); // you can use custom name in place of "led"
HASwitch led("led", false); // "led" is unique ID of the switch. You should define your own ID.
void onBeforeSwitchStateChanged(bool state, HASwitch* s)
{
// this callback will be called before publishing new state to HA
// in some cases there may be delay before onStateChanged is called due to network latency
}
void onSwitchStateChanged(bool state, HASwitch* s)
{
@@ -29,8 +35,10 @@ void setup() {
// set icon (optional)
led.setIcon("mdi:lightbulb");
led.setName("My LED");
// handle switch state
led.onBeforeStateChanged(onBeforeSwitchStateChanged); // optional
led.onStateChanged(onSwitchStateChanged);
mqtt.begin(BROKER_ADDR);
+56
View File
@@ -0,0 +1,56 @@
#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 onMqttMessage(const char* topic, const uint8_t* payload, uint16_t length) {
// This callback is called when message from MQTT broker is received.
// Please note that you should always verify if the message's topic is the one you expect.
// For example: if (memcmp(topic, "myCustomTopic") == 0) { ... }
Serial.print("New message on topic: ");
Serial.println(topic);
Serial.print("Data: ");
Serial.println((const char*)payload);
mqtt.publish("myPublishTopic", "hello");
}
void onMqttConnected() {
Serial.println("Connected to the broker!");
// You can subscribe to custom topic if you need
mqtt.subscribe("myCustomTopic");
}
void onMqttConnectionFailed() {
Serial.println("Failed to connect to the broker!");
}
void setup() {
Serial.begin(9600);
Ethernet.begin(mac);
mqtt.onMessage(onMqttMessage);
mqtt.onConnected(onMqttConnected);
mqtt.onConnectionFailed(onMqttConnectionFailed);
// If you use custom discovery prefix you can change it as following:
// mqtt.setDiscoveryPrefix("customPrefix");
// If you want to change prefix only for non-discovery prefix:
// mqtt.setDataPrefix("data");
mqtt.begin(BROKER_ADDR);
}
void loop() {
Ethernet.maintain();
mqtt.loop();
}
-32
View File
@@ -1,32 +0,0 @@
#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); // you can use custom name in place of "led"
HASwitch led("led", false); // "led" is unique ID of the switch. You should define your own ID.
void onSwitchStateChanged(bool state, HASwitch* s)
{
@@ -31,6 +31,7 @@ void setup() {
// handle switch state
led.onStateChanged(onSwitchStateChanged);
led.setName("My LED"); // optional
mqtt.begin(BROKER_ADDR, BROKER_USERNAME, BROKER_PASSWORD);
}
@@ -22,8 +22,6 @@ void setup() {
// you don't need to verify return status
Ethernet.begin(mac);
Serial.println(Ethernet.localIP());
// set device's details (optional)
device.setName("Arduino");
device.setSoftwareVersion("1.0.0");
+2 -1
View File
@@ -9,7 +9,7 @@
WiFiClient client;
HADevice device;
HAMqtt mqtt(client, device);
HASwitch led("led", false); // you can use custom name in place of "led"
HASwitch led("led", false); // "led" is unique ID of the switch. You should define your own ID.
void onSwitchStateChanged(bool state, HASwitch* s)
{
@@ -43,6 +43,7 @@ void setup() {
// handle switch state
led.onStateChanged(onSwitchStateChanged);
led.setName("My LED"); // optional
mqtt.begin(BROKER_ADDR);
}
+9 -1
View File
@@ -9,7 +9,13 @@
WiFiClient client;
HADevice device;
HAMqtt mqtt(client, device);
HASwitch led("led", false); // you can use custom name in place of "led"
HASwitch led("led", false); // "led" is unique ID of the switch. You should define your own ID.
void onBeforeSwitchStateChanged(bool state, HASwitch* s)
{
// this callback will be called before publishing new state to HA
// in some cases there may be delay before onStateChanged is called due to network latency
}
void onSwitchStateChanged(bool state, HASwitch* s)
{
@@ -42,7 +48,9 @@ void setup() {
device.setSoftwareVersion("1.0.0");
// handle switch state
led.onBeforeStateChanged(onBeforeSwitchStateChanged); // optional
led.onStateChanged(onSwitchStateChanged);
led.setName("My LED"); // optional
mqtt.begin(BROKER_ADDR);
}
+8 -1
View File
@@ -10,7 +10,13 @@ double lastValue = 0;
EthernetClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
HASensor temp("temp");
HASensor temp("temp"); // "temp" is unique ID of the sensor. You should define your own ID.
void onBeforeSwitchStateChanged(bool state, HASwitch* s)
{
// this callback will be called before publishing new state to HA
// in some cases there may be delay before onStateChanged is called due to network latency
}
void setup() {
// you don't need to verify return status
@@ -24,6 +30,7 @@ void setup() {
temp.setUnitOfMeasurement("°C");
temp.setDeviceClass("temperature");
temp.setIcon("mdi:home");
temp.setName("Home temperature");
mqtt.begin(BROKER_ADDR);
}
+1 -1
View File
@@ -1,5 +1,5 @@
name=home-assistant-integration
version=1.2.0
version=1.3.0
author=Dawid Chyrzynski <dawid.chyrzynski@gmail.com>
maintainer=Dawid Chyrzynski <dawid.chyrzynski@gmail.com>
sentence=Home Assistant MQTT integration for Arduino
+1
View File
@@ -5,6 +5,7 @@
#include "HAMqtt.h"
#include "HAUtils.h"
#include "device-types/HABinarySensor.h"
#include "device-types/HACover.h"
#include "device-types/HAFan.h"
#include "device-types/HAHVAC.h"
#include "device-types/HASensor.h"
+10
View File
@@ -5,9 +5,19 @@
// You can reduce Flash size of the compiled library by commenting unused components below
#define ARDUINOHA_BINARY_SENSOR
#define ARDUINOHA_COVER
#define ARDUINOHA_FAN
#define ARDUINOHA_HVAC
#define ARDUINOHA_SENSOR
#define ARDUINOHA_SWITCH
#define ARDUINOHA_TAG_SCANNER
#define ARDUINOHA_TRIGGERS
#ifdef __GNUC__
#define AHA_DEPRECATED(func) func __attribute__ ((deprecated))
#elif defined(_MSC_VER)
#define AHA_DEPRECATED(func) __declspec(deprecated) func
#else
#warning "Arduino Home Assistant: You may miss deprecation warnings."
#define AHA_DEPRECATED(func) func
#endif
+10 -4
View File
@@ -20,10 +20,12 @@ void onMessageReceived(char* topic, uint8_t* payload, unsigned int length)
HAMqtt::HAMqtt(Client& netClient, HADevice& device) :
_netClient(netClient),
_device(device),
_messageCallback(nullptr),
_connectedCallback(nullptr),
_connectionFailedCallback(nullptr),
_initialized(false),
_discoveryPrefix(DefaultDiscoveryPrefix),
_dataPrefix(nullptr),
_mqtt(new PubSubClient(netClient)),
_username(nullptr),
_password(nullptr),
@@ -195,9 +197,9 @@ bool HAMqtt::publish(const char* topic, const char* payload, bool retained)
}
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Publishing message with topic: "));
Serial.print(F("Publishing: "));
Serial.print(topic);
Serial.print(F(", payload length: "));
Serial.print(F(", len: "));
Serial.print(strlen(payload));
Serial.println();
#endif
@@ -214,9 +216,9 @@ bool HAMqtt::beginPublish(
)
{
#if defined(ARDUINOHA_DEBUG)
Serial.print(F("Publishing message with topic: "));
Serial.print(F("Publishing: "));
Serial.print(topic);
Serial.print(F(", payload length: "));
Serial.print(F(", len: "));
Serial.print(payloadLength);
Serial.println();
#endif
@@ -263,6 +265,10 @@ void HAMqtt::processMessage(char* topic, uint8_t* payload, uint16_t length)
Serial.println();
#endif
if (_messageCallback) {
_messageCallback(topic, payload, length);
}
for (uint8_t i = 0; i < _devicesTypesNb; i++) {
_devicesTypes[i]->onMqttMessage(topic, payload, length);
}
+28
View File
@@ -5,6 +5,7 @@
#include <IPAddress.h>
#define HAMQTT_CALLBACK(name) void (*name)()
#define HAMQTT_MESSAGE_CALLBACK(name) void (*name)(const char* topic, const uint8_t* payload, uint16_t length)
#define HAMQTT_DEFAULT_PORT 1883
class PubSubClient;
@@ -25,6 +26,8 @@ public:
* Sets prefix for Home Assistant discovery.
* It needs to match prefix set in the HA admin panel.
* The default prefix is "homeassistant".
*
* @param prefix
*/
inline void setDiscoveryPrefix(const char* prefix)
{ _discoveryPrefix = prefix; }
@@ -35,12 +38,35 @@ public:
inline const char* getDiscoveryPrefix() const
{ return _discoveryPrefix; }
/**
* Sets prefix that will be used for topics different than discovery.
* It may be useful if you want to pass MQTT trafic through bridge.
*
* @param prefix
*/
inline void setDataPrefix(const char* prefix)
{ _dataPrefix = prefix; }
/**
* Returns data prefix.
*/
inline const char* getDataPrefix() const
{ return _dataPrefix; }
/**
* Returns instance of the device assigned to the HAMqtt class.
*/
inline HADevice const* getDevice() const
{ return &_device; }
/**
* Given callback will be called for each received message from the broker.
*
* @param callback
*/
inline void onMessage(HAMQTT_MESSAGE_CALLBACK(callback))
{ _messageCallback = callback; }
/**
* Given callback will be called each time the connection with broker is acquired.
*
@@ -197,10 +223,12 @@ private:
Client& _netClient;
HADevice& _device;
HAMQTT_MESSAGE_CALLBACK(_messageCallback);
HAMQTT_CALLBACK(_connectedCallback);
HAMQTT_CALLBACK(_connectionFailedCallback);
bool _initialized;
const char* _discoveryPrefix;
const char* _dataPrefix;
PubSubClient* _mqtt;
const char* _username;
const char* _password;
+22 -18
View File
@@ -5,11 +5,12 @@
BaseDeviceType::BaseDeviceType(
const char* componentName,
const char* name
const char* uniqueId
) :
_componentName(componentName),
_name(name),
_availability(AvailabilityDefault)
_uniqueId(uniqueId),
_availability(AvailabilityDefault),
_name(nullptr)
{
mqtt()->addDeviceType(this);
}
@@ -60,8 +61,10 @@ void BaseDeviceType::publishConfig()
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
uniqueId(),
DeviceTypeSerializer::ConfigTopic,
true,
true
);
const uint16_t& dataLength = calculateSerializedLength(serializedDevice);
@@ -73,8 +76,9 @@ void BaseDeviceType::publishConfig()
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
name(),
DeviceTypeSerializer::ConfigTopic
uniqueId(),
DeviceTypeSerializer::ConfigTopic,
true
);
if (strlen(topic) == 0) {
@@ -96,15 +100,15 @@ void BaseDeviceType::publishAvailability()
if (_availability == AvailabilityDefault ||
!mqtt()->isConnected() ||
strlen(_name) == 0 ||
strlen(_componentName) == 0 ||
strlen(uniqueId()) == 0 ||
strlen(componentName()) == 0 ||
device->isSharedAvailabilityEnabled()) {
return;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
_componentName,
_name,
componentName(),
uniqueId(),
DeviceTypeSerializer::AvailabilityTopic
);
if (topicSize == 0) {
@@ -114,8 +118,8 @@ void BaseDeviceType::publishAvailability()
char topic[topicSize];
DeviceTypeSerializer::generateTopic(
topic,
_componentName,
_name,
componentName(),
uniqueId(),
DeviceTypeSerializer::AvailabilityTopic
);
@@ -134,24 +138,24 @@ void BaseDeviceType::publishAvailability()
);
}
bool BaseDeviceType::isMyTopic(const char* topic, const char* expectedTopic)
bool BaseDeviceType::compareTopics(const char* topic, const char* expectedTopic)
{
if (topic == nullptr || expectedTopic == nullptr) {
return false;
}
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return false;
}
static const char Slash[] PROGMEM = {"/"};
// name + cmd topic + two slashes + null terminator
uint8_t suffixLength = strlen(name()) + strlen(expectedTopic) + 3;
// unique ID + cmd topic + two slashes + null terminator
uint8_t suffixLength = strlen(uniqueId()) + strlen(expectedTopic) + 3;
char suffix[suffixLength];
strcpy_P(suffix, Slash);
strcat(suffix, name());
strcat(suffix, uniqueId());
strcat_P(suffix, Slash);
strcat(suffix, expectedTopic);
+12 -5
View File
@@ -13,12 +13,12 @@ class BaseDeviceType
public:
BaseDeviceType(
const char* componentName,
const char* name
const char* uniqueId
);
virtual ~BaseDeviceType();
inline const char* name() const
{ return _name; }
inline const char* uniqueId() const
{ return _uniqueId; }
inline const char* componentName() const
{ return _componentName; }
@@ -29,6 +29,12 @@ public:
inline bool isOnline() const
{ return (_availability == AvailabilityOnline); }
inline void setName(const char* name)
{ _name = name; }
inline const char* getName() const
{ return _name; }
virtual void setAvailability(bool online);
protected:
@@ -43,12 +49,12 @@ protected:
virtual void publishConfig();
virtual void publishAvailability();
virtual bool isMyTopic(const char* topic, const char* expectedTopic);
virtual bool compareTopics(const char* topic, const char* expectedTopic);
virtual uint16_t calculateSerializedLength(const char* serializedDevice) const = 0;
virtual bool writeSerializedData(const char* serializedDevice) const = 0;
const char* const _componentName;
const char* const _name;
const char* const _uniqueId;
private:
enum Availability {
@@ -58,6 +64,7 @@ private:
};
Availability _availability;
const char* _name;
friend class HAMqtt;
};
+39 -25
View File
@@ -19,14 +19,27 @@ const char* DeviceTypeSerializer::Offline = "offline";
const char* DeviceTypeSerializer::StateOn = "ON";
const char* DeviceTypeSerializer::StateOff = "OFF";
const char* DeviceTypeSerializer::getTopicPrefix(bool isDiscoveryTopic)
{
if (!isDiscoveryTopic) {
const char* dataPrefix = HAMqtt::instance()->getDataPrefix();
if (dataPrefix != nullptr) {
return dataPrefix;
}
}
return HAMqtt::instance()->getDiscoveryPrefix();
}
uint16_t DeviceTypeSerializer::calculateTopicLength(
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator
bool includeNullTerminator,
bool isDiscoveryTopic
)
{
const char* prefix = HAMqtt::instance()->getDiscoveryPrefix();
const char* prefix = getTopicPrefix(isDiscoveryTopic);
if (prefix == nullptr) {
return 0;
}
@@ -58,10 +71,11 @@ uint16_t DeviceTypeSerializer::generateTopic(
char* output,
const char* component,
const char* objectId,
const char* suffix
const char* suffix,
bool isDiscoveryTopic
)
{
const char* prefix = HAMqtt::instance()->getDiscoveryPrefix();
const char* prefix = getTopicPrefix(isDiscoveryTopic);
if (prefix == nullptr) {
return 0;
}
@@ -104,18 +118,18 @@ uint16_t DeviceTypeSerializer::calculateNameFieldSize(const char* name)
}
uint16_t DeviceTypeSerializer::calculateUniqueIdFieldSize(
const char* name
const char* uniqueId
)
{
HADevice const* device = HAMqtt::instance()->getDevice();
if (device == nullptr || name == nullptr) {
if (device == nullptr || uniqueId == nullptr) {
return 0;
}
// Field format: ,"uniq_id":"[DEVICE ID]_[NAME]"
// Field format: ,"uniq_id":"[DEVICE ID]_[UNIQUE ID]"
return (
strlen(device->getUniqueId()) +
strlen(name) +
strlen(uniqueId) +
14 // 14 - length of the JSON decorators for this field
);
}
@@ -136,7 +150,7 @@ uint16_t DeviceTypeSerializer::calculateAvailabilityFieldSize(
const uint16_t& availabilityTopicLength = calculateTopicLength(
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->name()),
(sharedAvailability ? nullptr : dt->uniqueId()),
AvailabilityTopic,
false
);
@@ -212,10 +226,10 @@ void DeviceTypeSerializer::mqttWriteNameField(const char* name)
}
void DeviceTypeSerializer::mqttWriteUniqueIdField(
const char* name
const char* uniqueId
)
{
if (name == nullptr) {
if (uniqueId == nullptr) {
return;
}
@@ -226,13 +240,13 @@ void DeviceTypeSerializer::mqttWriteUniqueIdField(
static const char Prefix[] PROGMEM = {",\"uniq_id\":\""};
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, device->getUniqueId());
uint8_t uniqueIdLength = strlen(uniqueId) + strlen(device->getUniqueId()) + 2; // underscore + null temrinator
char finalUniqueId[uniqueIdLength];
strcpy(finalUniqueId, uniqueId);
strcat_P(finalUniqueId, CharUnderscore);
strcat(finalUniqueId, device->getUniqueId());
mqttWriteConstCharField(Prefix, uniqueId);
mqttWriteConstCharField(Prefix, finalUniqueId);
}
void DeviceTypeSerializer::mqttWriteAvailabilityField(
@@ -251,7 +265,7 @@ void DeviceTypeSerializer::mqttWriteAvailabilityField(
const uint16_t& topicSize = calculateTopicLength(
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->name()),
(sharedAvailability ? nullptr : dt->uniqueId()),
AvailabilityTopic
);
if (topicSize == 0) {
@@ -262,7 +276,7 @@ void DeviceTypeSerializer::mqttWriteAvailabilityField(
generateTopic(
availabilityTopic,
(sharedAvailability ? nullptr : dt->componentName()),
(sharedAvailability ? nullptr : dt->name()),
(sharedAvailability ? nullptr : dt->uniqueId()),
AvailabilityTopic
);
@@ -316,7 +330,7 @@ bool DeviceTypeSerializer::mqttWriteTopicField(
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
dt->componentName(),
dt->name() ,
dt->uniqueId() ,
topicSuffix
);
if (topicSize == 0) {
@@ -327,7 +341,7 @@ bool DeviceTypeSerializer::mqttWriteTopicField(
DeviceTypeSerializer::generateTopic(
topic,
dt->componentName(),
dt->name(),
dt->uniqueId(),
topicSuffix
);
@@ -351,7 +365,7 @@ bool DeviceTypeSerializer::mqttPublishMessage(
const uint16_t& topicSize = calculateTopicLength(
dt->componentName(),
dt->name(),
dt->uniqueId(),
topicSuffix
);
if (topicSize == 0) {
@@ -362,7 +376,7 @@ bool DeviceTypeSerializer::mqttPublishMessage(
generateTopic(
topic,
dt->componentName(),
dt->name(),
dt->uniqueId(),
topicSuffix
);
@@ -384,7 +398,7 @@ bool DeviceTypeSerializer::mqttSubscribeTopic(
{
const uint16_t& topicSize = calculateTopicLength(
dt->componentName(),
dt->name(),
dt->uniqueId(),
topicSuffix
);
if (topicSize == 0) {
@@ -395,7 +409,7 @@ bool DeviceTypeSerializer::mqttSubscribeTopic(
generateTopic(
topic,
dt->componentName(),
dt->name(),
dt->uniqueId(),
topicSuffix
);
+12 -6
View File
@@ -19,38 +19,44 @@ public:
static const char* StateOn;
static const char* StateOff;
static const char* getTopicPrefix(bool isDiscoveryTopic);
/**
* Calculates length of the topic with given parameters.
* Topic format: [discovery prefix]/[component]/[deviceId]/[objectId]/[suffix]
* Topic format: [prefix]/[component]/[deviceId]/[objectId]/[suffix]
*
* @param component
* @param objectId
* @param suffix
* @param includeNullTerminator
* @param isDiscoveryTopic Determines which prefix will be used for topic.
*/
static uint16_t calculateTopicLength(
const char* component,
const char* objectId,
const char* suffix,
bool includeNullTerminator = true
bool includeNullTerminator = true,
bool isDiscoveryTopic = false
);
/**
* Generates topic and saves it to the given buffer.
* Please note that size of the buffer must be calculated by `calculateTopicLength` method first.
* Topic format: [discovery prefix]/[component]/[deviceId]/[objectId]/[suffix]
* Topic format: [prefix]/[component]/[deviceId]/[objectId]/[suffix]
*
* @param output
* @param component
* @param objectId
* @param suffix
* @param includeNullTerminator
* @param isDiscoveryTopic Determines which prefix will be used for topic.
*/
static uint16_t generateTopic(
char* output,
const char* component,
const char* objectId,
const char* suffix
const char* suffix,
bool isDiscoveryTopic = false
);
static uint16_t calculateBaseJsonDataSize();
@@ -58,7 +64,7 @@ public:
const char* name
);
static uint16_t calculateUniqueIdFieldSize(
const char* name
const char* uniqueId
);
static uint16_t calculateAvailabilityFieldSize(
const BaseDeviceType* const dt
@@ -81,7 +87,7 @@ public:
const char* name
);
static void mqttWriteUniqueIdField(
const char* name
const char* uniqueId
);
static void mqttWriteAvailabilityField(
const BaseDeviceType* const dt
+17 -17
View File
@@ -6,10 +6,10 @@
#include "../HADevice.h"
HABinarySensor::HABinarySensor(
const char* name,
const char* uniqueId,
bool initialState
) :
BaseDeviceType("binary_sensor", name),
BaseDeviceType("binary_sensor", uniqueId),
_class(nullptr),
_currentState(initialState)
{
@@ -17,21 +17,21 @@ HABinarySensor::HABinarySensor(
}
HABinarySensor::HABinarySensor(
const char* name,
const char* uniqueId,
bool initialState,
HAMqtt& mqtt
) :
HABinarySensor(name, initialState)
HABinarySensor(uniqueId, initialState)
{
(void)mqtt;
}
HABinarySensor::HABinarySensor(
const char* name,
const char* uniqueId,
const char* deviceClass,
bool initialState
) :
BaseDeviceType("binary_sensor", name),
BaseDeviceType("binary_sensor", uniqueId),
_class(deviceClass),
_currentState(initialState)
{
@@ -39,19 +39,19 @@ HABinarySensor::HABinarySensor(
}
HABinarySensor::HABinarySensor(
const char* name,
const char* uniqueId,
const char* deviceClass,
bool initialState,
HAMqtt& mqtt
) :
HABinarySensor(name, deviceClass, initialState)
HABinarySensor(uniqueId, deviceClass, initialState)
{
(void)mqtt;
}
void HABinarySensor::onMqttConnected()
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return;
}
@@ -76,13 +76,13 @@ bool HABinarySensor::setState(bool state)
bool HABinarySensor::publishState(bool state)
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
@@ -93,7 +93,7 @@ bool HABinarySensor::publishState(bool state)
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::StateTopic
);
@@ -127,8 +127,8 @@ uint16_t HABinarySensor::calculateSerializedLength(
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(name());
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
@@ -136,7 +136,7 @@ uint16_t HABinarySensor::calculateSerializedLength(
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::StateTopic,
false
);
@@ -182,8 +182,8 @@ bool HABinarySensor::writeSerializedData(const char* serializedDevice) const
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _class);
}
DeviceTypeSerializer::mqttWriteNameField(name());
DeviceTypeSerializer::mqttWriteUniqueIdField(name());
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
+6 -6
View File
@@ -11,16 +11,16 @@ public:
/**
* Initializes binary sensor.
*
* @param name Name of the sensor. Recommended characters: [a-z0-9\-_]
* @param uniqueId Unique ID of the sensor. Recommended characters: [a-z0-9\-_]
* @param initialState Initial state of the sensor.
It will be published right after "config" message in order to update HA state.
*/
HABinarySensor(
const char* name,
const char* uniqueId,
bool initialState
);
HABinarySensor(
const char* name,
const char* uniqueId,
bool initialState,
HAMqtt& mqtt
); // legacy constructor
@@ -29,18 +29,18 @@ public:
* 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 uniqueId Unique ID of the sensor. Recommendes characters: [a-z0-9\-_]
* @param deviceClass Name of the class (lower case).
* @param initialState Initial state of the sensor.
It will be published right after "config" message in order to update HA state.
*/
HABinarySensor(
const char* name,
const char* uniqueId,
const char* deviceClass,
bool initialState
);
HABinarySensor(
const char* name,
const char* uniqueId,
const char* deviceClass,
bool initialState,
HAMqtt& mqtt
+286
View File
@@ -0,0 +1,286 @@
#include "HACover.h"
#ifdef ARDUINOHA_COVER
#include "../HAMqtt.h"
#include "../HADevice.h"
static const char ClosedStateStr[] PROGMEM = {"closed"};
static const char ClosingStateStr[] PROGMEM = {"closing"};
static const char OpenStateStr[] PROGMEM = {"open"};
static const char OpeningStateStr[] PROGMEM = {"opening"};
static const char StoppedStateStr[] PROGMEM = {"stopped"};
static const char OpenCommandStr[] PROGMEM = {"OPEN"};
static const char CloseCommandStr[] PROGMEM = {"CLOSE"};
static const char StopCommandStr[] PROGMEM = {"STOP"};
const char* HACover::PositionTopic = "ps";
HACover::HACover(const char* uniqueId) :
BaseDeviceType("cover", uniqueId),
_commandCallback(nullptr),
_currentState(StateUnknown),
_currentPosition(0),
_retain(false)
{
}
HACover::HACover(const char* uniqueId, HAMqtt& mqtt) :
HACover(uniqueId)
{
(void)mqtt;
}
void HACover::onMqttConnected()
{
if (strlen(uniqueId()) == 0) {
return;
}
publishConfig();
publishAvailability();
DeviceTypeSerializer::mqttSubscribeTopic(
this,
DeviceTypeSerializer::CommandTopic
);
if (!_retain) {
publishState(_currentState);
publishPosition(_currentPosition);
}
}
void HACover::onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
)
{
(void)payload;
if (compareTopics(topic, DeviceTypeSerializer::CommandTopic)) {
char cmd[length + 1];
memset(cmd, 0, sizeof(cmd));
memcpy(cmd, payload, length);
handleCommand(cmd);
}
}
bool HACover::setState(CoverState state, bool force)
{
if (!force && _currentState == state) {
return true;
}
if (publishState(state)) {
_currentState = state;
return true;
}
return false;
}
bool HACover::setPosition(int16_t position)
{
if (_currentPosition == position) {
return true;
}
if (publishPosition(position)) {
_currentPosition = position;
return true;
}
return false;
}
bool HACover::publishState(CoverState state)
{
if (strlen(uniqueId()) == 0 || state == StateUnknown) {
return false;
}
char stateStr[8];
switch (state) {
case StateClosed:
strcpy_P(stateStr, ClosedStateStr);
break;
case StateClosing:
strcpy_P(stateStr, ClosingStateStr);
break;
case StateOpen:
strcpy_P(stateStr, OpenStateStr);
break;
case StateOpening:
strcpy_P(stateStr, OpeningStateStr);
break;
case StateStopped:
strcpy_P(stateStr, StoppedStateStr);
break;
default:
return false;
}
return DeviceTypeSerializer::mqttPublishMessage(
this,
DeviceTypeSerializer::StateTopic,
stateStr
);
}
bool HACover::publishPosition(int16_t position)
{
if (strlen(uniqueId()) == 0) {
return false;
}
uint8_t digitsNb = floor(log10(position)) + 1;
char str[digitsNb + 2]; // + null terminator + negative sign
memset(str, 0, sizeof(str));
itoa(position, str, 10);
return DeviceTypeSerializer::mqttPublishMessage(
this,
PositionTopic,
str
);
}
uint16_t HACover::calculateSerializedLength(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return 0;
}
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
size += DeviceTypeSerializer::calculateRetainFieldSize(_retain);
// command topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::CommandTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: "cmd_t":"[TOPIC]"
size += topicLength + 10; // 10 - length of the JSON decorators for this field
}
// state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
DeviceTypeSerializer::StateTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: ,"stat_t":"[TOPIC]"
size += topicLength + 12; // 12 - length of the JSON decorators for this field
}
// position topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
uniqueId(),
PositionTopic,
false
);
if (topicLength == 0) {
return 0;
}
// Field format: ,"pos_t":"[TOPIC]"
size += topicLength + 11; // 11 - length of the JSON decorators for this field
}
return size; // exludes null terminator
}
bool HACover::writeSerializedData(const char* serializedDevice) const
{
if (serializedDevice == nullptr) {
return false;
}
DeviceTypeSerializer::mqttWriteBeginningJson();
// command topic
{
static const char Prefix[] PROGMEM = {"\"cmd_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::CommandTopic
);
}
// state topic
{
static const char Prefix[] PROGMEM = {",\"stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
DeviceTypeSerializer::StateTopic
);
}
// position topic
{
static const char Prefix[] PROGMEM = {",\"pos_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
PositionTopic
);
}
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
}
void HACover::handleCommand(const char* cmd)
{
if (!_commandCallback) {
return;
}
if (strcmp_P(cmd, CloseCommandStr) == 0) {
_commandCallback(CommandClose);
} else if (strcmp_P(cmd, OpenCommandStr) == 0) {
_commandCallback(CommandOpen);
} else if (strcmp_P(cmd, StopCommandStr) == 0) {
_commandCallback(CommandStop);
}
}
#endif
+113
View File
@@ -0,0 +1,113 @@
#ifndef AHA_COVER_H
#define AHA_COVER_H
#include "BaseDeviceType.h"
#ifdef ARDUINOHA_COVER
#define HACOVER_CALLBACK(name) void (*name)(CoverCommand cmd)
class HACover : public BaseDeviceType
{
public:
static const char* PositionTopic;
enum CoverState {
StateUnknown = 0,
StateClosed,
StateClosing,
StateOpen,
StateOpening,
StateStopped
};
enum CoverCommand {
CommandOpen,
CommandClose,
CommandStop
};
HACover(const char* uniqueId);
HACover(const char* uniqueId, HAMqtt& mqtt); // legacy constructor
virtual void onMqttConnected() override;
virtual void onMqttMessage(
const char* topic,
const uint8_t* payload,
const uint16_t& length
) override;
/**
* Changes state of the cover and publishes MQTT message.
* Please note that if a new value is the same as previous one,
* the MQTT message won't be published.
*
* @param state New state of the cover.
* @param force Forces to update state without comparing it to previous known state.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setState(CoverState state, bool force = false);
/**
* Sets current state of the cover without pushing the state to Home Assistant.
* This method may be useful if you want to change state before connection
* with MQTT broker is acquired.
*
* @param state New state of the cover.
*/
inline void setCurrentState(CoverState state)
{ _currentState = state; }
/**
* Changes position of the cover and publishes MQTT message.
* Please note that if a new value is the same as previous one,
* the MQTT message won't be published.
*
* @param position New position of the cover.
* @returns Returns true if MQTT message has been published successfully.
*/
bool setPosition(int16_t position);
/**
* Sets current position of the cover without pushing the value to Home Assistant.
* This method may be useful if you want to change position before connection
* with MQTT broker is acquired.
*
* @param position New position of the cover.
*/
inline void setCurrentPosition(int16_t position)
{ _currentPosition = position; }
/**
* Sets `retain` flag for commands published by Home Assistant.
* By default it's set to false.
*
* @param retain
*/
inline void setRetain(bool retain)
{ _retain = retain; }
/**
* Registers callback that will be called each time the command from HA is received.
* Please note that it's not possible to register multiple callbacks for the same covers.
*
* @param callback
*/
inline void onCommand(HACOVER_CALLBACK(callback))
{ _commandCallback = callback; }
protected:
bool publishState(CoverState state);
bool publishPosition(int16_t position);
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
bool writeSerializedData(const char* serializedDevice) const override;
void handleCommand(const char* cmd);
HACOVER_CALLBACK(_commandCallback);
CoverState _currentState;
int16_t _currentPosition;
bool _retain;
};
#endif
#endif
+88 -163
View File
@@ -4,24 +4,19 @@
#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"};
const char* HAFan::PercentageCommandTopic = {"sct"};
const char* HAFan::PercentageStateTopic = {"sst"};
HAFan::HAFan(const char* uniqueId, uint8_t features) :
BaseDeviceType("fan", uniqueId),
_features(features),
_speeds(OffSpeed | LowSpeed | MediumSpeed | HighSpeed),
_currentState(false),
_stateCallback(nullptr),
_currentSpeed(OffSpeed),
_currentSpeed(0),
_speedCallback(nullptr),
_label(nullptr),
_retain(false)
_retain(false),
_speedRangeMin(1),
_speedRangeMax(100)
{
}
@@ -34,7 +29,7 @@ HAFan::HAFan(const char* uniqueId, uint8_t features, HAMqtt& mqtt) :
void HAFan::onMqttConnected()
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return;
}
@@ -49,7 +44,7 @@ void HAFan::onMqttConnected()
if (_features & SpeedsFeature) {
DeviceTypeSerializer::mqttSubscribeTopic(
this,
SpeedCommandTopic
PercentageCommandTopic
);
}
@@ -67,15 +62,17 @@ void HAFan::onMqttMessage(
{
(void)payload;
if (isMyTopic(topic, DeviceTypeSerializer::CommandTopic)) {
if (compareTopics(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);
} else if (compareTopics(topic, PercentageCommandTopic)) {
char speedStr[length + 1];
memset(speedStr, 0, sizeof(speedStr));
memcpy(speedStr, payload, length);
int32_t speed = atoi(speedStr);
if (speed >= 0) {
setSpeed(speed);
}
}
}
@@ -98,12 +95,8 @@ bool HAFan::setState(bool state, bool force)
return false;
}
bool HAFan::setSpeed(Speed speed)
bool HAFan::setSpeed(uint16_t speed)
{
if (speed == UnknownSpeed) {
return false;
}
if (publishSpeed(speed)) {
_currentSpeed = speed;
@@ -117,24 +110,9 @@ bool HAFan::setSpeed(Speed speed)
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) {
if (strlen(uniqueId()) == 0) {
return false;
}
@@ -149,38 +127,21 @@ bool HAFan::publishState(bool state)
);
}
bool HAFan::publishSpeed(Speed speed)
bool HAFan::publishSpeed(uint16_t speed)
{
if (strlen(name()) == 0 || speed == UnknownSpeed) {
if (strlen(uniqueId()) == 0) {
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;
}
uint8_t digitsNb = floor(log10(speed)) + 1;
char str[digitsNb + 1]; // + null terminator
memset(str, 0, sizeof(str));
itoa(speed, str, 10);
return DeviceTypeSerializer::mqttPublishMessage(
this,
SpeedStateTopic,
speedStr
PercentageStateTopic,
str
);
}
@@ -192,17 +153,17 @@ uint16_t HAFan::calculateSerializedLength(const char* serializedDevice) const
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(name());
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
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(),
uniqueId(),
DeviceTypeSerializer::CommandTopic,
false
);
@@ -219,7 +180,7 @@ uint16_t HAFan::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::StateTopic,
false
);
@@ -234,12 +195,12 @@ uint16_t HAFan::calculateSerializedLength(const char* serializedDevice) const
// speeds
if (_features & SpeedsFeature) {
// command topic
// percentage command topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
SpeedCommandTopic,
uniqueId(),
PercentageCommandTopic,
false
);
@@ -247,16 +208,16 @@ uint16_t HAFan::calculateSerializedLength(const char* serializedDevice) const
return 0;
}
// Field format: ,"spd_cmd_t":"[TOPIC]"
// Field format: ,"pct_cmd_t":"[TOPIC]"
size += topicLength + 15; // 15 - length of the JSON decorators for this field
}
// state topic
// percentage state topic
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
SpeedStateTopic,
uniqueId(),
PercentageStateTopic,
false
);
@@ -264,47 +225,28 @@ uint16_t HAFan::calculateSerializedLength(const char* serializedDevice) const
return 0;
}
// Field format: ,"spd_stat_t":"[TOPIC]"
// Field format: ,"pct_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
// speed range min
if (_speedRangeMin != 1) {
uint8_t digitsNb = floor(log10(_speedRangeMin)) + 1;
// Field format: ,"spd_rng_min":[VALUE]
size += digitsNb + 15; // 15 - length of the JSON decorators for this field
}
// speed range max
if (_speedRangeMax != 100) {
uint8_t digitsNb = floor(log10(_speedRangeMax)) + 1;
// Field format: ,"spd_rng_max":[VALUE]
size += digitsNb + 15; // 15 - length of the JSON decorators for this field
}
}
return size; // 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
return size; // excludes null terminator
}
bool HAFan::writeSerializedData(const char* serializedDevice) const
@@ -337,79 +279,62 @@ bool HAFan::writeSerializedData(const char* serializedDevice) const
// speeds
if (_features & SpeedsFeature) {
// command topic
// percentage command topic
{
static const char Prefix[] PROGMEM = {",\"spd_cmd_t\":\""};
static const char Prefix[] PROGMEM = {",\"pct_cmd_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
SpeedCommandTopic
PercentageCommandTopic
);
}
// state topic
// percentage state topic
{
static const char Prefix[] PROGMEM = {",\"spd_stat_t\":\""};
static const char Prefix[] PROGMEM = {",\"pct_stat_t\":\""};
DeviceTypeSerializer::mqttWriteTopicField(
this,
Prefix,
SpeedStateTopic
PercentageStateTopic
);
}
// 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, "]");
}
// speed range min
if (_speedRangeMin != 1) {
uint8_t digitsNb = floor(log10(_speedRangeMin)) + 1;
char str[digitsNb + 1]; // + null terminator
memset(str, 0, sizeof(str));
itoa(_speedRangeMin, str, 10);
static const char Prefix[] PROGMEM = {",\"spd_rng_min\":"};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
speedStr,
str,
false
);
}
// speed range max
if (_speedRangeMax != 100) {
uint8_t digitsNb = floor(log10(_speedRangeMax)) + 1;
char str[digitsNb + 1]; // + null terminator
memset(str, 0, sizeof(str));
itoa(_speedRangeMax, str, 10);
static const char Prefix[] PROGMEM = {",\"spd_rng_max\":"};
DeviceTypeSerializer::mqttWriteConstCharField(
Prefix,
str,
false
);
}
}
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteNameField(_label);
DeviceTypeSerializer::mqttWriteUniqueIdField(name());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
+31 -26
View File
@@ -6,19 +6,21 @@
#ifdef ARDUINOHA_FAN
#define HAFAN_STATE_CALLBACK_BOOL(name) void (*name)(bool)
#define HAFAN_STATE_CALLBACK_SPEED(name) void (*name)(Speed)
#define HAFAN_STATE_CALLBACK_SPEED(name) void (*name)(uint16_t)
#define HAFAN_STATE_CALLBACK_SPEED_DEPRECATED(name) void (*name)(Speed)
class HAFan : public BaseDeviceType
{
public:
static const char* SpeedCommandTopic;
static const char* SpeedStateTopic;
static const char* PercentageCommandTopic;
static const char* PercentageStateTopic;
enum Features {
DefaultFeatures = 0,
SpeedsFeature = 1
};
// @deprecated
enum Speed {
UnknownSpeed = 0,
OffSpeed = 1,
@@ -81,27 +83,20 @@ public:
*
* @param speeds
*/
inline void setSpeeds(uint8_t speeds)
{ _speeds = speeds; }
AHA_DEPRECATED(inline void setSpeeds(uint8_t speeds))
{ (void)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);
bool setSpeed(uint16_t speed);
/**
* Returns current speed of the fan.
*/
inline Speed getSpeed() const
inline uint16_t getSpeed() const
{ return _currentSpeed; }
/**
@@ -113,13 +108,8 @@ public:
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
AHA_DEPRECATED(inline void onSpeedChanged(HAFAN_STATE_CALLBACK_SPEED_DEPRECATED(callback)))
{ (void)callback; }
/**
* Sets `retain` flag for commands published by Home Assistant.
@@ -130,21 +120,36 @@ public:
inline void setRetain(bool retain)
{ _retain = retain; }
/**
* Sets minimum range for slider in the HA panel.
*
* @param min
*/
inline void setSpeedRangeMin(uint16_t min)
{ _speedRangeMin = min; }
/**
* Sets maximum range for slider in the HA panel.
*
* @param min
*/
inline void setSpeedRangeMax(uint16_t max)
{ _speedRangeMax = max; }
protected:
bool publishState(bool state);
bool publishSpeed(Speed speed);
bool publishSpeed(uint16_t 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;
uint16_t _currentSpeed;
HAFAN_STATE_CALLBACK_SPEED(_speedCallback);
const char* _label;
bool _retain;
uint16_t _speedRangeMin;
uint16_t _speedRangeMax;
};
#endif
+31 -32
View File
@@ -44,7 +44,6 @@ HAHVAC::HAHVAC(const char* uniqueId, uint8_t features) :
_tempStep(1),
_targetTempCallback(nullptr),
_targetTemperature(__DBL_MAX__),
_label(nullptr),
_modes(
OffMode |
AutoMode |
@@ -72,7 +71,7 @@ HAHVAC::HAHVAC(
void HAHVAC::onMqttConnected()
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return;
}
@@ -98,24 +97,24 @@ void HAHVAC::onMqttMessage(
)
{
if ((_features & AuxHeatingFeature) &&
isMyTopic(topic, AuxCommandTopic)) {
compareTopics(topic, AuxCommandTopic)) {
bool state = (length == strlen(DeviceTypeSerializer::StateOn));
setAuxHeatingState(state);
} else if ((_features & AwayModeFeature) &&
isMyTopic(topic, AwayCommandTopic)) {
compareTopics(topic, AwayCommandTopic)) {
bool state = (length == strlen(DeviceTypeSerializer::StateOn));
setAwayState(state);
} else if ((_features & HoldFeature) &&
isMyTopic(topic, HoldCommandTopic)) {
compareTopics(topic, HoldCommandTopic)) {
bool state = (length == strlen(DeviceTypeSerializer::StateOn));
setHoldState(state);
} else if (isMyTopic(topic, TargetTemperatureCommandTopic)) {
} else if (compareTopics(topic, TargetTemperatureCommandTopic)) {
char src[length + 1];
memset(src, 0, sizeof(src));
memcpy(src, payload, length);
setTargetTemperature(HAUtils::strToTemp(src));
} else if (isMyTopic(topic, ModeCommandTopic)) {
} else if (compareTopics(topic, ModeCommandTopic)) {
char mode[length + 1];
memset(mode, 0, sizeof(mode));
memcpy(mode, payload, length);
@@ -279,7 +278,7 @@ bool HAHVAC::setTempStep(double tempStep)
bool HAHVAC::publishAction(Action action)
{
if (!(_features & ActionFeature) ||
strlen(name()) == 0) {
strlen(uniqueId()) == 0) {
return false;
}
@@ -323,7 +322,7 @@ bool HAHVAC::publishAction(Action action)
bool HAHVAC::publishAuxHeatingState(bool state)
{
if (!(_features & AuxHeatingFeature) ||
strlen(name()) == 0) {
strlen(uniqueId()) == 0) {
return false;
}
@@ -341,7 +340,7 @@ bool HAHVAC::publishAuxHeatingState(bool state)
bool HAHVAC::publishAwayState(bool state)
{
if (!(_features & AwayModeFeature) ||
strlen(name()) == 0) {
strlen(uniqueId()) == 0) {
return false;
}
@@ -359,7 +358,7 @@ bool HAHVAC::publishAwayState(bool state)
bool HAHVAC::publishHoldState(bool state)
{
if (!(_features & HoldFeature) ||
strlen(name()) == 0) {
strlen(uniqueId()) == 0) {
return false;
}
@@ -376,7 +375,7 @@ bool HAHVAC::publishHoldState(bool state)
bool HAHVAC::publishCurrentTemperature(double temperature)
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return false;
}
@@ -396,7 +395,7 @@ bool HAHVAC::publishCurrentTemperature(double temperature)
bool HAHVAC::publishTargetTemperature(double temperature)
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return false;
}
@@ -416,7 +415,7 @@ bool HAHVAC::publishTargetTemperature(double temperature)
bool HAHVAC::publishMode(Mode mode)
{
if (strlen(name()) == 0 || mode == UnknownMode) {
if (strlen(uniqueId()) == 0 || mode == UnknownMode) {
return false;
}
@@ -509,17 +508,17 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(_uniqueId);
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
size += DeviceTypeSerializer::calculateNameFieldSize(_label);
size += DeviceTypeSerializer::calculateRetainFieldSize(_retain);
// current temperature
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
CurrentTemperatureTopic,
false
);
@@ -536,7 +535,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
if (_features & ActionFeature) {
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
ActionTopic,
false
);
@@ -555,7 +554,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
AuxCommandTopic,
false
);
@@ -572,7 +571,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
AuxStateTopic,
false
);
@@ -592,7 +591,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
AwayCommandTopic,
false
);
@@ -609,7 +608,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
AwayStateTopic,
false
);
@@ -629,7 +628,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
HoldCommandTopic,
false
);
@@ -646,7 +645,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
HoldStateTopic,
false
);
@@ -693,7 +692,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
TargetTemperatureCommandTopic,
false
);
@@ -710,7 +709,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
TargetTemperatureStateTopic,
false
);
@@ -737,7 +736,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
ModeCommandTopic,
false
);
@@ -754,7 +753,7 @@ uint16_t HAHVAC::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
ModeStateTopic,
false
);
@@ -1066,11 +1065,11 @@ bool HAHVAC::writeSerializedData(const char* serializedDevice) const
);
}
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteNameField(_label);
DeviceTypeSerializer::mqttWriteUniqueIdField(_uniqueId);
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteEndJson();
return true;
-9
View File
@@ -258,14 +258,6 @@ public:
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)`
@@ -334,7 +326,6 @@ private:
double _tempStep;
HAHVAC_STATE_CALLBACK_DOUBLE(_targetTempCallback);
double _targetTemperature;
const char* _label;
uint8_t _modes;
HAHVAC_STATE_CALLBACK_MODE(_modeChangedCallback);
Mode _currentMode;
+13 -13
View File
@@ -8,8 +8,8 @@
#include "../HAMqtt.h"
#include "../HADevice.h"
HASensor::HASensor(const char* name) :
BaseDeviceType("sensor", name),
HASensor::HASensor(const char* uniqueId) :
BaseDeviceType("sensor", uniqueId),
_class(nullptr),
_units(nullptr),
_icon(nullptr)
@@ -17,15 +17,15 @@ HASensor::HASensor(const char* name) :
}
HASensor::HASensor(const char* name, HAMqtt& mqtt) :
HASensor(name)
HASensor::HASensor(const char* uniqueId, HAMqtt& mqtt) :
HASensor(uniqueId)
{
(void)mqtt;
}
void HASensor::onMqttConnected()
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return;
}
@@ -78,7 +78,7 @@ bool HASensor::setValue(float value, uint8_t precision)
bool HASensor::publishValue(const char* value)
{
if (strlen(name()) == 0 || value == nullptr) {
if (strlen(uniqueId()) == 0 || value == nullptr) {
return false;
}
@@ -88,7 +88,7 @@ bool HASensor::publishValue(const char* value)
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::StateTopic
);
if (topicSize == 0) {
@@ -99,7 +99,7 @@ bool HASensor::publishValue(const char* value)
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::StateTopic
);
@@ -125,15 +125,15 @@ uint16_t HASensor::calculateSerializedLength(
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(name());
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::StateTopic,
false
);
@@ -206,8 +206,8 @@ bool HASensor::writeSerializedData(const char* serializedDevice) const
);
}
DeviceTypeSerializer::mqttWriteNameField(name());
DeviceTypeSerializer::mqttWriteUniqueIdField(name());
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
+3 -3
View File
@@ -11,13 +11,13 @@ public:
/**
* Initializes binary sensor.
*
* @param name Name of the sensor. Recommendes characters: [a-z0-9\-_]
* @param uniqueId Unique ID of the sensor. Recommendes characters: [a-z0-9\-_]
*/
HASensor(
const char* name
const char* uniqueId
);
HASensor(
const char* name,
const char* uniqueId,
HAMqtt& mqtt
); // legacy constructor
+19 -14
View File
@@ -5,9 +5,10 @@
#include "../HAMqtt.h"
#include "../HADevice.h"
HASwitch::HASwitch(const char* name, bool initialState) :
BaseDeviceType("switch", name),
HASwitch::HASwitch(const char* uniqueId, bool initialState) :
BaseDeviceType("switch", uniqueId),
_stateCallback(nullptr),
_beforeStateCallback(nullptr),
_currentState(initialState),
_icon(nullptr),
_retain(false)
@@ -16,18 +17,18 @@ HASwitch::HASwitch(const char* name, bool initialState) :
}
HASwitch::HASwitch(
const char* name,
const char* uniqueId,
bool initialState,
HAMqtt& mqtt
) :
HASwitch(name, initialState)
HASwitch(uniqueId, initialState)
{
(void)mqtt;
}
void HASwitch::onMqttConnected()
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return;
}
@@ -52,7 +53,7 @@ void HASwitch::onMqttMessage(
{
(void)payload;
if (isMyTopic(topic, DeviceTypeSerializer::CommandTopic)) {
if (compareTopics(topic, DeviceTypeSerializer::CommandTopic)) {
bool state = (length == strlen(DeviceTypeSerializer::StateOn));
setState(state, true);
}
@@ -64,11 +65,15 @@ bool HASwitch::setState(bool state, bool force)
return true;
}
if (_beforeStateCallback) {
_beforeStateCallback(state, this);
}
if (publishState(state)) {
_currentState = state;
if (_stateCallback) {
_stateCallback(state, this);
_stateCallback(_currentState, this);
}
return true;
@@ -79,7 +84,7 @@ bool HASwitch::setState(bool state, bool force)
bool HASwitch::publishState(bool state)
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return false;
}
@@ -107,8 +112,8 @@ uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
uint16_t size = 0;
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
size += DeviceTypeSerializer::calculateNameFieldSize(name());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(name());
size += DeviceTypeSerializer::calculateNameFieldSize(getName());
size += DeviceTypeSerializer::calculateUniqueIdFieldSize(uniqueId());
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
size += DeviceTypeSerializer::calculateAvailabilityFieldSize(this);
size += DeviceTypeSerializer::calculateRetainFieldSize(_retain);
@@ -117,7 +122,7 @@ uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::CommandTopic,
false
);
@@ -134,7 +139,7 @@ uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
{
const uint16_t& topicLength = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::StateTopic,
false
);
@@ -194,8 +199,8 @@ bool HASwitch::writeSerializedData(const char* serializedDevice) const
}
DeviceTypeSerializer::mqttWriteRetainField(_retain);
DeviceTypeSerializer::mqttWriteNameField(name());
DeviceTypeSerializer::mqttWriteUniqueIdField(name());
DeviceTypeSerializer::mqttWriteNameField(getName());
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
DeviceTypeSerializer::mqttWriteEndJson();
+14 -10
View File
@@ -13,16 +13,16 @@ public:
/**
* Initializes switch.
*
* @param name Name of the switch. Recommendes characters: [a-z0-9\-_]
* @param uniqueId Unique ID of the switch. Recommendes characters: [a-z0-9\-_]
* @param initialState Initial state of the switch.
It will be published right after "config" message in order to update HA state.
*/
HASwitch(
const char* name,
const char* uniqueId,
bool initialState
);
HASwitch(
const char* name,
const char* uniqueId,
bool initialState,
HAMqtt& mqtt
); // legacy constructor
@@ -42,12 +42,6 @@ public:
const uint16_t& length
) override;
/**
* Returns name of the switch assigned via constructor.
*/
inline const char* getName() const
{ return _name; }
/**
* Changes state of the switch and publishes MQTT message.
* Please note that if a new value is the same as previous one,
@@ -79,7 +73,7 @@ public:
{ return _currentState; }
/**
* Registers callback that will be called each time the value of the switch changes.
* Registers callback that will be called each time the state of the switch changes.
* Please note that it's not possible to register multiple callbacks for the same switch.
*
* @param callback
@@ -87,6 +81,15 @@ public:
inline void onStateChanged(HASWITCH_CALLBACK(callback))
{ _stateCallback = callback; }
/**
* Registers callback that will be called before state of the switch changes.
* The state passed to callback is a new state that is going to be set.
*
* @param callback
*/
inline void onBeforeStateChanged(HASWITCH_CALLBACK(callback))
{ _beforeStateCallback = callback; }
/**
* Sets icon of the switch, e.g. `mdi:home`.
*
@@ -110,6 +113,7 @@ private:
bool writeSerializedData(const char* serializedDevice) const override;
HASWITCH_CALLBACK(_stateCallback);
HASWITCH_CALLBACK(_beforeStateCallback);
bool _currentState;
const char* _icon;
bool _retain;
+9 -9
View File
@@ -5,21 +5,21 @@
#include "../HAMqtt.h"
#include "../HADevice.h"
HATagScanner::HATagScanner(const char* name) :
BaseDeviceType("tag", name)
HATagScanner::HATagScanner(const char* uniqueId) :
BaseDeviceType("tag", uniqueId)
{
}
HATagScanner::HATagScanner(const char* name, HAMqtt& mqtt) :
HATagScanner(name)
HATagScanner::HATagScanner(const char* uniqueId, HAMqtt& mqtt) :
HATagScanner(uniqueId)
{
(void)mqtt;
}
void HATagScanner::onMqttConnected()
{
if (strlen(name()) == 0) {
if (strlen(uniqueId()) == 0) {
return;
}
@@ -28,13 +28,13 @@ void HATagScanner::onMqttConnected()
bool HATagScanner::tagScanned(const char* tag)
{
if (tag == nullptr || strlen(tag) == 0 || strlen(name()) == 0) {
if (tag == nullptr || strlen(tag) == 0 || strlen(uniqueId()) == 0) {
return false;
}
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::EventTopic
);
if (topicSize == 0) {
@@ -45,7 +45,7 @@ bool HATagScanner::tagScanned(const char* tag)
DeviceTypeSerializer::generateTopic(
topic,
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::EventTopic
);
@@ -72,7 +72,7 @@ uint16_t HATagScanner::calculateSerializedLength(
{
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
componentName(),
name(),
uniqueId(),
DeviceTypeSerializer::EventTopic,
false
);
+3 -3
View File
@@ -11,10 +11,10 @@ public:
/**
* Initializes tag scanner with the given name.
*
* @param name Name of the scanner. Recommendes characters: [a-z0-9\-_]
* @param uniqueId Unique ID of the scanner. Recommendes characters: [a-z0-9\-_]
*/
HATagScanner(const char* name);
HATagScanner(const char* name, HAMqtt& mqtt); // legacy constructor
HATagScanner(const char* uniqueId);
HATagScanner(const char* uniqueId, HAMqtt& mqtt); // legacy constructor
/**
* Publishes configuration of the sensor to the MQTT.
+15 -8
View File
@@ -132,7 +132,9 @@ void HATriggers::publishConfig()
const uint16_t& topicLength = calculateTopicLength(
componentName(),
trigger,
DeviceTypeSerializer::ConfigTopic
DeviceTypeSerializer::ConfigTopic,
true,
true
);
const uint16_t& dataLength = calculateSerializedLength(
trigger,
@@ -147,7 +149,8 @@ void HATriggers::publishConfig()
topic,
componentName(),
trigger,
DeviceTypeSerializer::ConfigTopic
DeviceTypeSerializer::ConfigTopic,
true
);
if (strlen(topic) == 0) {
@@ -165,15 +168,17 @@ uint16_t HATriggers::calculateTopicLength(
const char* component,
const HATrigger *trigger,
const char* suffix,
bool includeNullTerminator
bool includeNullTerminator,
bool isDiscoveryTopic
) const
{
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // + underscore and slash
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // underscore and slash
return DeviceTypeSerializer::calculateTopicLength(
component,
nullptr,
suffix,
includeNullTerminator
includeNullTerminator,
isDiscoveryTopic
) + length;
}
@@ -181,11 +186,12 @@ uint16_t HATriggers::generateTopic(
char* output,
const char* component,
const HATrigger *trigger,
const char* suffix
const char* suffix,
bool isDiscoveryTopic
) const
{
static const char Underscore[] PROGMEM = {"_"};
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // slash + null terminator
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // underscore + null terminator
char objectId[length];
strcpy(objectId, trigger->subtype);
@@ -196,7 +202,8 @@ uint16_t HATriggers::generateTopic(
output,
component,
objectId,
suffix
suffix,
isDiscoveryTopic
);
}
+4 -2
View File
@@ -42,14 +42,16 @@ protected:
const char* component,
const HATrigger *trigger,
const char* suffix,
bool includeNullTerminator = true
bool includeNullTerminator = true,
bool isDiscoveryTopic = false
) const;
uint16_t generateTopic(
char* output,
const char* component,
const HATrigger *trigger,
const char* suffix
const char* suffix,
bool isDiscoveryTopic = false
) const;
private: