Compare commits

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