Add Home Assistant controls over MQTT
Upgrade ArduinoHA from 1.3.0 to 2.1.0 and rewrite mqtt.ino for it. The clock now shows up in Home Assistant (discovery prefix "homeassistant") with: - switches Tubes (blank the display, runtime only) and Auto dim - numbers Day/Night brightness - light Backlight (on/off, brightness, RGB mapped to the colour wheel) - select LED effect, button Cathode protect - sensors Light (LDR) and WiFi signal, plus the upstream sensors when the clock has them; master-clock subscriptions are kept Settings changed from HA are saved to EEPROM 3 s after the last change. Fixes the unbounded memcpy in the MQTT message handler. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0d3e878051
commit
9a7061383c
108 files changed
+11482
-6567
No files matched your search
@@ -90,5 +90,8 @@ GPIO0 to GND, tap RST to GND, then release GPIO0.
|
||||
2. Skipped (the disassembly was enough): bit-stepping test sketch.
|
||||
3. Done: 74HC595-multiplex driver (`nixie595.ino`) + CLOCK_90 in ESP_UniClock, ArduinoOTA,
|
||||
EU DST fix. Runs on the clock.
|
||||
4. Next: Home Assistant controls over MQTT (display on/off, brightness, LEDs; upstream MQTT
|
||||
only publishes sensors), OTA password via `secrets.h`.
|
||||
4. Done: Home Assistant over MQTT (broker 10.1.1.175, discovery prefix `homeassistant`).
|
||||
`mqtt.ino` rewritten for ArduinoHA 2.1.0: switches Tubes/Auto dim, numbers Day/Night
|
||||
brightness, light Backlight (on/off, brightness, RGB -> UniClock colour wheel), select LED
|
||||
effect, button Cathode protect, sensors Light/WiFi signal. Broker settings are read at boot.
|
||||
5. Open: OTA password via `secrets.h`; ArduinoOTA after the AP -> WiFi switch.
|
||||
@@ -247,6 +247,9 @@ DNSServer dnsServer;
|
||||
#ifdef USE_ARDUINO_OTA
|
||||
#include <ArduinoOTA.h>
|
||||
#endif
|
||||
#ifdef USE_MQTT
|
||||
#include <ArduinoHA.h> //here and not only in mqtt.ino: PlatformIO's generated prototypes need its types early
|
||||
#endif
|
||||
|
||||
#if defined(USE_NEOPIXEL) || defined(WORDCLOCK)
|
||||
#include <NeoPixelBrightnessBus.h>
|
||||
@@ -338,6 +341,7 @@ volatile boolean EEPROMsaving = false; //saving in progress - stop display refre
|
||||
bool colonBlinkState = false;
|
||||
boolean radarON = true;
|
||||
boolean mqttRadarON = true;
|
||||
volatile boolean tubesOn = true; //Tubes switch from Home Assistant (runtime only, not saved); honoured by nixie595.ino
|
||||
unsigned long radarLastOn = 0;
|
||||
boolean makeFirmwareUpdate = false;
|
||||
boolean makeCathodeProtect = false;
|
||||
|
||||
@@ -141,6 +141,8 @@ byte tubePixels[] = {0, 0, 1, 1, 2, 2, 3, 3}; //8 WS2812, two per tube (strip
|
||||
#define AP_NAME "UNICLOCK"
|
||||
#define AP_PASSWORD ""
|
||||
#define WEBNAME "IN-12 Nixie Clock"
|
||||
#define USE_MQTT //Home Assistant: broker address/user/password on the web page, then reboot
|
||||
#define MQTT_PREFIX "homeassistant" //Home Assistant's default discovery prefix
|
||||
#define FIRMWARE_SERVER "" //no web-triggered HTTP update; use ArduinoOTA
|
||||
#define USE_ARDUINO_OTA
|
||||
#define OTA_HOSTNAME "nixie-in12"
|
||||
|
||||
@@ -2,127 +2,107 @@
|
||||
//=========================================================================================================================
|
||||
// mqtt.ino - Home Assistant / MQTT integration (compiled only if USE_MQTT is defined for the active CLOCK_NN in clocks.h)
|
||||
//
|
||||
// Library: ArduinoHA (https://github.com/dawidchyrzynski/arduino-home-assistant), which wraps PubSubClient
|
||||
// (https://pubsubclient.knolleary.net/) and adds Home Assistant MQTT Discovery on top of it. Because ArduinoHA
|
||||
// is used, this file never hand-builds HA discovery JSON payloads itself - HADevice/HAMqtt/HASensor below
|
||||
// publish the discovery config topics automatically the first time mqtt.begin()/mqtt.loop() connects.
|
||||
// Library: ArduinoHA 2.x (https://github.com/dawidchyrzynski/arduino-home-assistant), which wraps PubSubClient
|
||||
// (https://pubsubclient.knolleary.net/) and adds Home Assistant MQTT Discovery on top of it. The HA* objects
|
||||
// below publish their discovery config automatically whenever the broker connection is (re)established.
|
||||
// (Rewritten for ArduinoHA 2.1: the bundled 1.3 had no light/number/select/button entities.)
|
||||
//
|
||||
// MASTER / SLAVE sensor-sharing architecture (the distinctive part of this subsystem):
|
||||
// Entities (all grouped under one HA device, unique IDs prefixed with the MAC address):
|
||||
// Sensors: temperature/humidity/pressure/lux/radar (whichever exist on this clock), WiFi signal
|
||||
// Controls: Tubes (switch, runtime only), Day/Night brightness (numbers), Auto dim (switch),
|
||||
// Cathode protect (button); with USE_NEOPIXEL also Backlight (light: on/off, brightness, colour)
|
||||
// and LED effect (select).
|
||||
// Changes made on the clock's own web page are reported back to HA by mqttSend().
|
||||
// Settings changed from HA are saved to EEPROM a few seconds after the last change (one flash write per burst).
|
||||
//
|
||||
// MASTER / SLAVE sensor-sharing architecture:
|
||||
// Every clock that defines USE_MQTT connects to the same broker (prm.mqttBrokerAddr/User/Psw, set on the web UI) and
|
||||
// PUBLISHES its own local sensors (temperature/humidity/pressure/lux/radar, whichever are physically present - see
|
||||
// HASensor objects below) under a "<MQTT_PREFIX>/sensor/<mac>/..." style topic, discovered via HA MQTT Discovery.
|
||||
// A clock that does NOT have its own sensor (or wants to reuse another clock's reading) can instead be built with
|
||||
// USE_MASTER_CLOCK, and SUBSCRIBE to another clock's already-published topics:
|
||||
// PUBLISHES its own local sensors. A clock that does NOT have its own sensor (or wants to reuse another clock's
|
||||
// reading) can instead be built with USE_MASTER_CLOCK, and SUBSCRIBE to another clock's already-published topics:
|
||||
// USE_MASTER_TEMP + MASTER_TEMPERATURE_TOPIC -> mirrors the master's temperature into local temperature[] array
|
||||
// USE_MASTER_HUMID + MASTER_HUMIDITY_TOPIC -> mirrors the master's humidity into local humid[] array
|
||||
// USE_MASTER_RADAR + MASTER_RADAR_TOPIC -> mirrors the master's PIR/radar presence state into mqttRadarON
|
||||
// This lets one "master" clock's physical sensor (e.g. a dedicated sensor box, or a clock with a real radar/PIR
|
||||
// sensor wired to RADAR_PIN) feed several "slave" clocks that have no sensor of their own, purely over MQTT.
|
||||
// IMPORTANT: if a "slave" clock DOES also have its own local RADAR_PIN wired, the local pin always wins - the radar
|
||||
// topic subscription is skipped entirely in onMqttConnected() (see RADAR_PIN>=0 check there), and the core loop's
|
||||
// checkTubePowerOnOff() also prefers digitalRead(RADAR_PIN) over the MQTT-derived mqttRadarON when both exist.
|
||||
// As of the current clocks.h: CLOCK_99 ("Dummy clock, sensor box with MQTT connector") is a pure MASTER/publisher
|
||||
// (USE_MQTT on, USE_MASTER_CLOCK off, has its own RADAR_PIN). CLOCK_26 has USE_MQTT + USE_MASTER_CLOCK +
|
||||
// USE_MASTER_RADAR all on, so it both publishes its own temperature/humidity sensors AND subscribes to another
|
||||
// clock's radar topic. CLOCK_23/CLOCK_23A define the USE_MASTER_* macros too but currently have USE_MQTT commented
|
||||
// out in clocks.h, so for those configs this whole file is compiled out and the master settings are presently inert.
|
||||
// If a "slave" clock DOES also have its own local RADAR_PIN wired, the local pin always wins - the radar topic
|
||||
// subscription is skipped in onMqttConnected(), and checkTubePowerOnOff() prefers digitalRead(RADAR_PIN).
|
||||
// NOTE: the topic layout changed with ArduinoHA 2.x: sensor values are now published on
|
||||
// "<data prefix>/<device id>/<entity id>/stat_t" (data prefix "aha" by default), so MASTER_*_TOPIC settings
|
||||
// written for the old library must be updated.
|
||||
//
|
||||
// Call pattern from the core .ino: setupMqtt() is called once from setup() when USE_MQTT is defined; mqttSend() is
|
||||
// called every loop() iteration (but only while WiFi is connected and prm.mqttEnable is set) - it internally rate-limits
|
||||
// actual publishes to once every prm.mqttBrokerRefresh seconds, while still pumping mqtt.loop() (which also drives
|
||||
// PubSubClient's/ArduinoHA's own reconnect handling) on every call.
|
||||
// Call pattern from the core .ino: setupMqtt() is called once from setup() when USE_MQTT is defined (the broker
|
||||
// address/user/password are read then, so a change on the web UI needs a reboot); mqttSend() is called every loop()
|
||||
// iteration while WiFi is connected and prm.mqttEnable is set. It pumps mqtt.loop() on every call and refreshes
|
||||
// sensor values every prm.mqttBrokerRefresh seconds (at least every MQTT_MIN_REFRESH seconds).
|
||||
//_______________________________________________________________________________________________________________________
|
||||
//To use MQTT services the following settings should be defined in clocks.h
|
||||
//#define USE_MQTT
|
||||
//#define MQTT_PREFIX "UniClock32"
|
||||
//#define MQTT_PREFIX "homeassistant" //HA discovery prefix (Home Assistant's default is "homeassistant")
|
||||
//#define USE_MASTER_CLOCK //enable it, if you want to get any data from MASTER CLOCK. This will be the sensor#0
|
||||
//#define USE_MASTER_TEMP //enable it, if you want to get temperature from MASTER CLOCK
|
||||
//#define USE_MASTER_HUMID //enable it, if you want to get humidity from MASTER CLOCK
|
||||
//#define USE_MASTER_RADAR //enable it, if you want to get radar from MASTER CLOCK
|
||||
//#define MASTER_TEMPERATURE_TOPIC "homeassistant/sensor/10521c5e14c4/temperature/state"
|
||||
//#define MASTER_HUMIDITY_TOPIC "homeassistant/sensor/10521c5e14c4/humidity/state"
|
||||
//#define MASTER_RADAR_TOPIC "homeassistant/sensor/10521c5e14c4/radar/state"
|
||||
//#define USE_MASTER_RADAR //enable it, if you want to get radar state from MASTER CLOCK
|
||||
//#define MASTER_TEMPERATURE_TOPIC "..."
|
||||
//#define MASTER_HUMIDITY_TOPIC "..."
|
||||
//#define MASTER_RADAR_TOPIC "..."
|
||||
//_______________________________________________________________________________________________________________________
|
||||
// (the block above is documentation only, shown here as a template of what to copy into a CLOCK_NN section of clocks.h -
|
||||
// none of these #define lines are active in this file; the real values come from clocks.h at compile time.)
|
||||
|
||||
//To subscribe to my sensors: You can check it with Chrome browser + MQTTlens addon) MAC_ADDRESS: for example: 3e090b3e090b
|
||||
// homeassistant/sensor/3e090b3e090b/temperature/value
|
||||
// homeassistant/sensor/3e090b3e090b/humidity/value
|
||||
// homeassistant/sensor/3e090b3e090b/pressure/value
|
||||
// homeassistant/sensor/3e090b3e090b/lux/value
|
||||
|
||||
// old solution, now moved to prm.mqttBrokerAddr, available on clock's web page
|
||||
//#define BROKER_ADDR IPAddress(192,168,1,241)
|
||||
//#define BROKER_USERNAME "" //moved to prm.mqttBrokerUser
|
||||
//#define BROKER_PASSWORD "" //moved to mqttBrokerPsw
|
||||
//_______________________________________________________________________________________________________________________
|
||||
// Credential handling: broker IP/user/password are NOT compile-time constants (see the commented-out BROKER_* macros
|
||||
// above, kept only as historical documentation) - they live in prm.mqttBrokerAddr/mqttBrokerUser/mqttBrokerPsw, which
|
||||
// are persisted settings editable from the clock's own web UI (and read here in setupMqtt()/mqttSend()). This lets a
|
||||
// user point any already-flashed clock at a different broker without recompiling.
|
||||
// SECURITY: factoryReset() in the core .ino seeds these with a hardcoded default
|
||||
// (strcpy(prm.mqttBrokerAddr,"10.10.0.202"); strcpy(prm.mqttBrokerUser,"mqtt"); strcpy(prm.mqttBrokerPsw,"mqtt")) -
|
||||
// i.e. a real broker IP and a trivial user/password baked into the firmware as the out-of-the-box default. Not this
|
||||
// file's code, but directly relevant here since it is exactly the credential pair mqttSend()/setupMqtt() use below.
|
||||
|
||||
#include <ArduinoHA.h> //https://github.com/dawidchyrzynski/arduino-home-assistant
|
||||
//https://pubsubclient.knolleary.net/ https://github.com/knolleary/pubsubclient
|
||||
|
||||
#if !defined(MQTT_PREFIX)
|
||||
#define MQTT_PREFIX "UniClock32" //used both as the HA discovery prefix and as the first path segment of this device's own published topics
|
||||
#define MQTT_PREFIX "UniClock32" //HA discovery prefix (upstream default; Home Assistant itself listens on "homeassistant")
|
||||
#endif
|
||||
#define MQTT_MIN_REFRESH 10 //seconds between sensor refreshes if prm.mqttBrokerRefresh is lower
|
||||
#define MQTT_SAVE_DELAY 3000 //ms after the last change from HA before the settings are written to EEPROM
|
||||
#define MQTT_WHITE_INDEX 192 //same as WHITE_INDEX in z_neopixel.ino (that file is compiled after this one)
|
||||
|
||||
byte mac[6]; //this device's WiFi MAC, used as a per-device unique ID/topic segment (see setupMqtt())
|
||||
char masterTemperatureTopic[80] = ""; //resolved from MASTER_TEMPERATURE_TOPIC (clocks.h) if USE_MASTER_CLOCK/USE_MASTER_TEMP is used - topic this clock SUBSCRIBES to
|
||||
char masterHumidityTopic[80] = ""; //resolved from MASTER_HUMIDITY_TOPIC (clocks.h) if USE_MASTER_CLOCK/USE_MASTER_HUMID is used - topic this clock SUBSCRIBES to
|
||||
char masterRadarTopic[80] = ""; //resolved from MASTER_RADAR_TOPIC (clocks.h) if USE_MASTER_CLOCK/USE_MASTER_RADAR is used - topic this clock SUBSCRIBES to
|
||||
|
||||
// Index into the shared temperature[]/humid[] arrays (declared in the core .ino, up to 6 slots) reserved for the
|
||||
// MASTER clock's mirrored reading, so the rest of the firmware (display rotation, web UI, etc.) can treat the
|
||||
// MQTT-sourced master reading exactly like one more local sensor. Assigned in setupMqtt() as "next free slot"
|
||||
// (useTemp/useHumid BEFORE incrementing) - despite the doc comment above saying "this will be the sensor#0", it is
|
||||
// only actually sensor#0 if this clock has no other local temperature/humidity sensor already counted.
|
||||
byte mac[6]; //this device's WiFi MAC, used as the HA device ID
|
||||
char masterTemperatureTopic[80] = ""; //resolved from MASTER_TEMPERATURE_TOPIC (clocks.h) - topic this clock SUBSCRIBES to
|
||||
char masterHumidityTopic[80] = ""; //resolved from MASTER_HUMIDITY_TOPIC (clocks.h) - topic this clock SUBSCRIBES to
|
||||
char masterRadarTopic[80] = ""; //resolved from MASTER_RADAR_TOPIC (clocks.h) - topic this clock SUBSCRIBES to
|
||||
byte MASTERtempPtr=0;
|
||||
byte MASTERhumidPtr=0;
|
||||
char mqttConfigUrl[40];
|
||||
unsigned long mqttSaveRequested = 0; //millis() of the last change from HA that still has to be saved, 0 = none
|
||||
|
||||
WiFiClient client;
|
||||
HADevice device;
|
||||
HAMqtt mqtt(client, device); //ArduinoHA wrapper around PubSubClient; mqtt.loop() also drives PubSubClient's own reconnect/keepalive handling
|
||||
//Sensors of local clock, to send data (each one auto-publishes its HA MQTT Discovery config the first time it connects):
|
||||
HASensor mqttTemp("temperature");
|
||||
HASensor mqttHumid("humidity");
|
||||
HASensor mqttPress("pressure");
|
||||
HASensor mqttLux("lux");
|
||||
HASensor mqttRadar("radar");
|
||||
HAMqtt mqtt(client, device, 20); //up to 20 entities
|
||||
|
||||
//Sensors that only some clocks have are created in setupMqtt(): every constructed entity registers itself with
|
||||
//mqtt and is published to HA, so unused ones would show up as empty entities.
|
||||
HASensorNumber* mqttTemp = nullptr;
|
||||
HASensorNumber* mqttHumid = nullptr;
|
||||
HASensorNumber* mqttPress = nullptr;
|
||||
HASensorNumber* mqttLux = nullptr;
|
||||
HABinarySensor* mqttRadar = nullptr;
|
||||
HASensorNumber mqttRssi("rssi");
|
||||
HASwitch mqttTubes("tubes");
|
||||
HANumber mqttDayBright("day_brightness");
|
||||
HANumber mqttNightBright("night_brightness");
|
||||
HASwitch mqttAutoDim("auto_dim");
|
||||
HAButton mqttCathodeProtect("cathode_protect");
|
||||
#ifdef USE_NEOPIXEL
|
||||
HALight mqttBacklight("backlight", HALight::BrightnessFeature | HALight::RGBFeature);
|
||||
HASelect mqttLedEffect("led_effect");
|
||||
byte mqttLastEffect = 2; //effect restored when the backlight is switched on from HA (rainbow flow by default)
|
||||
#endif
|
||||
|
||||
//Bounded copy of an MQTT payload into a zero-terminated buffer.
|
||||
// FIXED: the old onMqttMessage() memcpy'd the payload into a 100-byte stack buffer without a length check or a
|
||||
// terminator (possible stack overflow with a long payload).
|
||||
static void payloadToString(const uint8_t* payload, uint16_t length, char* buf, size_t bufSize) {
|
||||
size_t n = min((size_t)length, bufSize - 1);
|
||||
memcpy(buf, payload, n);
|
||||
buf[n] = 0;
|
||||
}
|
||||
|
||||
// This is the SLAVE side of the master/slave architecture: called by ArduinoHA/PubSubClient whenever a message
|
||||
// arrives on any topic this clock is subscribed to (see onMqttConnected() below for what gets subscribed - only
|
||||
// the MASTER_* topics selected via USE_MASTER_TEMP/USE_MASTER_HUMID/USE_MASTER_RADAR in clocks.h). The payload is
|
||||
// the raw value published by the MASTER clock's own mqttSend()/HASensor.setValue() (a plain ASCII number for
|
||||
// temperature/humidity, "0"/"1" for radar - matching HASensor's default text encoding).
|
||||
void onMqttMessage(const char* topic, const uint8_t* payload, uint16_t length) {
|
||||
float tempTMP,humidTMP;
|
||||
char tmp[100];
|
||||
// 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) { ... }
|
||||
char tmp[32];
|
||||
|
||||
payloadToString(payload, length, tmp, sizeof(tmp));
|
||||
DPRINT("New message on topic: "); DPRINT(topic);
|
||||
|
||||
// POSSIBLE BUG: `length` (the broker-reported payload size) is copied into the fixed 100-byte `tmp` buffer with
|
||||
// no bounds check - a payload of 100 bytes or more (e.g. a misconfigured/malicious publisher on the same topic)
|
||||
// overflows `tmp` on the stack. Also, memcpy() does NOT null-terminate `tmp`: it is used afterwards as a C string
|
||||
// (DPRINTLN(tmp), atof(tmp), tmp[0]) with whatever byte happens to follow the copied data in memory, which is
|
||||
// undefined behavior if that byte isn't already '\0'. A `if (length >= sizeof(tmp)) length = sizeof(tmp)-1;`
|
||||
// guard plus `tmp[length] = '\0';` after the memcpy would fix both issues.
|
||||
memcpy(tmp,payload,length);
|
||||
DPRINT(" Data: "); DPRINTLN(tmp);
|
||||
// Topic match: compares up to sizeof(masterTemperatureTopic) (80, the fixed buffer size) bytes rather than the
|
||||
// actual topic length. This still behaves like strcmp() in practice because strncmp() stops as soon as it hits
|
||||
// the '\0' terminator on either side, but it is fragile - if MASTER_TEMPERATURE_TOPIC in clocks.h were ever 80
|
||||
// characters or longer, strncpy() in setupMqtt() would not null-terminate masterTemperatureTopic, and this
|
||||
// comparison could silently misbehave. Using strcmp(topic, masterTemperatureTopic) would be more robust.
|
||||
|
||||
if ((strlen(masterTemperatureTopic)>0) && (strncmp(topic,masterTemperatureTopic,sizeof(masterTemperatureTopic)) ==0)) {
|
||||
tempTMP = atof(tmp);
|
||||
temperature[MASTERtempPtr] = tempTMP; //overwrite the reserved "master" slot in the shared temperature[] array (see MASTERtempPtr above)
|
||||
@@ -134,12 +114,8 @@ void onMqttMessage(const char* topic, const uint8_t* payload, uint16_t length) {
|
||||
humid[MASTERhumidPtr] = humidTMP; //overwrite the reserved "master" slot in the shared humid[] array (see MASTERhumidPtr above)
|
||||
}
|
||||
if ((strlen(masterRadarTopic)>0) && (strncmp(topic,masterRadarTopic,sizeof(masterRadarTopic)) ==0)) {
|
||||
// Master clock's radar/PIR presence state, mirrored into mqttRadarON (and radarON) so the rest of the
|
||||
// firmware (night animation, tube power on/off, etc.) reacts to it exactly like a local sensor reading.
|
||||
// We only ever get here if this clock did NOT subscribe to the radar topic due to having its own local
|
||||
// RADAR_PIN (see the RADAR_PIN>=0 guard in onMqttConnected() below) - local hardware always wins.
|
||||
DPRINT("MASTER radar:"); DPRINTLN(tmp[0]);
|
||||
if (tmp[0] == '0') {
|
||||
if ((tmp[0] == '0') || (strcmp(tmp, "OFF") == 0)) {
|
||||
mqttRadarON = false;
|
||||
radarON = mqttRadarON;
|
||||
}
|
||||
@@ -149,17 +125,11 @@ void onMqttMessage(const char* topic, const uint8_t* payload, uint16_t length) {
|
||||
radarLastOn = millis();
|
||||
}
|
||||
DPRINT("mqttRadarON:"); DPRINTLN(mqttRadarON);
|
||||
|
||||
}
|
||||
//mqtt.publish("myPublishTopic", "hello");
|
||||
}
|
||||
|
||||
// Called by ArduinoHA once the broker connection (re)establishes - this is where the SLAVE side actually subscribes
|
||||
// to the MASTER clock's topics. Runs again on every reconnect (not just the first connect), so subscriptions survive
|
||||
// broker restarts/WiFi drops without any extra logic needed in mqttSend().
|
||||
void onMqttConnected() {
|
||||
DPRINTLN("Connected to the broker!");
|
||||
// You can subscribe to custom topic if you need
|
||||
#ifdef USE_MASTER_CLOCK
|
||||
DPRINTLN(F("MASTER_CLOCK enabled! Subscribe to following topics:"));
|
||||
#ifdef USE_MASTER_TEMP
|
||||
@@ -181,10 +151,6 @@ void onMqttConnected() {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_MASTER_RADAR
|
||||
// WHY: if this clock has its own physical radar/PIR sensor wired to RADAR_PIN, that local reading always
|
||||
// takes priority (see checkTubePowerOnOff() in the core .ino, which prefers digitalRead(RADAR_PIN) over
|
||||
// mqttRadarON when both exist). So there is no point subscribing to the master's radar topic at all in that
|
||||
// case - onMqttMessage()'s radar branch would just be mirrored into a value nothing ever reads.
|
||||
if (RADAR_PIN>=0) {
|
||||
DPRINTLN(F("Local radar enabled! MQTT MASTER radar disabled!"));
|
||||
}
|
||||
@@ -201,65 +167,213 @@ void onMqttConnected() {
|
||||
#endif
|
||||
}
|
||||
|
||||
//--- commands from Home Assistant ---------------------------------------------------------------------------------------
|
||||
|
||||
void mqttRequestSave() {
|
||||
mqttSaveRequested = millis();
|
||||
if (mqttSaveRequested == 0) mqttSaveRequested = 1;
|
||||
}
|
||||
|
||||
void onTubesCommand(bool state, HASwitch* sender) {
|
||||
tubesOn = state;
|
||||
DPRINT("MQTT: tubes "); DPRINTLN(state ? "on" : "off");
|
||||
sender->setState(state);
|
||||
}
|
||||
|
||||
void onBrightnessCommand(HANumeric number, HANumber* sender) {
|
||||
if (!number.isSet()) return;
|
||||
int v = constrain((int)number.toInt16(), 0, MAXBRIGHTNESS);
|
||||
if (sender == &mqttDayBright) prm.dayBright = v;
|
||||
else prm.nightBright = v;
|
||||
sender->setState(v);
|
||||
mqttRequestSave();
|
||||
}
|
||||
|
||||
void onAutoDimCommand(bool state, HASwitch* sender) {
|
||||
prm.enableAutoDim = state;
|
||||
autoBrightness = state; //same as the web UI handler: takes effect at once
|
||||
sender->setState(state);
|
||||
mqttRequestSave();
|
||||
}
|
||||
|
||||
void onCathodeProtectCommand(HAButton* sender) {
|
||||
makeCathodeProtect = true; //executed from loop(), like the web UI button
|
||||
}
|
||||
|
||||
#ifdef USE_NEOPIXEL
|
||||
//Inverse of Wheel() in z_neopixel.ino: the wheel position whose hue is closest to an RGB colour (saturation and
|
||||
//brightness are ignored; grey/white maps to the white position).
|
||||
byte rgbToWheel(byte r, byte g, byte b) {
|
||||
byte lo = min(r, min(g, b));
|
||||
int q;
|
||||
if ((max(r, max(g, b)) - lo) < 16) return MQTT_WHITE_INDEX;
|
||||
r -= lo; g -= lo; b -= lo;
|
||||
if (g == 0) q = (85 * b) / (r + b); //Wheel q 0..84: (255-3k, 0, 3k)
|
||||
else if (r == 0) q = 85 + (85 * g) / (g + b); //Wheel q 85..169: (0, 3k, 255-3k)
|
||||
else q = 170 + (85 * r) / (r + g); //Wheel q 170..254: (3k, 255-3k, 0)
|
||||
q = min(q, 254);
|
||||
byte pos = 255 - q; //Wheel() inverts its argument
|
||||
if (pos == MQTT_WHITE_INDEX) pos++; //192 is reserved for white
|
||||
return pos;
|
||||
}
|
||||
|
||||
HALight::RGBColor wheelToRgb(byte pos) {
|
||||
RgbColor c = Wheel(pos);
|
||||
HALight::RGBColor color;
|
||||
color.red = c.R; color.green = c.G; color.blue = c.B;
|
||||
color.isSet = true;
|
||||
return color;
|
||||
}
|
||||
|
||||
void onBacklightStateCommand(bool state, HALight* sender) {
|
||||
if (state) {
|
||||
if (prm.rgbEffect == 0) prm.rgbEffect = (mqttLastEffect > 0) ? mqttLastEffect : 1;
|
||||
}
|
||||
else {
|
||||
if (prm.rgbEffect > 0) mqttLastEffect = prm.rgbEffect;
|
||||
prm.rgbEffect = 0;
|
||||
}
|
||||
sender->setState(state);
|
||||
mqttLedEffect.setState(prm.rgbEffect);
|
||||
mqttRequestSave();
|
||||
}
|
||||
|
||||
void onBacklightBrightnessCommand(uint8_t brightness, HALight* sender) {
|
||||
prm.rgbBrightness = brightness;
|
||||
sender->setBrightness(brightness);
|
||||
mqttRequestSave();
|
||||
}
|
||||
|
||||
void onBacklightColorCommand(HALight::RGBColor color, HALight* sender) {
|
||||
if (!color.isSet) return;
|
||||
prm.rgbFixColor = rgbToWheel(color.red, color.green, color.blue);
|
||||
prm.rgbEffect = 1; //a colour from HA means "fixed colour"
|
||||
sender->setRGBColor(wheelToRgb(prm.rgbFixColor));
|
||||
mqttLedEffect.setState(prm.rgbEffect);
|
||||
mqttRequestSave();
|
||||
}
|
||||
|
||||
void onLedEffectCommand(int8_t index, HASelect* sender) {
|
||||
if ((index < 0) || (index > 10)) return;
|
||||
prm.rgbEffect = index;
|
||||
if (index > 0) mqttLastEffect = index;
|
||||
sender->setState(index);
|
||||
mqttBacklight.setState(index > 0);
|
||||
mqttRequestSave();
|
||||
}
|
||||
#endif
|
||||
|
||||
void setupMqtt() {
|
||||
char mactmp[20];
|
||||
|
||||
WiFi.macAddress(mac);
|
||||
device.setUniqueId(mac, sizeof(mac)); //set device MAC, as unique ID
|
||||
sprintf(mactmp,"%02x%02x%02x%02x%02x%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
|
||||
device.enableExtendedUniqueIds(); //entity unique IDs = <mac>_<entity>, so several clocks don't clash in HA
|
||||
sprintf(mactmp,"%02x%02x%02x%02x%02x%02x",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
|
||||
|
||||
#if defined(MASTER_TEMPERATURE_TOPIC)
|
||||
strncpy(masterTemperatureTopic,MASTER_TEMPERATURE_TOPIC,sizeof(masterTemperatureTopic));
|
||||
#endif
|
||||
#if defined(MASTER_HUMIDITY_TOPIC)
|
||||
strncpy(masterHumidityTopic,MASTER_HUMIDITY_TOPIC,sizeof(masterHumidityTopic));
|
||||
#endif
|
||||
#endif
|
||||
#if defined(MASTER_RADAR_TOPIC)
|
||||
strncpy(masterRadarTopic,MASTER_RADAR_TOPIC,sizeof(masterRadarTopic));
|
||||
#endif
|
||||
DPRINT("Starting MQTT CLient: "); DPRINT(MQTT_PREFIX); DPRINT("/sensor/"); DPRINTLN(mactmp);
|
||||
|
||||
|
||||
DPRINT("Starting MQTT Client, discovery prefix: "); DPRINT(MQTT_PREFIX); DPRINT(" device: "); DPRINTLN(mactmp);
|
||||
mqtt.setDiscoveryPrefix(MQTT_PREFIX);
|
||||
device.setName(FW);
|
||||
device.setSoftwareVersion("3.0");
|
||||
device.setName(webName);
|
||||
device.setManufacturer("UniClock");
|
||||
device.setModel(tubeDriver);
|
||||
device.setSoftwareVersion(FW);
|
||||
snprintf(mqttConfigUrl, sizeof(mqttConfigUrl), "http://%s/", WiFi.localIP().toString().c_str());
|
||||
device.setConfigurationUrl(mqttConfigUrl);
|
||||
device.enableSharedAvailability();
|
||||
device.enableLastWill();
|
||||
|
||||
//useTemp/useHumid count the master clock's sensors too, which are added at the end of this function -
|
||||
//so as before, a master-only sensor is not republished by this clock
|
||||
if (useTemp>0) {
|
||||
mqttTemp.setUnitOfMeasurement("°C");
|
||||
mqttTemp.setDeviceClass("sensor");
|
||||
mqttTemp.setName("Inside temperature");
|
||||
mqttTemp = new HASensorNumber("temperature", HASensorNumber::PrecisionP1);
|
||||
mqttTemp->setName("Temperature");
|
||||
mqttTemp->setDeviceClass("temperature");
|
||||
mqttTemp->setUnitOfMeasurement("°C");
|
||||
}
|
||||
if (useHumid>0) {
|
||||
mqttHumid.setUnitOfMeasurement("%");
|
||||
mqttHumid.setDeviceClass("sensor");
|
||||
mqttHumid.setName("Inside humidity");
|
||||
mqttHumid = new HASensorNumber("humidity", HASensorNumber::PrecisionP1);
|
||||
mqttHumid->setName("Humidity");
|
||||
mqttHumid->setDeviceClass("humidity");
|
||||
mqttHumid->setUnitOfMeasurement("%");
|
||||
}
|
||||
if (usePress>0) {
|
||||
mqttPress.setUnitOfMeasurement("hPa");
|
||||
mqttPress.setDeviceClass("sensor");
|
||||
mqttPress.setName("Air pressure");
|
||||
mqttPress = new HASensorNumber("pressure", HASensorNumber::PrecisionP1);
|
||||
mqttPress->setName("Air pressure");
|
||||
mqttPress->setDeviceClass("pressure");
|
||||
mqttPress->setUnitOfMeasurement("hPa");
|
||||
}
|
||||
if (useLux>0) {
|
||||
mqttLux.setUnitOfMeasurement("lux");
|
||||
mqttLux.setDeviceClass("sensor");
|
||||
mqttLux.setName("Inside lux");
|
||||
mqttLux = new HASensorNumber("lux");
|
||||
mqttLux->setName("Light");
|
||||
mqttLux->setDeviceClass("illuminance");
|
||||
mqttLux->setUnitOfMeasurement("lx");
|
||||
}
|
||||
#if RADAR_PIN>=0
|
||||
mqttRadar.setUnitOfMeasurement("on/off");
|
||||
mqttRadar.setDeviceClass("sensor");
|
||||
mqttRadar.setName("Radar sensor");
|
||||
mqttRadar = new HABinarySensor("radar");
|
||||
mqttRadar->setName("Radar");
|
||||
mqttRadar->setDeviceClass("occupancy");
|
||||
#endif
|
||||
mqttRssi.setName("WiFi signal");
|
||||
mqttRssi.setDeviceClass("signal_strength");
|
||||
mqttRssi.setUnitOfMeasurement("dBm");
|
||||
|
||||
mqttTubes.setName("Tubes");
|
||||
mqttTubes.setIcon("mdi:numeric");
|
||||
mqttTubes.onCommand(onTubesCommand);
|
||||
|
||||
mqttDayBright.setName("Day brightness");
|
||||
mqttNightBright.setName("Night brightness");
|
||||
for (HANumber* n : {&mqttDayBright, &mqttNightBright}) {
|
||||
n->setIcon("mdi:brightness-6");
|
||||
n->setMin(0);
|
||||
n->setMax(MAXBRIGHTNESS);
|
||||
n->setMode(HANumber::ModeSlider);
|
||||
n->onCommand(onBrightnessCommand);
|
||||
}
|
||||
|
||||
mqttAutoDim.setName("Auto dim");
|
||||
mqttAutoDim.setIcon("mdi:brightness-auto");
|
||||
mqttAutoDim.onCommand(onAutoDimCommand);
|
||||
|
||||
mqttCathodeProtect.setName("Cathode protect");
|
||||
mqttCathodeProtect.setIcon("mdi:autorenew");
|
||||
mqttCathodeProtect.onCommand(onCathodeProtectCommand);
|
||||
|
||||
#ifdef USE_NEOPIXEL
|
||||
mqttBacklight.setName("Backlight");
|
||||
mqttBacklight.onStateCommand(onBacklightStateCommand);
|
||||
mqttBacklight.onBrightnessCommand(onBacklightBrightnessCommand);
|
||||
mqttBacklight.onRGBColorCommand(onBacklightColorCommand);
|
||||
if (prm.rgbEffect > 0) mqttLastEffect = prm.rgbEffect;
|
||||
|
||||
mqttLedEffect.setName("LED effect");
|
||||
mqttLedEffect.setIcon("mdi:led-strip-variant");
|
||||
//order = prm.rgbEffect values, see doAnimationMakuna() in z_neopixel.ino
|
||||
mqttLedEffect.setOptions("Off;Fixed colour;Rainbow flow;Rainbow step;Colour dimmer;Colour stepper;"
|
||||
"Stepflow table;Stepflow random;Stepflow random pixels;Stepflow random all;KITT");
|
||||
mqttLedEffect.onCommand(onLedEffectCommand);
|
||||
#endif
|
||||
|
||||
mqtt.onMessage(onMqttMessage);
|
||||
mqtt.onConnected(onMqttConnected);
|
||||
|
||||
|
||||
IPAddress tmp;
|
||||
tmp.fromString(String(prm.mqttBrokerAddr));
|
||||
if (strlen(prm.mqttBrokerUser)==0)
|
||||
mqtt.begin(tmp);
|
||||
else
|
||||
else
|
||||
mqtt.begin(tmp, prm.mqttBrokerUser, prm.mqttBrokerPsw);
|
||||
|
||||
//mqtt.begin(BROKER_ADDR, BROKER_USERNAME, BROKER_PASSWORD);
|
||||
|
||||
device.enableLastWill();
|
||||
#ifdef USE_MASTER_CLOCK
|
||||
DPRINTLN("MQTT MASTER_CLOCK enabled!");
|
||||
DPRINT("MASTER_TEMPERATURE_TOPIC:"); DPRINTLN(masterTemperatureTopic);
|
||||
@@ -270,37 +384,69 @@ void setupMqtt() {
|
||||
MASTERtempPtr = useTemp; //remember my ID-s
|
||||
useTemp++; //increase the number of sensors
|
||||
#endif
|
||||
#ifdef USE_MASTER_HUMID
|
||||
#ifdef USE_MASTER_HUMID
|
||||
DPRINTLN(" - MQTT MASTER_HUMID sensor added!");
|
||||
MASTERhumidPtr = useHumid;
|
||||
useHumid++;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
//Report the current settings to HA. The library only publishes values that changed, so this is cheap; it keeps
|
||||
//HA in sync with changes made on the clock's web page.
|
||||
void mqttReportState() {
|
||||
mqttTubes.setState(tubesOn);
|
||||
mqttDayBright.setState((int16_t)prm.dayBright);
|
||||
mqttNightBright.setState((int16_t)prm.nightBright);
|
||||
mqttAutoDim.setState(prm.enableAutoDim);
|
||||
#ifdef USE_NEOPIXEL
|
||||
mqttBacklight.setState(prm.rgbEffect > 0);
|
||||
mqttBacklight.setBrightness(prm.rgbBrightness);
|
||||
mqttBacklight.setRGBColor(wheelToRgb(prm.rgbFixColor));
|
||||
mqttLedEffect.setState(prm.rgbEffect);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mqttSend() {
|
||||
static unsigned long lastRun = millis();
|
||||
//if (!mqtt.isConnected()) return;
|
||||
static unsigned long lastRun = 0;
|
||||
static unsigned long lastReport = 0;
|
||||
static bool wasConnected = false;
|
||||
|
||||
mqtt.loop();
|
||||
|
||||
if ((millis() - lastRun) < prm.mqttBrokerRefresh*1000) return;
|
||||
lastRun = millis();
|
||||
if (mqtt.isConnected()) DPRINT("MQTT Connected.");
|
||||
else {
|
||||
DPRINTLN("MQTT Failed to connect.");
|
||||
|
||||
if (mqttSaveRequested && ((millis() - mqttSaveRequested) > MQTT_SAVE_DELAY)) {
|
||||
mqttSaveRequested = 0;
|
||||
DPRINTLN("MQTT: saving settings");
|
||||
saveEEPROM();
|
||||
}
|
||||
|
||||
if (!mqtt.isConnected()) {
|
||||
wasConnected = false;
|
||||
return;
|
||||
}
|
||||
if (!wasConnected) { //(re)connected: force the first sensor refresh now
|
||||
wasConnected = true;
|
||||
lastRun = millis() - 1000ul * max((int)prm.mqttBrokerRefresh, MQTT_MIN_REFRESH);
|
||||
}
|
||||
|
||||
if ((millis() - lastReport) > 1000) {
|
||||
lastReport = millis();
|
||||
mqttReportState();
|
||||
}
|
||||
|
||||
if ((millis() - lastRun) < 1000ul * max((int)prm.mqttBrokerRefresh, MQTT_MIN_REFRESH)) return;
|
||||
lastRun = millis();
|
||||
|
||||
DPRINT("MQTT send:");
|
||||
if (useTemp>0) {mqttTemp.setValue(temperature[0]+ prm.corrT0); DPRINT(temperature[0]+ prm.corrT0); DPRINT(", ");}
|
||||
if (useHumid>0) {mqttHumid.setValue(humid[0]+ prm.corrH0); DPRINT(humid[0]+ prm.corrH0); DPRINT(", ");}
|
||||
if (usePress>0) {mqttPress.setValue(pressur[0]); DPRINT(pressur[0]); DPRINT(", ");}
|
||||
if (useLux>0) {mqttLux.setValue(lx); DPRINT(lx); DPRINT(", ");}
|
||||
#if RADAR_PIN>=0
|
||||
mqttRadar.setValue(radarON); DPRINT(radarON);
|
||||
#endif
|
||||
if (mqttTemp) {mqttTemp->setValue(temperature[0]+ prm.corrT0); DPRINT(temperature[0]+ prm.corrT0); DPRINT(", ");}
|
||||
if (mqttHumid) {mqttHumid->setValue(humid[0]+ prm.corrH0); DPRINT(humid[0]+ prm.corrH0); DPRINT(", ");}
|
||||
if (mqttPress) {mqttPress->setValue(pressur[0]); DPRINT(pressur[0]); DPRINT(", ");}
|
||||
if (mqttLux) {mqttLux->setValue((int32_t)lx); DPRINT(lx); DPRINT(", ");}
|
||||
if (mqttRadar) {mqttRadar->setState(radarON); DPRINT(radarON);}
|
||||
mqttRssi.setValue((int32_t)WiFi.RSSI());
|
||||
DPRINTLN(" ");
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
void mqttSend() {}
|
||||
#endif
|
||||
@@ -128,7 +128,7 @@ void IRAM_ATTR writeDisplay() { //timer1 ISR, single-shot: must re-arm timer1
|
||||
}
|
||||
if (timer < 500) timer = 500; //a too short timer1 reload can miss the interrupt
|
||||
|
||||
if ((brightness == 0) || (state == 3) || (!radarON)) {
|
||||
if ((brightness == 0) || (state == 3) || (!radarON) || (!tubesOn)) {
|
||||
state = 0;
|
||||
w = 0;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ Peter Gautier (GPL-3.0-or-later), vendored from upstream commit `1189810` (2026-
|
||||
It keeps the upstream layout, so it still opens in the Arduino IDE.
|
||||
- `lib/`: the library versions bundled upstream (`libraries/*.zip`), only those the ESP8266 build
|
||||
needs. The upstream manuals, datasheets, photos and ESP32-only libraries were left out.
|
||||
Exception: `arduino-home-assistant` is v2.1.0 (upstream bundles 1.3.0, which has no light,
|
||||
number, select or button entities); `mqtt.ino` was rewritten for it.
|
||||
- `platformio.ini`: PlatformIO build for the ESP-12 (4 MB flash, 2 MB SPIFFS).
|
||||
|
||||
Local changes are separate commits on top of the import (`git log -- firmware/uniclock`).
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# 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
|
||||
@@ -1,85 +0,0 @@
|
||||
# Arduino Home Assistant integration
|
||||
|
||||
ArduinoHA allows to integrate an Arduino/ESP based device with Home Assistant using MQTT.
|
||||
The library is designed to use as little resources (RAM/flash) as possible.
|
||||
Initially it was optimized to work on Arduino Uno with Ethernet Shield,
|
||||
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)
|
||||
* [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
|
||||
* 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)
|
||||
|
||||
## Roadmap
|
||||
|
||||
* FAQ + Home Assistant setup instructions
|
||||
* Documentation of the library
|
||||
* Unit tests
|
||||
* Reduce flash memory usage
|
||||
* Add support for HA lights
|
||||
|
||||
## Unsupported features
|
||||
|
||||
The library doesn't support all features of the MQTT integration.
|
||||
If you need support for a new feature please open a new issue in the repository.
|
||||
|
||||
# License
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License v3.0 as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/gpl.html
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
# Home Assistant availability example
|
||||
|
||||
This example shows how to use "availability" feature of the Home Assistant.
|
||||
It's supported only by following device types:
|
||||
* Binary sensor
|
||||
* Sensor
|
||||
* Switch
|
||||
* HVACs
|
||||
* Fans
|
||||
|
||||
## Initialization and usage
|
||||
|
||||
Availability feature is turned off by default. In order to turn it on for the specific
|
||||
sensor/switch you need to set initial state of the unit by calling `setAvailability` method.
|
||||
|
||||
Example:
|
||||
```cpp
|
||||
...
|
||||
HABinarySensor sensor("input", "door", true, mqtt);
|
||||
|
||||
void setup() {
|
||||
// ...
|
||||
|
||||
// method must be called before `mqtt.begin(...)`
|
||||
sensor.setAvailability(false); // offline
|
||||
|
||||
mqtt.begin(BROKER_ADDR);
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
After availability is set for the very first time you can call `setAvailability` in any time.
|
||||
Each time the method is called, the device will publish MQTT message to the broker.
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
#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);
|
||||
|
||||
// turn on "availability" feature
|
||||
// this method also sets initial availability so you can use "true" or "false"
|
||||
sensor.setAvailability(false);
|
||||
|
||||
lastReadAt = millis();
|
||||
lastAvailabilityToggleAt = millis();
|
||||
|
||||
// set device's details (optional)
|
||||
device.setName("Arduino");
|
||||
device.setSoftwareVersion("1.0.0");
|
||||
|
||||
sensor.setName("Door sensor"); // optional
|
||||
|
||||
mqtt.begin(BROKER_ADDR);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Ethernet.maintain();
|
||||
mqtt.loop();
|
||||
|
||||
if ((millis() - lastReadAt) > 30) { // read in 30ms interval
|
||||
// library produces MQTT message if a new state is different than the previous one
|
||||
sensor.setState(digitalRead(INPUT_PIN));
|
||||
lastInputState = sensor.getState();
|
||||
lastReadAt = millis();
|
||||
}
|
||||
|
||||
if ((millis() - lastAvailabilityToggleAt) > 5000) {
|
||||
sensor.setAvailability(!sensor.isOnline());
|
||||
lastAvailabilityToggleAt = millis();
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
#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();
|
||||
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);
|
||||
|
||||
// set device's details (optional)
|
||||
device.setName("Arduino");
|
||||
device.setSoftwareVersion("1.0.0");
|
||||
|
||||
sensor.setName("Door sensor"); // optional
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#include <Ethernet.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
#define BROKER_ADDR IPAddress(192,168,0,17)
|
||||
|
||||
byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
|
||||
|
||||
EthernetClient client;
|
||||
HADevice device(mac, sizeof(mac));
|
||||
HAMqtt mqtt(client, device);
|
||||
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();
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#include <Ethernet.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
#define BROKER_ADDR IPAddress(192,168,0,17)
|
||||
|
||||
byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
|
||||
|
||||
EthernetClient client;
|
||||
HADevice device(mac, sizeof(mac));
|
||||
HAMqtt mqtt(client, device);
|
||||
|
||||
// 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();
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#include <Ethernet.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
#define LED_PIN 9
|
||||
#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);
|
||||
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)
|
||||
{
|
||||
digitalWrite(LED_PIN, (state ? HIGH : LOW));
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
|
||||
// you don't need to verify return status
|
||||
Ethernet.begin(mac);
|
||||
|
||||
// set device's details (optional)
|
||||
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);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Ethernet.maintain();
|
||||
mqtt.loop();
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
#include <Ethernet.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
#define BROKER_ADDR IPAddress(192,168,0,17)
|
||||
|
||||
byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
|
||||
|
||||
EthernetClient client;
|
||||
HADevice device(mac, sizeof(mac));
|
||||
HAMqtt mqtt(client, device);
|
||||
|
||||
void 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();
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
#include <Ethernet.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
#define LED_PIN 9
|
||||
#define BROKER_ADDR IPAddress(192,168,0,17)
|
||||
#define BROKER_USERNAME "user" // replace with your credentials
|
||||
#define BROKER_PASSWORD "pass"
|
||||
|
||||
byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
|
||||
|
||||
EthernetClient client;
|
||||
HADevice device(mac, sizeof(mac));
|
||||
HAMqtt mqtt(client, device);
|
||||
HASwitch led("led", false); // "led" is unique ID of the switch. You should define your own ID.
|
||||
|
||||
void onSwitchStateChanged(bool state, HASwitch* s)
|
||||
{
|
||||
digitalWrite(LED_PIN, (state ? HIGH : LOW));
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
|
||||
// you don't need to verify return status
|
||||
Ethernet.begin(mac);
|
||||
|
||||
// set device's details (optional)
|
||||
device.setName("Arduino");
|
||||
device.setSoftwareVersion("1.0.0");
|
||||
|
||||
// handle switch state
|
||||
led.onStateChanged(onSwitchStateChanged);
|
||||
led.setName("My LED"); // optional
|
||||
|
||||
mqtt.begin(BROKER_ADDR, BROKER_USERNAME, BROKER_PASSWORD);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Ethernet.maintain();
|
||||
mqtt.loop();
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
#include <Ethernet.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
// This example uses JC Button library
|
||||
// https://github.com/JChristensen/JC_Button
|
||||
#include <JC_Button.h>
|
||||
|
||||
#define BUTTON_PIN 9
|
||||
#define BUTTON_NAME "mybtn"
|
||||
#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);
|
||||
HATriggers triggers;
|
||||
Button btn(BUTTON_PIN);
|
||||
bool holdingBtn = false;
|
||||
|
||||
void setup() {
|
||||
// you don't need to verify return status
|
||||
Ethernet.begin(mac);
|
||||
|
||||
// set device's details (optional)
|
||||
device.setName("Arduino");
|
||||
device.setSoftwareVersion("1.0.0");
|
||||
|
||||
// setup triggers
|
||||
triggers.add("button_short_press", BUTTON_NAME);
|
||||
triggers.add("button_long_press", BUTTON_NAME);
|
||||
btn.begin();
|
||||
|
||||
mqtt.begin(BROKER_ADDR);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Ethernet.maintain();
|
||||
mqtt.loop();
|
||||
btn.read();
|
||||
|
||||
if (btn.pressedFor(3000) && !holdingBtn) {
|
||||
triggers.trigger("button_long_press", BUTTON_NAME);
|
||||
holdingBtn = true;
|
||||
} else if (btn.wasReleased()) {
|
||||
if (holdingBtn) {
|
||||
holdingBtn = false;
|
||||
} else {
|
||||
triggers.trigger("button_short_press", BUTTON_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#include <WiFiNINA.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
#define LED_PIN 9
|
||||
#define BROKER_ADDR IPAddress(192,168,0,17)
|
||||
#define WIFI_SSID "MyNetwork"
|
||||
#define WIFI_PASSWORD "MyPassword"
|
||||
|
||||
WiFiClient client;
|
||||
HADevice device;
|
||||
HAMqtt mqtt(client, device);
|
||||
HASwitch led("led", false); // "led" is unique ID of the switch. You should define your own ID.
|
||||
|
||||
void onSwitchStateChanged(bool state, HASwitch* s)
|
||||
{
|
||||
digitalWrite(LED_PIN, (state ? HIGH : LOW));
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Serial.println("Starting...");
|
||||
|
||||
// Unique ID must be set!
|
||||
byte mac[WL_MAC_ADDR_LENGTH];
|
||||
WiFi.macAddress(mac);
|
||||
device.setUniqueId(mac, sizeof(mac));
|
||||
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
|
||||
// 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("Nano 33 IoT");
|
||||
device.setSoftwareVersion("1.0.0");
|
||||
|
||||
// handle switch state
|
||||
led.onStateChanged(onSwitchStateChanged);
|
||||
led.setName("My LED"); // optional
|
||||
|
||||
mqtt.begin(BROKER_ADDR);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mqtt.loop();
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
#define LED_PIN D0
|
||||
#define BROKER_ADDR IPAddress(192,168,0,17)
|
||||
#define WIFI_SSID "MyNetwork"
|
||||
#define WIFI_PASSWORD "MyPassword"
|
||||
|
||||
WiFiClient client;
|
||||
HADevice device;
|
||||
HAMqtt mqtt(client, device);
|
||||
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)
|
||||
{
|
||||
digitalWrite(LED_PIN, (state ? HIGH : LOW));
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Serial.println("Starting...");
|
||||
|
||||
// Unique ID must be set!
|
||||
byte mac[WL_MAC_ADDR_LENGTH];
|
||||
WiFi.macAddress(mac);
|
||||
device.setUniqueId(mac, sizeof(mac));
|
||||
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
|
||||
// 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");
|
||||
|
||||
// handle switch state
|
||||
led.onBeforeStateChanged(onBeforeSwitchStateChanged); // optional
|
||||
led.onStateChanged(onSwitchStateChanged);
|
||||
led.setName("My LED"); // optional
|
||||
|
||||
mqtt.begin(BROKER_ADDR);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mqtt.loop();
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#include <Ethernet.h>
|
||||
#include <ArduinoHA.h>
|
||||
|
||||
#define BROKER_ADDR IPAddress(192,168,0,17)
|
||||
|
||||
byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
|
||||
unsigned long lastSentAt = millis();
|
||||
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.
|
||||
|
||||
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
|
||||
Ethernet.begin(mac);
|
||||
|
||||
// set device's details (optional)
|
||||
device.setName("Arduino");
|
||||
device.setSoftwareVersion("1.0.0");
|
||||
|
||||
// configure sensor (optional)
|
||||
temp.setUnitOfMeasurement("°C");
|
||||
temp.setDeviceClass("temperature");
|
||||
temp.setIcon("mdi:home");
|
||||
temp.setName("Home temperature");
|
||||
|
||||
mqtt.begin(BROKER_ADDR);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Ethernet.maintain();
|
||||
mqtt.loop();
|
||||
|
||||
if ((millis() - lastSentAt) >= 5000) {
|
||||
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,16 +0,0 @@
|
||||
#ifndef AHA_ARDUINOHA_H
|
||||
#define AHA_ARDUINOHA_H
|
||||
|
||||
#include "HADevice.h"
|
||||
#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/HASwitch.h"
|
||||
#include "device-types/HATagScanner.h"
|
||||
#include "device-types/HATriggers.h"
|
||||
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
// Turns on debug information of the ArduinoHA core.
|
||||
// Please note that you need to initialize serial interface manually
|
||||
// by calling Serial.begin([baudRate]) before initializing ArduinoHA.
|
||||
// #define ARDUINOHA_DEBUG
|
||||
|
||||
// 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
|
||||
@@ -1,198 +0,0 @@
|
||||
#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), \
|
||||
_sharedAvailability(false), \
|
||||
_availabilityTopic(nullptr), \
|
||||
_available(true) // device will be available by default
|
||||
|
||||
HADevice::HADevice() :
|
||||
_uniqueId(nullptr),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HADevice::HADevice(const char* uniqueId) :
|
||||
_uniqueId(uniqueId),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HADevice::HADevice(const byte* uniqueId, const uint16_t& length) :
|
||||
_uniqueId(HAUtils::byteArrayToStr(uniqueId, length)),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_uniqueId = HAUtils::byteArrayToStr(uniqueId, 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 =
|
||||
3 + // opening and closing bracket + null terminator
|
||||
strlen(_uniqueId) + 8; // 8 - length of the JSON data for this field
|
||||
|
||||
if (_manufacturer != nullptr) {
|
||||
size += strlen(_manufacturer) + 8; // 8 - length of the JSON data for this field
|
||||
}
|
||||
|
||||
if (_model != nullptr) {
|
||||
size += strlen(_model) + 9; // 9 - length of the JSON data for this field
|
||||
}
|
||||
|
||||
if (_name != nullptr) {
|
||||
size += strlen(_name) + 10; // 10 - length of the JSON data for this field
|
||||
}
|
||||
|
||||
if (_softwareVersion != nullptr) {
|
||||
size += strlen(_softwareVersion) + 8; // 8 - length of the JSON data for this field
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint16_t HADevice::serialize(char* dst) const
|
||||
{
|
||||
static const char QuotationSign[] PROGMEM = {"\""};
|
||||
|
||||
{
|
||||
static const char DataBefore[] PROGMEM = {"{\"ids\":\""};
|
||||
|
||||
strcpy_P(dst, DataBefore);
|
||||
strcat(dst, _uniqueId);
|
||||
strcat_P(dst, QuotationSign);
|
||||
}
|
||||
|
||||
if (_manufacturer != nullptr) {
|
||||
static const char DataBefore[] PROGMEM = {",\"mf\":\""};
|
||||
|
||||
strcat_P(dst, DataBefore);
|
||||
strcat(dst, _manufacturer);
|
||||
strcat_P(dst, QuotationSign);
|
||||
}
|
||||
|
||||
if (_model != nullptr) {
|
||||
static const char DataBefore[] PROGMEM = {",\"mdl\":\""};
|
||||
|
||||
strcat_P(dst, DataBefore);
|
||||
strcat(dst, _model);
|
||||
strcat_P(dst, QuotationSign);
|
||||
}
|
||||
|
||||
if (_name != nullptr) {
|
||||
static const char DataBefore[] PROGMEM = {",\"name\":\""};
|
||||
|
||||
strcat_P(dst, DataBefore);
|
||||
strcat(dst, _name);
|
||||
strcat_P(dst, QuotationSign);
|
||||
}
|
||||
|
||||
if (_softwareVersion != nullptr) {
|
||||
static const char DataBefore[] PROGMEM = {",\"sw\":\""};
|
||||
|
||||
strcat_P(dst, DataBefore);
|
||||
strcat(dst, _softwareVersion);
|
||||
strcat_P(dst, QuotationSign);
|
||||
}
|
||||
|
||||
{
|
||||
static const char Data[] PROGMEM = {"}"};
|
||||
strcat_P(dst, Data);
|
||||
}
|
||||
|
||||
return strlen(dst) + 1; // size with null terminator
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#ifndef AHA_HADEVICE_H
|
||||
#define AHA_HADEVICE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class HADevice
|
||||
{
|
||||
public:
|
||||
HADevice();
|
||||
HADevice(const char* uniqueId);
|
||||
HADevice(const byte* uniqueId, const uint16_t& length);
|
||||
|
||||
inline const char* getUniqueId() const
|
||||
{ return _uniqueId; }
|
||||
|
||||
inline void setManufacturer(const char* manufacturer)
|
||||
{ _manufacturer = manufacturer; }
|
||||
|
||||
inline void setModel(const char* model)
|
||||
{ _model = model; }
|
||||
|
||||
inline void setName(const char* name)
|
||||
{ _name = name; }
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
const char* _uniqueId;
|
||||
const char* _manufacturer;
|
||||
const char* _model;
|
||||
const char* _name;
|
||||
const char* _softwareVersion;
|
||||
bool _sharedAvailability;
|
||||
char* _availabilityTopic;
|
||||
bool _available;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,331 +0,0 @@
|
||||
#include <PubSubClient.h>
|
||||
|
||||
#include "HAMqtt.h"
|
||||
#include "HADevice.h"
|
||||
#include "ArduinoHADefines.h"
|
||||
#include "device-types/BaseDeviceType.h"
|
||||
|
||||
static const char* DefaultDiscoveryPrefix = "esp";
|
||||
HAMqtt* HAMqtt::_instance = nullptr;
|
||||
|
||||
void onMessageReceived(char* topic, uint8_t* payload, unsigned int length)
|
||||
{
|
||||
if (HAMqtt::instance() == nullptr || length > UINT16_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
HAMqtt::instance()->processMessage(topic, payload, static_cast<uint16_t>(length));
|
||||
}
|
||||
|
||||
HAMqtt::HAMqtt(Client& netClient, HADevice& device) :
|
||||
_netClient(netClient),
|
||||
_device(device),
|
||||
_messageCallback(nullptr),
|
||||
_connectedCallback(nullptr),
|
||||
_connectionFailedCallback(nullptr),
|
||||
_initialized(false),
|
||||
_discoveryPrefix(DefaultDiscoveryPrefix),
|
||||
_dataPrefix(nullptr),
|
||||
_mqtt(new PubSubClient(netClient)),
|
||||
_username(nullptr),
|
||||
_password(nullptr),
|
||||
_lastConnectionAttemptAt(0),
|
||||
_devicesTypesNb(0),
|
||||
_devicesTypes(nullptr),
|
||||
_lastWillTopic(nullptr),
|
||||
_lastWillMessage(nullptr),
|
||||
_lastWillRetain(false)
|
||||
{
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const IPAddress& serverIp,
|
||||
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(serverIp);
|
||||
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(serverIp, serverPort);
|
||||
_mqtt->setCallback(onMessageReceived);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const IPAddress& serverIp,
|
||||
const char* username,
|
||||
const char* 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 && !_mqtt->loop()) {
|
||||
connectToServer();
|
||||
}
|
||||
}
|
||||
|
||||
bool HAMqtt::isConnected()
|
||||
{
|
||||
return _mqtt->connected();
|
||||
}
|
||||
|
||||
void HAMqtt::addDeviceType(BaseDeviceType* deviceType)
|
||||
{
|
||||
BaseDeviceType** data = (BaseDeviceType**)realloc(
|
||||
_devicesTypes,
|
||||
sizeof(BaseDeviceType*) * (_devicesTypesNb + 1)
|
||||
);
|
||||
|
||||
if (data != nullptr) {
|
||||
_devicesTypes = data;
|
||||
_devicesTypes[_devicesTypesNb] = deviceType;
|
||||
_devicesTypesNb++;
|
||||
}
|
||||
}
|
||||
|
||||
bool HAMqtt::publish(const char* topic, const char* payload, bool retained)
|
||||
{
|
||||
if (!isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
Serial.print(F("Publishing: "));
|
||||
Serial.print(topic);
|
||||
Serial.print(F(", len: "));
|
||||
Serial.print(strlen(payload));
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
_mqtt->beginPublish(topic, strlen(payload), retained);
|
||||
_mqtt->write((const uint8_t*)(payload), strlen(payload));
|
||||
return _mqtt->endPublish();
|
||||
}
|
||||
|
||||
bool HAMqtt::beginPublish(
|
||||
const char* topic,
|
||||
uint16_t payloadLength,
|
||||
bool retained
|
||||
)
|
||||
{
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
Serial.print(F("Publishing: "));
|
||||
Serial.print(topic);
|
||||
Serial.print(F(", len: "));
|
||||
Serial.print(payloadLength);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
return _mqtt->beginPublish(topic, payloadLength, retained);
|
||||
}
|
||||
|
||||
bool HAMqtt::writePayload(const char* data, uint16_t length)
|
||||
{
|
||||
return (_mqtt->write((const uint8_t*)(data), length) > 0);
|
||||
}
|
||||
|
||||
bool HAMqtt::writePayload_P(const char* src)
|
||||
{
|
||||
char data[strlen_P(src) + 1];
|
||||
strcpy_P(data, src);
|
||||
|
||||
return _mqtt->write((const uint8_t*)(data), strlen(data));
|
||||
}
|
||||
|
||||
bool HAMqtt::endPublish()
|
||||
{
|
||||
return _mqtt->endPublish();
|
||||
}
|
||||
|
||||
bool HAMqtt::subscribe(const char* topic)
|
||||
{
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
Serial.print(F("Subscribing topic: "));
|
||||
Serial.print(topic);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
return _mqtt->subscribe(topic);
|
||||
}
|
||||
|
||||
void HAMqtt::processMessage(char* topic, uint8_t* payload, uint16_t length)
|
||||
{
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
Serial.print(F("Received message on topic: "));
|
||||
Serial.print(topic);
|
||||
Serial.print(F(", payload length: "));
|
||||
Serial.print(length);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
if (_messageCallback) {
|
||||
_messageCallback(topic, payload, length);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < _devicesTypesNb; i++) {
|
||||
_devicesTypes[i]->onMqttMessage(topic, payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
void HAMqtt::connectToServer()
|
||||
{
|
||||
if (_lastConnectionAttemptAt > 0 &&
|
||||
(millis() - _lastConnectionAttemptAt) < ReconnectInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastConnectionAttemptAt = millis();
|
||||
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
Serial.print(F("Connecting to the MQTT broker... Client ID: "));
|
||||
Serial.print(_device.getUniqueId());
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
_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
|
||||
|
||||
onConnectedLogic();
|
||||
} else {
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
Serial.println(F("Failed to connect to the broker"));
|
||||
|
||||
if (_connectionFailedCallback) {
|
||||
_connectionFailedCallback();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void HAMqtt::onConnectedLogic()
|
||||
{
|
||||
if (_connectedCallback) {
|
||||
_connectedCallback();
|
||||
}
|
||||
|
||||
_device.publishAvailability();
|
||||
|
||||
for (uint8_t i = 0; i < _devicesTypesNb; i++) {
|
||||
_devicesTypes[i]->onMqttConnected();
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
#ifndef AHA_HAMQTT_H
|
||||
#define AHA_HAMQTT_H
|
||||
|
||||
#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;
|
||||
|
||||
class HAMqtt
|
||||
{
|
||||
public:
|
||||
static const uint16_t ReconnectInterval = 5000; // ms
|
||||
|
||||
inline static HAMqtt* instance()
|
||||
{ return _instance; }
|
||||
|
||||
HAMqtt(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; }
|
||||
|
||||
/**
|
||||
* Returns discovery prefix.
|
||||
*/
|
||||
inline const char* getDiscoveryPrefix() const
|
||||
{ return _discoveryPrefix; }
|
||||
|
||||
/**
|
||||
* Sets prefix that will be used for topics different than discovery.
|
||||
* It may be useful if you want to pass MQTT trafic through bridge.
|
||||
*
|
||||
* @param prefix
|
||||
*/
|
||||
inline void setDataPrefix(const char* prefix)
|
||||
{ _dataPrefix = prefix; }
|
||||
|
||||
/**
|
||||
* Returns data prefix.
|
||||
*/
|
||||
inline const char* getDataPrefix() const
|
||||
{ return _dataPrefix; }
|
||||
|
||||
/**
|
||||
* Returns instance of the device assigned to the HAMqtt class.
|
||||
*/
|
||||
inline HADevice const* getDevice() const
|
||||
{ return &_device; }
|
||||
|
||||
/**
|
||||
* Given callback will be called for each received message from the broker.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onMessage(HAMQTT_MESSAGE_CALLBACK(callback))
|
||||
{ _messageCallback = callback; }
|
||||
|
||||
/**
|
||||
* Given callback will be called each time the connection with broker is acquired.
|
||||
*
|
||||
* @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.
|
||||
* 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 uint16_t& serverPort = HAMQTT_DEFAULT_PORT,
|
||||
const char* username = nullptr,
|
||||
const char* password = nullptr
|
||||
);
|
||||
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.
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/**
|
||||
* Returns true if connection to the MQTT broker is established.
|
||||
*/
|
||||
bool isConnected();
|
||||
|
||||
/**
|
||||
* Adds a new device's type to the MQTT.
|
||||
* Each time the connection with MQTT broker is acquired, the HAMqtt class
|
||||
* calls "onMqttConnected" method in all devices' types instances.
|
||||
*
|
||||
* @param deviceType Instance of the device's type (eg. HATriggers).
|
||||
*/
|
||||
void addDeviceType(BaseDeviceType* deviceType);
|
||||
|
||||
/**
|
||||
* Publishes MQTT message with given topic and payload.
|
||||
* Message won't be published if connection with MQTT broker is not established.
|
||||
* In this case method returns false.
|
||||
*
|
||||
* @param topic Topic to publish.
|
||||
* @param payload Payload to publish (it may be empty const char).
|
||||
* @param retained Determines whether message should be retained.
|
||||
*/
|
||||
bool publish(const char* topic, const char* payload, bool retained = false);
|
||||
|
||||
bool beginPublish(const char* topic, uint16_t payloadLength, bool retained = false);
|
||||
bool writePayload(const char* data, uint16_t length);
|
||||
bool writePayload_P(const char* src);
|
||||
bool endPublish();
|
||||
|
||||
/**
|
||||
* Subscribes to the given topic.
|
||||
* Whenever a new message is received the onMqttMessage callback in all
|
||||
* devices types is called.
|
||||
*
|
||||
* Please note that you need to subscribe topic each time the connection
|
||||
* with the broker is acquired.
|
||||
*
|
||||
* @param topic Topic to subscribe
|
||||
*/
|
||||
bool subscribe(const char* topic);
|
||||
|
||||
/**
|
||||
* Enables 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).
|
||||
*
|
||||
* @param topic Topic of the message.
|
||||
* @param payload Content of the message.
|
||||
* @param length Length of the message.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
void connectToServer();
|
||||
|
||||
/**
|
||||
* This method is called each time the connection with MQTT broker is acquired.
|
||||
*/
|
||||
void onConnectedLogic();
|
||||
|
||||
Client& _netClient;
|
||||
HADevice& _device;
|
||||
HAMQTT_MESSAGE_CALLBACK(_messageCallback);
|
||||
HAMQTT_CALLBACK(_connectedCallback);
|
||||
HAMQTT_CALLBACK(_connectionFailedCallback);
|
||||
bool _initialized;
|
||||
const char* _discoveryPrefix;
|
||||
const char* _dataPrefix;
|
||||
PubSubClient* _mqtt;
|
||||
const char* _username;
|
||||
const char* _password;
|
||||
uint32_t _lastConnectionAttemptAt;
|
||||
uint8_t _devicesTypesNb;
|
||||
BaseDeviceType** _devicesTypes;
|
||||
const char* _lastWillTopic;
|
||||
const char* _lastWillMessage;
|
||||
bool _lastWillRetain;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,67 +0,0 @@
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
#include <avr/dtostrf.h>
|
||||
#endif
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "HAUtils.h"
|
||||
|
||||
bool HAUtils::endsWith(const char* str, const char* suffix)
|
||||
{
|
||||
if (str == nullptr || suffix == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t& lenstr = strlen(str);
|
||||
const uint16_t& lensuffix = strlen(suffix);
|
||||
|
||||
if (lensuffix > lenstr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0);
|
||||
}
|
||||
|
||||
void HAUtils::byteArrayToStr(
|
||||
char* dst,
|
||||
const byte* src,
|
||||
const uint16_t& length
|
||||
)
|
||||
{
|
||||
const uint16_t& finalLength = (length * 2);
|
||||
static const char map[] PROGMEM = {"0123456789abcdef"};
|
||||
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
dst[i*2] = pgm_read_byte(&map[((char)src[i] & 0XF0) >> 4]);
|
||||
dst[i*2+1] = pgm_read_byte(&map[((char)src[i] & 0x0F)]);
|
||||
}
|
||||
|
||||
dst[finalLength] = '\0';
|
||||
}
|
||||
|
||||
char* HAUtils::byteArrayToStr(
|
||||
const byte* src,
|
||||
const uint16_t& length
|
||||
)
|
||||
{
|
||||
char* dst = (char*)malloc((length * 2) + 1); // include null terminator
|
||||
byteArrayToStr(dst, src, length);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void HAUtils::tempToStr(
|
||||
char* dst,
|
||||
const double& temp
|
||||
)
|
||||
{
|
||||
memset(dst, 0, sizeof(AHA_SERIALIZED_TEMP_SIZE));
|
||||
dtostrf(temp, 0, 2, dst);
|
||||
}
|
||||
|
||||
double HAUtils::strToTemp(
|
||||
const char* src
|
||||
)
|
||||
{
|
||||
return atof(src);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#ifndef AHA_HAUTILS_H
|
||||
#define AHA_HAUTILS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define AHA_SERIALIZED_TEMP_SIZE 8
|
||||
|
||||
class HAUtils
|
||||
{
|
||||
public:
|
||||
static bool endsWith(
|
||||
const char* str,
|
||||
const char* suffix
|
||||
);
|
||||
static void byteArrayToStr(
|
||||
char* dst,
|
||||
const byte* src,
|
||||
const uint16_t& length
|
||||
);
|
||||
static char* byteArrayToStr(
|
||||
const byte* src,
|
||||
const uint16_t& length
|
||||
);
|
||||
static void tempToStr(
|
||||
char* dst,
|
||||
const double& temp
|
||||
);
|
||||
static double strToTemp(
|
||||
const char* src
|
||||
);
|
||||
};
|
||||
|
||||
#endif
|
||||
-163
@@ -1,163 +0,0 @@
|
||||
#include "BaseDeviceType.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
#include "../HAUtils.h"
|
||||
|
||||
BaseDeviceType::BaseDeviceType(
|
||||
const char* componentName,
|
||||
const char* uniqueId
|
||||
) :
|
||||
_componentName(componentName),
|
||||
_uniqueId(uniqueId),
|
||||
_availability(AvailabilityDefault),
|
||||
_name(nullptr)
|
||||
{
|
||||
mqtt()->addDeviceType(this);
|
||||
}
|
||||
|
||||
BaseDeviceType::~BaseDeviceType()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void BaseDeviceType::setAvailability(bool online)
|
||||
{
|
||||
_availability = (online ? AvailabilityOnline : AvailabilityOffline);
|
||||
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(uniqueId()) == 0 ||
|
||||
strlen(componentName()) == 0 ||
|
||||
device->isSharedAvailabilityEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::AvailabilityTopic
|
||||
);
|
||||
if (topicSize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char topic[topicSize];
|
||||
DeviceTypeSerializer::generateTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::AvailabilityTopic
|
||||
);
|
||||
|
||||
if (strlen(topic) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mqtt()->publish(
|
||||
topic,
|
||||
(
|
||||
_availability == AvailabilityOnline ?
|
||||
DeviceTypeSerializer::Online :
|
||||
DeviceTypeSerializer::Offline
|
||||
),
|
||||
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);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#ifndef AHA_BASEDEVICETYPE_H
|
||||
#define AHA_BASEDEVICETYPE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../ArduinoHADefines.h"
|
||||
#include "DeviceTypeSerializer.h"
|
||||
|
||||
class HAMqtt;
|
||||
|
||||
class BaseDeviceType
|
||||
{
|
||||
public:
|
||||
BaseDeviceType(
|
||||
const char* componentName,
|
||||
const char* uniqueId
|
||||
);
|
||||
virtual ~BaseDeviceType();
|
||||
|
||||
inline const char* uniqueId() const
|
||||
{ return _uniqueId; }
|
||||
|
||||
inline const char* componentName() const
|
||||
{ return _componentName; }
|
||||
|
||||
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 _uniqueId;
|
||||
|
||||
private:
|
||||
enum Availability {
|
||||
AvailabilityDefault = 0,
|
||||
AvailabilityOnline,
|
||||
AvailabilityOffline
|
||||
};
|
||||
|
||||
Availability _availability;
|
||||
const char* _name;
|
||||
|
||||
friend class HAMqtt;
|
||||
};
|
||||
|
||||
#endif
|
||||
-421
@@ -1,421 +0,0 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "DeviceTypeSerializer.h"
|
||||
#include "BaseDeviceType.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
|
||||
static const char CharSlash[] PROGMEM = {"/"};
|
||||
static const char CharUnderscore[] PROGMEM = {"_"};
|
||||
static const char CharQuotation[] PROGMEM = {"\""};
|
||||
|
||||
const char* DeviceTypeSerializer::ConfigTopic = "config";
|
||||
const char* DeviceTypeSerializer::EventTopic = "event";
|
||||
const char* DeviceTypeSerializer::AvailabilityTopic = "avail";
|
||||
const char* DeviceTypeSerializer::StateTopic = "state";
|
||||
const char* DeviceTypeSerializer::CommandTopic = "cmd";
|
||||
const char* DeviceTypeSerializer::Online = "online";
|
||||
const char* DeviceTypeSerializer::Offline = "offline";
|
||||
const char* DeviceTypeSerializer::StateOn = "ON";
|
||||
const char* DeviceTypeSerializer::StateOff = "OFF";
|
||||
|
||||
const char* DeviceTypeSerializer::getTopicPrefix(bool isDiscoveryTopic)
|
||||
{
|
||||
if (!isDiscoveryTopic) {
|
||||
const char* dataPrefix = HAMqtt::instance()->getDataPrefix();
|
||||
if (dataPrefix != nullptr) {
|
||||
return dataPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
return HAMqtt::instance()->getDiscoveryPrefix();
|
||||
}
|
||||
|
||||
uint16_t DeviceTypeSerializer::calculateTopicLength(
|
||||
const char* component,
|
||||
const char* objectId,
|
||||
const char* suffix,
|
||||
bool includeNullTerminator,
|
||||
bool isDiscoveryTopic
|
||||
)
|
||||
{
|
||||
const char* prefix = getTopicPrefix(isDiscoveryTopic);
|
||||
if (prefix == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t size =
|
||||
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
|
||||
}
|
||||
|
||||
if (includeNullTerminator) {
|
||||
size += 1;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint16_t DeviceTypeSerializer::generateTopic(
|
||||
char* output,
|
||||
const char* component,
|
||||
const char* objectId,
|
||||
const char* suffix,
|
||||
bool isDiscoveryTopic
|
||||
)
|
||||
{
|
||||
const char* prefix = getTopicPrefix(isDiscoveryTopic);
|
||||
if (prefix == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
strcpy(output, prefix);
|
||||
strcat_P(output, CharSlash);
|
||||
|
||||
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, suffix);
|
||||
return strlen(output) + 1; // size with null terminator
|
||||
}
|
||||
|
||||
uint16_t DeviceTypeSerializer::calculateBaseJsonDataSize()
|
||||
{
|
||||
return 2; // opening and closing brackets of the JSON data
|
||||
}
|
||||
|
||||
uint16_t DeviceTypeSerializer::calculateNameFieldSize(const char* name)
|
||||
{
|
||||
if (name == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Field format: ,"name":"[NAME]"
|
||||
return strlen(name) + 10; // 10 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
uint16_t DeviceTypeSerializer::calculateUniqueIdFieldSize(
|
||||
const char* uniqueId
|
||||
)
|
||||
{
|
||||
HADevice const* device = HAMqtt::instance()->getDevice();
|
||||
if (device == nullptr || uniqueId == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Field format: ,"uniq_id":"[DEVICE ID]_[UNIQUE ID]"
|
||||
return (
|
||||
strlen(device->getUniqueId()) +
|
||||
strlen(uniqueId) +
|
||||
14 // 14 - length of the JSON decorators for this field
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t DeviceTypeSerializer::calculateAvailabilityFieldSize(
|
||||
const BaseDeviceType* const dt
|
||||
)
|
||||
{
|
||||
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(
|
||||
(sharedAvailability ? nullptr : dt->componentName()),
|
||||
(sharedAvailability ? nullptr : dt->uniqueId()),
|
||||
AvailabilityTopic,
|
||||
false
|
||||
);
|
||||
|
||||
if (availabilityTopicLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Field format: ,"avty_t":"[TOPIC]"
|
||||
return availabilityTopicLength + 12; // 12 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
uint16_t DeviceTypeSerializer::calculateRetainFieldSize(bool retain)
|
||||
{
|
||||
if (!retain) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Field format: ,"ret":true
|
||||
return 11;
|
||||
}
|
||||
|
||||
uint16_t DeviceTypeSerializer::calculateDeviceFieldSize(
|
||||
const char* serializedDevice
|
||||
)
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Field format: ,"dev":[DEVICE]
|
||||
return strlen(serializedDevice) + 7; // 7 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
void DeviceTypeSerializer::mqttWriteBeginningJson()
|
||||
{
|
||||
static const char Data[] PROGMEM = {"{"};
|
||||
HAMqtt::instance()->writePayload_P(Data);
|
||||
}
|
||||
|
||||
void DeviceTypeSerializer::mqttWriteEndJson()
|
||||
{
|
||||
static const char Data[] PROGMEM = {"}"};
|
||||
HAMqtt::instance()->writePayload_P(Data);
|
||||
}
|
||||
|
||||
void DeviceTypeSerializer::mqttWriteConstCharField(
|
||||
const char* prefix,
|
||||
const char* value,
|
||||
bool quoteSuffix
|
||||
)
|
||||
{
|
||||
if (prefix == nullptr || value == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
HAMqtt::instance()->writePayload_P(prefix);
|
||||
HAMqtt::instance()->writePayload(value, strlen(value));
|
||||
|
||||
if (quoteSuffix) {
|
||||
HAMqtt::instance()->writePayload_P(CharQuotation);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceTypeSerializer::mqttWriteNameField(const char* name)
|
||||
{
|
||||
if (name == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const char Prefix[] PROGMEM = {",\"name\":\""};
|
||||
mqttWriteConstCharField(Prefix, name);
|
||||
}
|
||||
|
||||
void DeviceTypeSerializer::mqttWriteUniqueIdField(
|
||||
const char* uniqueId
|
||||
)
|
||||
{
|
||||
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(uniqueId) + strlen(device->getUniqueId()) + 2; // underscore + null temrinator
|
||||
char finalUniqueId[uniqueIdLength];
|
||||
strcpy(finalUniqueId, uniqueId);
|
||||
strcat_P(finalUniqueId, CharUnderscore);
|
||||
strcat(finalUniqueId, device->getUniqueId());
|
||||
|
||||
mqttWriteConstCharField(Prefix, finalUniqueId);
|
||||
}
|
||||
|
||||
void DeviceTypeSerializer::mqttWriteAvailabilityField(
|
||||
const BaseDeviceType* const dt
|
||||
)
|
||||
{
|
||||
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(
|
||||
(sharedAvailability ? nullptr : dt->componentName()),
|
||||
(sharedAvailability ? nullptr : dt->uniqueId()),
|
||||
AvailabilityTopic
|
||||
);
|
||||
if (topicSize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char availabilityTopic[topicSize];
|
||||
generateTopic(
|
||||
availabilityTopic,
|
||||
(sharedAvailability ? nullptr : dt->componentName()),
|
||||
(sharedAvailability ? nullptr : dt->uniqueId()),
|
||||
AvailabilityTopic
|
||||
);
|
||||
|
||||
if (strlen(availabilityTopic) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const char Prefix[] PROGMEM = {",\"avty_t\":\""};
|
||||
mqttWriteConstCharField(Prefix, availabilityTopic);
|
||||
}
|
||||
|
||||
void DeviceTypeSerializer::mqttWriteRetainField(
|
||||
bool retain
|
||||
)
|
||||
{
|
||||
if (!retain) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const char Prefix[] PROGMEM = {",\"ret\":"};
|
||||
mqttWriteConstCharField(
|
||||
Prefix,
|
||||
"true",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
void DeviceTypeSerializer::mqttWriteDeviceField(
|
||||
const char* serializedDevice
|
||||
)
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const char Data[] PROGMEM = {",\"dev\":"};
|
||||
|
||||
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);
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
#ifndef AHA_DEVICETYPESERIALIZER_H
|
||||
#define AHA_DEVICETYPESERIALIZER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class HADevice;
|
||||
class BaseDeviceType;
|
||||
|
||||
class DeviceTypeSerializer
|
||||
{
|
||||
public:
|
||||
static const char* ConfigTopic;
|
||||
static const char* EventTopic;
|
||||
static const char* AvailabilityTopic;
|
||||
static const char* StateTopic;
|
||||
static const char* CommandTopic;
|
||||
static const char* Online;
|
||||
static const char* Offline;
|
||||
static const char* StateOn;
|
||||
static const char* StateOff;
|
||||
|
||||
static const char* getTopicPrefix(bool isDiscoveryTopic);
|
||||
|
||||
/**
|
||||
* Calculates length of the topic with given parameters.
|
||||
* Topic format: [prefix]/[component]/[deviceId]/[objectId]/[suffix]
|
||||
*
|
||||
* @param component
|
||||
* @param objectId
|
||||
* @param suffix
|
||||
* @param includeNullTerminator
|
||||
* @param isDiscoveryTopic Determines which prefix will be used for topic.
|
||||
*/
|
||||
static uint16_t calculateTopicLength(
|
||||
const char* component,
|
||||
const char* objectId,
|
||||
const char* suffix,
|
||||
bool includeNullTerminator = true,
|
||||
bool 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: [prefix]/[component]/[deviceId]/[objectId]/[suffix]
|
||||
*
|
||||
* @param output
|
||||
* @param component
|
||||
* @param objectId
|
||||
* @param suffix
|
||||
* @param includeNullTerminator
|
||||
* @param isDiscoveryTopic Determines which prefix will be used for topic.
|
||||
*/
|
||||
static uint16_t generateTopic(
|
||||
char* output,
|
||||
const char* component,
|
||||
const char* objectId,
|
||||
const char* suffix,
|
||||
bool isDiscoveryTopic = false
|
||||
);
|
||||
|
||||
static uint16_t calculateBaseJsonDataSize();
|
||||
static uint16_t calculateNameFieldSize(
|
||||
const char* name
|
||||
);
|
||||
static uint16_t calculateUniqueIdFieldSize(
|
||||
const char* uniqueId
|
||||
);
|
||||
static uint16_t calculateAvailabilityFieldSize(
|
||||
const BaseDeviceType* const dt
|
||||
);
|
||||
static uint16_t calculateRetainFieldSize(
|
||||
bool retain
|
||||
);
|
||||
static uint16_t calculateDeviceFieldSize(
|
||||
const char* serializedDevice
|
||||
);
|
||||
|
||||
static void mqttWriteBeginningJson();
|
||||
static void mqttWriteEndJson();
|
||||
static void mqttWriteConstCharField(
|
||||
const char* prefix,
|
||||
const char* value,
|
||||
bool quoteSuffix = true
|
||||
);
|
||||
static void mqttWriteNameField(
|
||||
const char* name
|
||||
);
|
||||
static void mqttWriteUniqueIdField(
|
||||
const char* uniqueId
|
||||
);
|
||||
static void mqttWriteAvailabilityField(
|
||||
const BaseDeviceType* const dt
|
||||
);
|
||||
static void mqttWriteRetainField(
|
||||
bool retain
|
||||
);
|
||||
static void mqttWriteDeviceField(
|
||||
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
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
#include "HABinarySensor.h"
|
||||
#ifdef ARDUINOHA_BINARY_SENSOR
|
||||
|
||||
#include "../ArduinoHADefines.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
|
||||
HABinarySensor::HABinarySensor(
|
||||
const char* uniqueId,
|
||||
bool initialState
|
||||
) :
|
||||
BaseDeviceType("binary_sensor", uniqueId),
|
||||
_class(nullptr),
|
||||
_currentState(initialState)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HABinarySensor::HABinarySensor(
|
||||
const char* uniqueId,
|
||||
bool initialState,
|
||||
HAMqtt& mqtt
|
||||
) :
|
||||
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(uniqueId()) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishState(_currentState);
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
bool HABinarySensor::setState(bool state)
|
||||
{
|
||||
if (state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HABinarySensor::publishState(bool state)
|
||||
{
|
||||
if (strlen(uniqueId()) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::StateTopic
|
||||
);
|
||||
if (topicSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char topic[topicSize];
|
||||
DeviceTypeSerializer::generateTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::StateTopic
|
||||
);
|
||||
|
||||
if (strlen(topic) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mqtt()->publish(
|
||||
topic,
|
||||
(
|
||||
state ?
|
||||
DeviceTypeSerializer::StateOn :
|
||||
DeviceTypeSerializer::StateOff
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t HABinarySensor::calculateSerializedLength(
|
||||
const char* serializedDevice
|
||||
) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const HADevice* device = mqtt()->getDevice();
|
||||
if (device == 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);
|
||||
|
||||
// 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 + 11; // 11 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
// device class
|
||||
if (_class != nullptr) {
|
||||
// Field format: ,"dev_cla":"[CLASS]"
|
||||
size += strlen(_class) + 13; // 13 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
return size; // exludes null terminator
|
||||
}
|
||||
|
||||
bool HABinarySensor::writeSerializedData(const char* serializedDevice) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttWriteBeginningJson();
|
||||
|
||||
// state topic
|
||||
{
|
||||
static const char Prefix[] PROGMEM = {"\"stat_t\":\""};
|
||||
DeviceTypeSerializer::mqttWriteTopicField(
|
||||
this,
|
||||
Prefix,
|
||||
DeviceTypeSerializer::StateTopic
|
||||
);
|
||||
}
|
||||
|
||||
// device class
|
||||
if (_class != nullptr) {
|
||||
static const char Prefix[] PROGMEM = {",\"dev_cla\":\""};
|
||||
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _class);
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttWriteNameField(getName());
|
||||
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
|
||||
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
|
||||
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
|
||||
DeviceTypeSerializer::mqttWriteEndJson();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,81 +0,0 @@
|
||||
#ifndef AHA_HABINARYSENSOR_H
|
||||
#define AHA_HABINARYSENSOR_H
|
||||
|
||||
#include "BaseDeviceType.h"
|
||||
|
||||
#ifdef ARDUINOHA_BINARY_SENSOR
|
||||
|
||||
class HABinarySensor : public BaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initializes binary sensor.
|
||||
*
|
||||
* @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* 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 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* uniqueId,
|
||||
const char* deviceClass,
|
||||
bool initialState
|
||||
);
|
||||
HABinarySensor(
|
||||
const char* uniqueId,
|
||||
const char* deviceClass,
|
||||
bool initialState,
|
||||
HAMqtt& mqtt
|
||||
);
|
||||
|
||||
/**
|
||||
* Publishes configuration of the sensor to the MQTT.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param state New state of the sensor.
|
||||
* @returns Returns true if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(bool state);
|
||||
|
||||
/**
|
||||
* Returns last known state of the sensor.
|
||||
* If setState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline bool getState() const
|
||||
{ return _currentState; }
|
||||
|
||||
private:
|
||||
bool publishState(bool state);
|
||||
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
|
||||
bool writeSerializedData(const char* serializedDevice) const override;
|
||||
|
||||
const char* _class;
|
||||
bool _currentState;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,286 +0,0 @@
|
||||
#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
|
||||
@@ -1,113 +0,0 @@
|
||||
#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
|
||||
@@ -1,343 +0,0 @@
|
||||
#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
|
||||
@@ -1,156 +0,0 @@
|
||||
#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
@@ -1,336 +0,0 @@
|
||||
#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
|
||||
@@ -1,218 +0,0 @@
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
#include <avr/dtostrf.h>
|
||||
#endif
|
||||
|
||||
#include "HASensor.h"
|
||||
#ifdef ARDUINOHA_SENSOR
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
|
||||
HASensor::HASensor(const char* uniqueId) :
|
||||
BaseDeviceType("sensor", uniqueId),
|
||||
_class(nullptr),
|
||||
_units(nullptr),
|
||||
_icon(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HASensor::HASensor(const char* uniqueId, HAMqtt& mqtt) :
|
||||
HASensor(uniqueId)
|
||||
{
|
||||
(void)mqtt;
|
||||
}
|
||||
|
||||
void HASensor::onMqttConnected()
|
||||
{
|
||||
if (strlen(uniqueId()) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
bool HASensor::setValue(const char* value)
|
||||
{
|
||||
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 (!mqtt()->isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::StateTopic
|
||||
);
|
||||
if (topicSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char topic[topicSize];
|
||||
DeviceTypeSerializer::generateTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::StateTopic
|
||||
);
|
||||
|
||||
if (strlen(topic) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mqtt()->publish(topic, value, true);
|
||||
}
|
||||
|
||||
uint16_t HASensor::calculateSerializedLength(
|
||||
const char* serializedDevice
|
||||
) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const HADevice* device = mqtt()->getDevice();
|
||||
if (device == 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);
|
||||
|
||||
{
|
||||
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::StateTopic,
|
||||
false
|
||||
);
|
||||
|
||||
if (topicSize == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Format: "stat_t":"[TOPIC]"
|
||||
size += topicSize + 11; // 11 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
// device class
|
||||
if (_class != nullptr) {
|
||||
// Field format: ,"dev_cla":"[CLASS]"
|
||||
size += strlen(_class) + 13; // 13 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
// units of measurement
|
||||
if (_units != nullptr) {
|
||||
// Format: ,"unit_of_meas":"[UNITS]"
|
||||
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
|
||||
}
|
||||
|
||||
bool HASensor::writeSerializedData(const char* serializedDevice) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttWriteBeginningJson();
|
||||
|
||||
// state topic
|
||||
{
|
||||
static const char Prefix[] PROGMEM = {"\"stat_t\":\""};
|
||||
DeviceTypeSerializer::mqttWriteTopicField(
|
||||
this,
|
||||
Prefix,
|
||||
DeviceTypeSerializer::StateTopic
|
||||
);
|
||||
}
|
||||
|
||||
// device class
|
||||
if (_class != nullptr) {
|
||||
static const char Prefix[] PROGMEM = {",\"dev_cla\":\""};
|
||||
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _class);
|
||||
}
|
||||
|
||||
// units of measurement
|
||||
if (_units != nullptr) {
|
||||
static const char Prefix[] PROGMEM = {",\"unit_of_meas\":\""};
|
||||
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, _units);
|
||||
}
|
||||
|
||||
// icon
|
||||
if (_icon != nullptr) {
|
||||
static const char Prefix[] PROGMEM = {",\"ic\":\""};
|
||||
DeviceTypeSerializer::mqttWriteConstCharField(
|
||||
Prefix,
|
||||
_icon
|
||||
);
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttWriteNameField(getName());
|
||||
DeviceTypeSerializer::mqttWriteUniqueIdField(uniqueId());
|
||||
DeviceTypeSerializer::mqttWriteAvailabilityField(this);
|
||||
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
|
||||
DeviceTypeSerializer::mqttWriteEndJson();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,90 +0,0 @@
|
||||
#ifndef AHA_HASENSOR_H
|
||||
#define AHA_HASENSOR_H
|
||||
|
||||
#include "BaseDeviceType.h"
|
||||
|
||||
#ifdef ARDUINOHA_SENSOR
|
||||
|
||||
class HASensor : public BaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initializes binary sensor.
|
||||
*
|
||||
* @param uniqueId Unique ID of the sensor. Recommendes characters: [a-z0-9\-_]
|
||||
*/
|
||||
HASensor(
|
||||
const char* uniqueId
|
||||
);
|
||||
HASensor(
|
||||
const char* uniqueId,
|
||||
HAMqtt& mqtt
|
||||
); // legacy constructor
|
||||
|
||||
/**
|
||||
* Publishes configuration of the sensor to the MQTT.
|
||||
*/
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
/**
|
||||
* Publishes new value of the sensor.
|
||||
* Please note that connection to MQTT broker must be acquired.
|
||||
* Otherwise method will return false.
|
||||
*
|
||||
* @param state Value to publish.
|
||||
* @returns Returns true if MQTT message has been published successfully.
|
||||
*/
|
||||
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)); }
|
||||
|
||||
/**
|
||||
* 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 void setDeviceClass(const char* className)
|
||||
{ _class = className; }
|
||||
|
||||
/**
|
||||
* Defines the units of measurement of the sensor, if any.
|
||||
*
|
||||
* @param units For example: °C, %
|
||||
*/
|
||||
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:
|
||||
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;
|
||||
const char* _icon;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,211 +0,0 @@
|
||||
#include "HASwitch.h"
|
||||
#ifdef ARDUINOHA_SWITCH
|
||||
|
||||
#include "../ArduinoHADefines.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
|
||||
HASwitch::HASwitch(const char* uniqueId, bool initialState) :
|
||||
BaseDeviceType("switch", uniqueId),
|
||||
_stateCallback(nullptr),
|
||||
_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(uniqueId()) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttSubscribeTopic(
|
||||
this,
|
||||
DeviceTypeSerializer::CommandTopic
|
||||
);
|
||||
}
|
||||
|
||||
void HASwitch::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);
|
||||
}
|
||||
}
|
||||
|
||||
bool HASwitch::setState(bool state, bool force)
|
||||
{
|
||||
if (!force && _currentState == state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_beforeStateCallback) {
|
||||
_beforeStateCallback(state, this);
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
|
||||
if (_stateCallback) {
|
||||
_stateCallback(_currentState, this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HASwitch::publishState(bool state)
|
||||
{
|
||||
if (strlen(uniqueId()) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DeviceTypeSerializer::mqttPublishMessage(
|
||||
this,
|
||||
DeviceTypeSerializer::StateTopic,
|
||||
(
|
||||
state ?
|
||||
DeviceTypeSerializer::StateOn :
|
||||
DeviceTypeSerializer::StateOff
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t HASwitch::calculateSerializedLength(const char* serializedDevice) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const HADevice* device = mqtt()->getDevice();
|
||||
if (device == 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);
|
||||
|
||||
// cmd 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
bool HASwitch::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
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1,123 +0,0 @@
|
||||
#ifndef AHA_HASWITCH_H
|
||||
#define AHA_HASWITCH_H
|
||||
|
||||
#include "BaseDeviceType.h"
|
||||
|
||||
#ifdef ARDUINOHA_SWITCH
|
||||
|
||||
#define HASWITCH_CALLBACK(name) void (*name)(bool, HASwitch*)
|
||||
|
||||
class HASwitch : public BaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initializes switch.
|
||||
*
|
||||
* @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* uniqueId,
|
||||
bool initialState
|
||||
);
|
||||
HASwitch(
|
||||
const char* uniqueId,
|
||||
bool initialState,
|
||||
HAMqtt& mqtt
|
||||
); // legacy constructor
|
||||
|
||||
/**
|
||||
* Publishes configuration of the sensor to the MQTT.
|
||||
*/
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
/**
|
||||
* Processes message received from the MQTT broker.
|
||||
* The method updates state of the switch (if message matches switch'es topic).
|
||||
*/
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t& length
|
||||
) override;
|
||||
|
||||
/**
|
||||
* 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 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 switch.
|
||||
* 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 switch changes.
|
||||
* Please note that it's not possible to register multiple callbacks for the same switch.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onStateChanged(HASWITCH_CALLBACK(callback))
|
||||
{ _stateCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called before state of the switch changes.
|
||||
* The state passed to callback is a new state that is going to be set.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onBeforeStateChanged(HASWITCH_CALLBACK(callback))
|
||||
{ _beforeStateCallback = callback; }
|
||||
|
||||
/**
|
||||
* Sets icon of the switch, e.g. `mdi:home`.
|
||||
*
|
||||
* @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
|
||||
@@ -1,111 +0,0 @@
|
||||
#include "HATagScanner.h"
|
||||
#ifdef ARDUINOHA_TAG_SCANNER
|
||||
|
||||
#include "../ArduinoHADefines.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
|
||||
HATagScanner::HATagScanner(const char* uniqueId) :
|
||||
BaseDeviceType("tag", uniqueId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HATagScanner::HATagScanner(const char* uniqueId, HAMqtt& mqtt) :
|
||||
HATagScanner(uniqueId)
|
||||
{
|
||||
(void)mqtt;
|
||||
}
|
||||
|
||||
void HATagScanner::onMqttConnected()
|
||||
{
|
||||
if (strlen(uniqueId()) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
bool HATagScanner::tagScanned(const char* tag)
|
||||
{
|
||||
if (tag == nullptr || strlen(tag) == 0 || strlen(uniqueId()) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::EventTopic
|
||||
);
|
||||
if (topicSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char topic[topicSize];
|
||||
DeviceTypeSerializer::generateTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::EventTopic
|
||||
);
|
||||
|
||||
if (strlen(topic) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mqtt()->publish(topic, tag);
|
||||
}
|
||||
|
||||
uint16_t HATagScanner::calculateSerializedLength(
|
||||
const char* serializedDevice
|
||||
) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t size = 0;
|
||||
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
|
||||
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
|
||||
|
||||
// event topic
|
||||
{
|
||||
const uint16_t& topicSize = DeviceTypeSerializer::calculateTopicLength(
|
||||
componentName(),
|
||||
uniqueId(),
|
||||
DeviceTypeSerializer::EventTopic,
|
||||
false
|
||||
);
|
||||
|
||||
// Format: "t":"[TOPIC]"
|
||||
size += topicSize + 6; // 6 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
return size; // exludes null terminator
|
||||
}
|
||||
|
||||
bool HATagScanner::writeSerializedData(const char* serializedDevice) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttWriteBeginningJson();
|
||||
|
||||
// topic
|
||||
{
|
||||
static const char Prefix[] PROGMEM = {"\"t\":\""};
|
||||
DeviceTypeSerializer::mqttWriteTopicField(
|
||||
this,
|
||||
Prefix,
|
||||
DeviceTypeSerializer::EventTopic
|
||||
);
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
|
||||
DeviceTypeSerializer::mqttWriteEndJson();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,43 +0,0 @@
|
||||
#ifndef AHA_HATAGSCANNER_H
|
||||
#define AHA_HATAGSCANNER_H
|
||||
|
||||
#include "BaseDeviceType.h"
|
||||
|
||||
#ifdef ARDUINOHA_TAG_SCANNER
|
||||
|
||||
class HATagScanner : public BaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initializes tag scanner with the given name.
|
||||
*
|
||||
* @param uniqueId Unique ID of the scanner. Recommendes characters: [a-z0-9\-_]
|
||||
*/
|
||||
HATagScanner(const char* uniqueId);
|
||||
HATagScanner(const char* uniqueId, HAMqtt& mqtt); // legacy constructor
|
||||
|
||||
/**
|
||||
* Publishes configuration of the sensor to the MQTT.
|
||||
*/
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
/**
|
||||
* Tag scanner doesn't support availability. Nothing to do here.
|
||||
*/
|
||||
virtual void setAvailability(bool online) override { (void)online; }
|
||||
|
||||
/**
|
||||
* Sends "tag scanned" event to the MQTT (Home Assistant).
|
||||
* Based on this event HA may perform user-defined automation.
|
||||
*
|
||||
* @param tag Value of the scanned tag.
|
||||
*/
|
||||
bool tagScanned(const char* tag);
|
||||
|
||||
private:
|
||||
uint16_t calculateSerializedLength(const char* serializedDevice) const override;
|
||||
bool writeSerializedData(const char* serializedDevice) const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,321 +0,0 @@
|
||||
#include "HATriggers.h"
|
||||
#ifdef ARDUINOHA_TRIGGERS
|
||||
|
||||
#include "../ArduinoHADefines.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
|
||||
HATriggers::HATriggers() :
|
||||
BaseDeviceType("device_automation", nullptr),
|
||||
_triggers(nullptr),
|
||||
_triggersNb(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HATriggers::HATriggers(HAMqtt& mqtt) :
|
||||
HATriggers()
|
||||
{
|
||||
(void)mqtt;
|
||||
}
|
||||
|
||||
HATriggers::~HATriggers()
|
||||
{
|
||||
if (_triggers != nullptr) {
|
||||
free(_triggers);
|
||||
}
|
||||
}
|
||||
|
||||
void HATriggers::onMqttConnected()
|
||||
{
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
bool HATriggers::add(const char* type, const char* subtype)
|
||||
{
|
||||
if (mqtt()->getDevice() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HATrigger* triggers = (HATrigger*)realloc(_triggers, sizeof(HATrigger) * (_triggersNb + 1));
|
||||
if (triggers == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
Serial.print(F("Adding HATrigger: "));
|
||||
Serial.print(type);
|
||||
Serial.print(F(" "));
|
||||
Serial.print(subtype);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
_triggers = triggers;
|
||||
_triggers[_triggersNb].type = type;
|
||||
_triggers[_triggersNb].subtype = subtype;
|
||||
|
||||
_triggersNb++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HATriggers::trigger(const char* type, const char* subtype)
|
||||
{
|
||||
HATrigger* trigger = nullptr;
|
||||
for (uint8_t i = 0; i < _triggersNb; i++) {
|
||||
if (strcmp(_triggers[i].type, type) == 0 &&
|
||||
strcmp(_triggers[i].subtype, subtype) == 0) {
|
||||
trigger = &_triggers[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t& topicSize = calculateTopicLength(
|
||||
componentName(),
|
||||
trigger,
|
||||
DeviceTypeSerializer::EventTopic
|
||||
);
|
||||
|
||||
if (topicSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char topic[topicSize];
|
||||
generateTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
trigger,
|
||||
DeviceTypeSerializer::EventTopic
|
||||
);
|
||||
|
||||
if (strlen(topic) == 0) {
|
||||
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, "");
|
||||
}
|
||||
|
||||
void HATriggers::publishConfig()
|
||||
{
|
||||
const HADevice* device = mqtt()->getDevice();
|
||||
if (device == nullptr) {
|
||||
return; // device is required for triggers
|
||||
}
|
||||
|
||||
const uint16_t& deviceLength = device->calculateSerializedLength();
|
||||
if (deviceLength == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char serializedDevice[deviceLength];
|
||||
if (device->serialize(serializedDevice) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < _triggersNb; i++) {
|
||||
const HATrigger* trigger = &_triggers[i];
|
||||
if (trigger == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint16_t& topicLength = calculateTopicLength(
|
||||
componentName(),
|
||||
trigger,
|
||||
DeviceTypeSerializer::ConfigTopic,
|
||||
true,
|
||||
true
|
||||
);
|
||||
const uint16_t& dataLength = calculateSerializedLength(
|
||||
trigger,
|
||||
serializedDevice
|
||||
);
|
||||
if (topicLength == 0 || dataLength == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char topic[topicLength];
|
||||
generateTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
trigger,
|
||||
DeviceTypeSerializer::ConfigTopic,
|
||||
true
|
||||
);
|
||||
|
||||
if (strlen(topic) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mqtt()->beginPublish(topic, dataLength, true)) {
|
||||
writeSerializedTrigger(trigger, serializedDevice);
|
||||
mqtt()->endPublish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t HATriggers::calculateTopicLength(
|
||||
const char* component,
|
||||
const HATrigger *trigger,
|
||||
const char* suffix,
|
||||
bool includeNullTerminator,
|
||||
bool isDiscoveryTopic
|
||||
) const
|
||||
{
|
||||
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // underscore and slash
|
||||
return DeviceTypeSerializer::calculateTopicLength(
|
||||
component,
|
||||
nullptr,
|
||||
suffix,
|
||||
includeNullTerminator,
|
||||
isDiscoveryTopic
|
||||
) + length;
|
||||
}
|
||||
|
||||
uint16_t HATriggers::generateTopic(
|
||||
char* output,
|
||||
const char* component,
|
||||
const HATrigger *trigger,
|
||||
const char* suffix,
|
||||
bool isDiscoveryTopic
|
||||
) const
|
||||
{
|
||||
static const char Underscore[] PROGMEM = {"_"};
|
||||
uint8_t length = strlen(trigger->type) + strlen(trigger->subtype) + 2; // underscore + null terminator
|
||||
char objectId[length];
|
||||
|
||||
strcpy(objectId, trigger->subtype);
|
||||
strcat_P(objectId, Underscore);
|
||||
strcat(objectId, trigger->type);
|
||||
|
||||
return DeviceTypeSerializer::generateTopic(
|
||||
output,
|
||||
component,
|
||||
objectId,
|
||||
suffix,
|
||||
isDiscoveryTopic
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t HATriggers::calculateSerializedLength(
|
||||
const HATrigger* trigger,
|
||||
const char* serializedDevice
|
||||
) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t size = 0;
|
||||
size += DeviceTypeSerializer::calculateBaseJsonDataSize();
|
||||
size += DeviceTypeSerializer::calculateDeviceFieldSize(serializedDevice);
|
||||
|
||||
// automation type
|
||||
{
|
||||
// Format: "atype":"trigger"
|
||||
size += 17;
|
||||
}
|
||||
|
||||
// topic
|
||||
{
|
||||
const uint16_t& topicSize = calculateTopicLength(
|
||||
componentName(),
|
||||
trigger,
|
||||
DeviceTypeSerializer::EventTopic,
|
||||
false
|
||||
);
|
||||
if (topicSize == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Format: ,"t":"[TOPIC]"
|
||||
size += topicSize + 7; // 7 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
// type
|
||||
{
|
||||
// Format: ,"type":"[TYPE]"
|
||||
size += strlen(trigger->type) + 10; // 10 - length of the JSON decorators for this field
|
||||
}
|
||||
|
||||
// subtype
|
||||
{
|
||||
// Format: ,"stype":"[SUBTYPE]"
|
||||
size += strlen(trigger->subtype) + 11; // 11 - length of the JSON decorators for this field;
|
||||
}
|
||||
|
||||
return size; // exludes null terminator
|
||||
}
|
||||
|
||||
bool HATriggers::writeSerializedTrigger(
|
||||
const HATrigger* trigger,
|
||||
const char* serializedDevice
|
||||
) const
|
||||
{
|
||||
if (serializedDevice == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttWriteBeginningJson();
|
||||
|
||||
// automation type
|
||||
{
|
||||
static const char Data[] PROGMEM = {"\"atype\":\"trigger\""};
|
||||
mqtt()->writePayload_P(Data);
|
||||
}
|
||||
|
||||
// topic
|
||||
{
|
||||
const uint16_t& topicSize = calculateTopicLength(
|
||||
componentName(),
|
||||
trigger,
|
||||
DeviceTypeSerializer::EventTopic
|
||||
);
|
||||
if (topicSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char topic[topicSize];
|
||||
generateTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
trigger,
|
||||
DeviceTypeSerializer::EventTopic
|
||||
);
|
||||
|
||||
static const char Prefix[] PROGMEM = {",\"t\":\""};
|
||||
DeviceTypeSerializer::mqttWriteConstCharField(
|
||||
Prefix,
|
||||
topic
|
||||
);
|
||||
}
|
||||
|
||||
// type
|
||||
{
|
||||
static const char Prefix[] PROGMEM = {",\"type\":\""};
|
||||
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, trigger->type);
|
||||
}
|
||||
|
||||
// subtype
|
||||
{
|
||||
static const char Prefix[] PROGMEM = {",\"stype\":\""};
|
||||
DeviceTypeSerializer::mqttWriteConstCharField(Prefix, trigger->subtype);
|
||||
}
|
||||
|
||||
DeviceTypeSerializer::mqttWriteDeviceField(serializedDevice);
|
||||
DeviceTypeSerializer::mqttWriteEndJson();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,73 +0,0 @@
|
||||
#ifndef AHA_HATRIGGERS_H
|
||||
#define AHA_HATRIGGERS_H
|
||||
|
||||
#include "BaseDeviceType.h"
|
||||
|
||||
#ifdef ARDUINOHA_TRIGGERS
|
||||
|
||||
struct HATrigger {
|
||||
const char* type;
|
||||
const char* subtype;
|
||||
} __attribute__((packed));
|
||||
|
||||
class HATriggers : public BaseDeviceType
|
||||
{
|
||||
public:
|
||||
HATriggers();
|
||||
HATriggers(HAMqtt& mqtt); // legacy constructor
|
||||
virtual ~HATriggers();
|
||||
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
/**
|
||||
* Triggers dont't support availability. Nothing to do here.
|
||||
*/
|
||||
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 isDiscoveryTopic = false
|
||||
) const;
|
||||
|
||||
uint16_t generateTopic(
|
||||
char* output,
|
||||
const char* component,
|
||||
const HATrigger *trigger,
|
||||
const char* suffix,
|
||||
bool isDiscoveryTopic = false
|
||||
) const;
|
||||
|
||||
private:
|
||||
uint16_t calculateSerializedLength(
|
||||
const HATrigger* trigger,
|
||||
const char* serializedDevice
|
||||
) const;
|
||||
|
||||
bool writeSerializedTrigger(
|
||||
const HATrigger* trigger,
|
||||
const char* serializedDevice
|
||||
) const;
|
||||
|
||||
HATrigger* _triggers;
|
||||
uint8_t _triggersNb;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Arduino Home Assistant integration 🏠
|
||||
|
||||
[](https://github.com/dawidchyrzynski/arduino-home-assistant/releases)
|
||||
[](https://dawidchyrzynski.github.io/arduino-home-assistant/)
|
||||
[](https://github.com/sponsors/dawidchyrzynski)
|
||||
|
||||
ArduinoHA allows to integrate an Arduino/ESP based device with Home Assistant using MQTT.
|
||||
The library is designed to use as low resources (RAM/flash) as possible.
|
||||
Initially, it was optimized to work on Arduino Uno with Ethernet Shield,
|
||||
but I successfully use it on ESP8266/ESP8255 boards in my projects.
|
||||
|
||||
## Features
|
||||
|
||||
* Two-way communication (state reporting and command execution)
|
||||
* 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
|
||||
* Doxygen documentation for all classes
|
||||
* Covered by unit tests (AUnit + EpoxyDuino + AUniter)
|
||||
|
||||
## Supported HA types
|
||||
|
||||
| Home Assistant type | Supported |
|
||||
| ------------------- | :--------: |
|
||||
| Alarm control panel | ❌ |
|
||||
| Binary sensor | ✅ |
|
||||
| Button | ✅ |
|
||||
| Camera | ✅ |
|
||||
| Cover | ✅ |
|
||||
| Device tracker | ✅ |
|
||||
| Device trigger | ✅ |
|
||||
| Event | ❌ |
|
||||
| Fan | ✅ |
|
||||
| Humidifier | ❌ |
|
||||
| Image | ❌ |
|
||||
| HVAC | ✅ |
|
||||
| Lawn mower | ❌ |
|
||||
| Light | ✅ |
|
||||
| Lock | ✅ |
|
||||
| Number | ✅ |
|
||||
| Scene | ✅ |
|
||||
| Select | ✅ |
|
||||
| Sensor | ✅ |
|
||||
| Siren | ❌ |
|
||||
| Switch | ✅ |
|
||||
| Update | ❌ |
|
||||
| Tag scanner | ✅ |
|
||||
| Text | ❌ |
|
||||
| Vacuum | ❌ |
|
||||
| Valve | ❌ |
|
||||
| Water heater | ❌ |
|
||||
|
||||
## Examples
|
||||
|
||||
|Example|Description |
|
||||
|-------|-----------------------------|
|
||||
|[Binary sensor](examples/binary-sensor/binary-sensor.ino)|Using the binary sensor as a door contact sensor.|
|
||||
|[Button](examples/button/button.ino)|Adding simple buttons to the Home Assistant panel.|
|
||||
|[Camera](examples/esp32-cam/esp32-cam.ino)|Publishing the preview from the ESP32-CAM module.|
|
||||
|[Cover](examples/cover/cover.ino)|Controlling a window cover (open / close / stop).|
|
||||
|[Device trigger](examples/multi-state-button/multi-state-button.ino)|Implementation of a simple wall switch that reports press and hold states.|
|
||||
|[Fan](examples/fan/fan.ino)|Controlling a simple fan (state + speed).|
|
||||
|[HVAC](examples/hvac/hvac.ino)|HVAC controller with multiple modes, power control and target temperature.|
|
||||
|[Lock](examples/lock/lock.ino)|A simple door lock that's controlled by the Home Assistant.|
|
||||
|[Light](examples/light/light.ino)|A simple light that allows changing brightness, color temperature and RGB color.|
|
||||
|[Number](examples/number/number.ino)|Adding an interactive numeric slider in the Home Assistant panel.|
|
||||
|[Scene](examples/scene/scene.ino)|Adding a custom scene in the Home Assistant panel. |
|
||||
|[Select](examples/select/select.ino)|A dropdown selector that's displayed in the Home Assistant panel.|
|
||||
|[Sensor](examples/sensor/sensor.ino)|A simple sensor that reports a state in a string representation (open / opening / close).|
|
||||
|[Analog sensor](examples/sensor-analog/sensor-analog.ino)|Reporting the analog pin's voltage to the Home Assistant.|
|
||||
|[Integer sensor](examples/sensor-integer/sensor-integer.ino)|Reporting the device's uptime to the Home Assistant.|
|
||||
|[Switch](examples/led-switch/led-switch.ino)|The LED that's controlled by the Home Assistant.|
|
||||
|[Multi-switch](examples/multi-switch/multi-switch.ino)|Multiple switches controlled by the Home Assistant.|
|
||||
|[Tag scanner](examples/tag-scanner/tag-scanner.ino)|Scanning RFID tags using the MFRC522 module.|
|
||||
|[Availability](examples/availability/availability.ino)|Reporting entities' availability (online / offline) to the Home Assistant.|
|
||||
|[Advanced availability](examples/advanced-availability/advanced-availability.ino)|Advanced availability reporting with MQTT LWT (Last Will and Testament).|
|
||||
|[MQTT advanced](examples/mqtt-advanced/mqtt-advanced.ino)|Subscribing to custom topics and publishing custom messages.|
|
||||
|[MQTT with credentials](examples/mqtt-with-credentials/mqtt-with-credentials.ino)|Establishing connection with a MQTT broker using the credentials. |
|
||||
|[NodeMCU (ESP8266)](examples/nodemcu/nodemcu.ino)|Basic example for ESP8266 devices.|
|
||||
|[Arduino Nano 33 IoT](examples/nano33iot/nano33iot.ino)|Basic example for Arduino Nano 33 IoT (SAMD family).|
|
||||
|[mDNS discovery](examples/mdns/mdns.ino)|Make your ESP8266 discoverable via the mDNS.|
|
||||
|
||||
## Compatible hardware
|
||||
|
||||
The library uses the Arduino Ethernet Client API for interacting with the network hardware.
|
||||
It should work fine as long as the `Client` class is available.
|
||||
|
||||
Here is the list of devices on which the library was tested:
|
||||
|
||||
* Arduino Uno
|
||||
* Arduino Mega
|
||||
* Arduino Nano
|
||||
* Arduino Pro Mini
|
||||
* Arduino Nano 33 IoT
|
||||
* Arduino Due
|
||||
* NodeMCU
|
||||
* Controllino Mega (Pure)
|
||||
* Controllino Maxi (Pure)
|
||||
* ESP-01
|
||||
* ESP32-CAM
|
||||
* Sonoff Dual R2
|
||||
* Sonoff Dual R3
|
||||
* Sonoff Basic
|
||||
* Sonoff Mini
|
||||
* Tuya Wi-Fi switch module
|
||||
* Tuya Wi-Fi curtain module
|
||||
|
||||
Please note that it's not the complete list of supported devices.
|
||||
You may try to use the library on any device that uses Arduino core.
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
name=home-assistant-integration
|
||||
version=1.3.0
|
||||
author=Dawid Chyrzynski <dawid.chyrzynski@gmail.com>
|
||||
maintainer=Dawid Chyrzynski <dawid.chyrzynski@gmail.com>
|
||||
version=2.1.0
|
||||
author=Dawid Chyrzynski <dev@chyrzynski.pl>
|
||||
maintainer=Dawid Chyrzynski <dev@chyrzynski.pl>
|
||||
sentence=Home Assistant MQTT integration for Arduino
|
||||
paragraph=Lightweight library that provides easy to use API for integrating your Arduino/ESP based device with Home Assistant.
|
||||
category=Communication
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef AHA_ARDUINOHA_H
|
||||
#define AHA_ARDUINOHA_H
|
||||
|
||||
#include "HADevice.h"
|
||||
#include "HAMqtt.h"
|
||||
#include "device-types/HABinarySensor.h"
|
||||
#include "device-types/HAButton.h"
|
||||
#include "device-types/HACamera.h"
|
||||
#include "device-types/HACover.h"
|
||||
#include "device-types/HADeviceTracker.h"
|
||||
#include "device-types/HADeviceTrigger.h"
|
||||
#include "device-types/HAFan.h"
|
||||
#include "device-types/HAHVAC.h"
|
||||
#include "device-types/HALight.h"
|
||||
#include "device-types/HALock.h"
|
||||
#include "device-types/HANumber.h"
|
||||
#include "device-types/HAScene.h"
|
||||
#include "device-types/HASelect.h"
|
||||
#include "device-types/HASensor.h"
|
||||
#include "device-types/HASensorNumber.h"
|
||||
#include "device-types/HASwitch.h"
|
||||
#include "device-types/HATagScanner.h"
|
||||
#include "utils/HAUtils.h"
|
||||
#include "utils/HANumeric.h"
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
#include "mocks/AUnitHelpers.h"
|
||||
#include "mocks/PubSubClientMock.h"
|
||||
#include "utils/HADictionary.h"
|
||||
#include "utils/HASerializer.h"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
// Turns on debug information of the ArduinoHA core.
|
||||
// Please note that you need to initialize serial interface manually
|
||||
// by calling Serial.begin([baudRate]) before initializing ArduinoHA.
|
||||
// #define ARDUINOHA_DEBUG
|
||||
|
||||
// These macros allow to exclude some parts of the library to save more resources.
|
||||
// #define EX_ARDUINOHA_BINARY_SENSOR
|
||||
// #define EX_ARDUINOHA_BUTTON
|
||||
// #define EX_ARDUINOHA_CAMERA
|
||||
// #define EX_ARDUINOHA_COVER
|
||||
// #define EX_ARDUINOHA_DEVICE_TRACKER
|
||||
// #define EX_ARDUINOHA_DEVICE_TRIGGER
|
||||
// #define EX_ARDUINOHA_FAN
|
||||
// #define EX_ARDUINOHA_HVAC
|
||||
// #define EX_ARDUINOHA_LIGHT
|
||||
// #define EX_ARDUINOHA_LOCK
|
||||
// #define EX_ARDUINOHA_NUMBER
|
||||
// #define EX_ARDUINOHA_SCENE
|
||||
// #define EX_ARDUINOHA_SELECT
|
||||
// #define EX_ARDUINOHA_SENSOR
|
||||
// #define EX_ARDUINOHA_SWITCH
|
||||
// #define EX_ARDUINOHA_TAG_SCANNER
|
||||
|
||||
#if defined(ARDUINOHA_DEBUG)
|
||||
#include <Arduino.h>
|
||||
|
||||
#define ARDUINOHA_DEBUG_INIT() Serial.begin(115200);
|
||||
#define ARDUINOHA_DEBUG_PRINTLN(x) Serial.println(x);
|
||||
#define ARDUINOHA_DEBUG_PRINT(x) Serial.print(x);
|
||||
#else
|
||||
#define ARDUINOHA_DEBUG_INIT()
|
||||
#define ARDUINOHA_DEBUG_PRINTLN(x)
|
||||
#define ARDUINOHA_DEBUG_PRINT(x)
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__SAMD21G18A__) or defined(__SAM3X8E__)
|
||||
#define ARDUINOHA_INT_OVERLOAD
|
||||
#endif
|
||||
|
||||
#define AHATOFSTR(x) reinterpret_cast<const __FlashStringHelper*>(x)
|
||||
#define AHAFROMFSTR(x) reinterpret_cast<const char*>(x)
|
||||
@@ -0,0 +1,155 @@
|
||||
#include "ArduinoHADefines.h"
|
||||
#include "HADevice.h"
|
||||
#include "HAMqtt.h"
|
||||
#include "utils/HAUtils.h"
|
||||
#include "utils/HASerializer.h"
|
||||
|
||||
#define HADEVICE_INIT \
|
||||
_ownsUniqueId(false), \
|
||||
_serializer(new HASerializer(nullptr, 6)), \
|
||||
_availabilityTopic(nullptr), \
|
||||
_sharedAvailability(false), \
|
||||
_available(true), \
|
||||
_extendedUniqueIds(false)
|
||||
|
||||
HADevice::HADevice() :
|
||||
_uniqueId(nullptr),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HADevice::HADevice(const char* uniqueId) :
|
||||
_uniqueId(uniqueId),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
_serializer->set(AHATOFSTR(HADeviceIdentifiersProperty), _uniqueId);
|
||||
}
|
||||
|
||||
HADevice::HADevice(const byte* uniqueId, const uint16_t length) :
|
||||
_uniqueId(HAUtils::byteArrayToStr(uniqueId, length)),
|
||||
HADEVICE_INIT
|
||||
{
|
||||
_ownsUniqueId = true;
|
||||
_serializer->set(AHATOFSTR(HADeviceIdentifiersProperty), _uniqueId);
|
||||
}
|
||||
|
||||
HADevice::~HADevice()
|
||||
{
|
||||
delete _serializer;
|
||||
|
||||
if (_availabilityTopic) {
|
||||
delete _availabilityTopic;
|
||||
}
|
||||
|
||||
if (_ownsUniqueId) {
|
||||
delete[] _uniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
bool HADevice::setUniqueId(const byte* uniqueId, const uint16_t length)
|
||||
{
|
||||
if (_uniqueId) {
|
||||
return false; // unique ID cannot be changed at runtime once it's set
|
||||
}
|
||||
|
||||
_uniqueId = HAUtils::byteArrayToStr(uniqueId, length);
|
||||
_ownsUniqueId = true;
|
||||
_serializer->set(AHATOFSTR(HADeviceIdentifiersProperty), _uniqueId);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HADevice::setManufacturer(const char* manufacturer)
|
||||
{
|
||||
_serializer->set(AHATOFSTR(HADeviceManufacturerProperty), manufacturer);
|
||||
}
|
||||
|
||||
void HADevice::setModel(const char* model)
|
||||
{
|
||||
_serializer->set(AHATOFSTR(HADeviceModelProperty), model);
|
||||
}
|
||||
|
||||
void HADevice::setName(const char* name)
|
||||
{
|
||||
_serializer->set(AHATOFSTR(HANameProperty), name);
|
||||
}
|
||||
|
||||
void HADevice::setSoftwareVersion(const char* softwareVersion)
|
||||
{
|
||||
_serializer->set(
|
||||
AHATOFSTR(HADeviceSoftwareVersionProperty),
|
||||
softwareVersion
|
||||
);
|
||||
}
|
||||
|
||||
void HADevice::setConfigurationUrl(const char* url)
|
||||
{
|
||||
_serializer->set(
|
||||
AHATOFSTR(HADeviceConfigurationUrlProperty),
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
void HADevice::setAvailability(bool online)
|
||||
{
|
||||
_available = online;
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
bool HADevice::enableSharedAvailability()
|
||||
{
|
||||
if (_sharedAvailability) {
|
||||
return true; // already enabled
|
||||
}
|
||||
|
||||
const uint16_t topicLength = HASerializer::calculateDataTopicLength(
|
||||
nullptr,
|
||||
AHATOFSTR(HAAvailabilityTopic)
|
||||
);
|
||||
if (topicLength == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_availabilityTopic = new char[topicLength];
|
||||
|
||||
if (HASerializer::generateDataTopic(
|
||||
_availabilityTopic,
|
||||
nullptr,
|
||||
AHATOFSTR(HAAvailabilityTopic)
|
||||
) > 0) {
|
||||
_sharedAvailability = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HADevice::enableLastWill()
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (!mqtt || !_availabilityTopic) {
|
||||
return;
|
||||
}
|
||||
|
||||
mqtt->setLastWill(
|
||||
_availabilityTopic,
|
||||
"offline",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
void HADevice::publishAvailability() const
|
||||
{
|
||||
HAMqtt* mqtt = HAMqtt::instance();
|
||||
if (!_availabilityTopic || !mqtt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* payload = _available ? HAOnline : HAOffline;
|
||||
const uint16_t length = strlen_P(payload);
|
||||
|
||||
if (mqtt->beginPublish(_availabilityTopic, length, true)) {
|
||||
mqtt->writePayload(AHATOFSTR(payload));
|
||||
mqtt->endPublish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
#ifndef AHA_HADEVICE_H
|
||||
#define AHA_HADEVICE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class HASerializer;
|
||||
|
||||
/**
|
||||
* This class represents your device that's going to be registered in the Home Assistant devices registry.
|
||||
* Each entity (HABinarySensor, HASensor, etc.) that you use will be owned by this device.
|
||||
*/
|
||||
class HADevice
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructs HADevice without the unique ID.
|
||||
*
|
||||
* @note You will need to set the ID using HADevice::setUniqueId method. Otherwise none of the entities will work.
|
||||
*/
|
||||
HADevice();
|
||||
|
||||
/**
|
||||
* Constructs HADevice with the given unique ID (string).
|
||||
* Keep the unique ID short to save the memory.
|
||||
*
|
||||
* @param uniqueId String with the null terminator.
|
||||
*/
|
||||
HADevice(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Constructs HADevice using the given byte array as the unique ID.
|
||||
* It works in the same way as HADevice::setUniqueId method.
|
||||
*
|
||||
* @param uniqueId Bytes array that's going to be converted into the string.
|
||||
* @param length Number of bytes in the array.
|
||||
*/
|
||||
HADevice(const byte* uniqueId, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Deletes HASerializer and the availability topic if the shared availability was enabled.
|
||||
*/
|
||||
~HADevice();
|
||||
|
||||
/**
|
||||
* Returns pointer to the unique ID. It can be nullptr if the device has no ID assigned.
|
||||
*/
|
||||
inline const char* getUniqueId() const
|
||||
{ return _uniqueId; }
|
||||
|
||||
/**
|
||||
* Returns the instance of the HASerializer used by the device.
|
||||
* This method is used by all entities to serialize device's representation.
|
||||
*/
|
||||
inline const HASerializer* getSerializer() const
|
||||
{ return _serializer; }
|
||||
|
||||
/**
|
||||
* Returns true if the shared availability is enabled for the device.
|
||||
*/
|
||||
inline bool isSharedAvailabilityEnabled() const
|
||||
{ return _sharedAvailability; }
|
||||
|
||||
/**
|
||||
* Returns true if the extended unique IDs feature is enabled for the device.
|
||||
*/
|
||||
inline bool isExtendedUniqueIdsEnabled() const
|
||||
{ return _extendedUniqueIds; }
|
||||
|
||||
/**
|
||||
* Returns availability topic generated by the HADevice::enableSharedAvailability method.
|
||||
* It can be nullptr if the shared availability is not enabled.
|
||||
*/
|
||||
inline const char* getAvailabilityTopic() const
|
||||
{ return _availabilityTopic; }
|
||||
|
||||
/**
|
||||
* Returns online/offline state of the device.
|
||||
*/
|
||||
inline bool isAvailable() const
|
||||
{ return _available; }
|
||||
|
||||
/**
|
||||
* Enables the use of extended unique IDs for all registered device types.
|
||||
* The unique ID of each device type will be prefixed with the device's ID once enabled.
|
||||
*/
|
||||
inline void enableExtendedUniqueIds()
|
||||
{ _extendedUniqueIds = true; }
|
||||
|
||||
/**
|
||||
* Sets unique ID of the device based on the given byte array.
|
||||
* Each byte is converted into a hex string representation, so the final length of the unique ID will be twice as given.
|
||||
*
|
||||
* @param uniqueId Bytes array that's going to be converted into the string.
|
||||
* @param length Number of bytes in the array.
|
||||
* @note The unique ID can be set only once (via constructor or using this method).
|
||||
*/
|
||||
bool setUniqueId(const byte* uniqueId, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Sets the "manufacturer" property that's going to be displayed in the Home Assistant.
|
||||
*
|
||||
* @param manufacturer Any string. Keep it short to save the memory.
|
||||
*/
|
||||
void setManufacturer(const char* manufacturer);
|
||||
|
||||
/**
|
||||
* Sets the "model" property that's going to be displayed in the Home Assistant.
|
||||
*
|
||||
* @param model Any string. Keep it short to save the memory.
|
||||
*/
|
||||
void setModel(const char* model);
|
||||
|
||||
/**
|
||||
* Sets the "name" property that's going to be displayed in the Home Assistant.
|
||||
*
|
||||
* @param name Any string. Keep it short to save the memory.
|
||||
*/
|
||||
void setName(const char* name);
|
||||
|
||||
/**
|
||||
* Sets the "software version" property that's going to be displayed in the Home Assistant.
|
||||
*
|
||||
* @param softwareVersion Any string. Keep it short to save the memory.
|
||||
*/
|
||||
void setSoftwareVersion(const char* softwareVersion);
|
||||
|
||||
/**
|
||||
* Sets the "configuration URL" property that's going to be used by the Home Assistant.
|
||||
*
|
||||
* @param url Configuration URL to publish.
|
||||
*/
|
||||
void setConfigurationUrl(const char* url);
|
||||
|
||||
/**
|
||||
* Sets device's availability and publishes MQTT message on the availability topic.
|
||||
* If the device is not connected to an MQTT broker or the shared availability is not enabled then nothing happens.
|
||||
*
|
||||
* @param online Set to true if the device should be displayed as available in the HA panel.
|
||||
*/
|
||||
void setAvailability(bool online);
|
||||
|
||||
/**
|
||||
* Enables the shared availability feature.
|
||||
*/
|
||||
bool enableSharedAvailability();
|
||||
|
||||
/**
|
||||
* Enables MQTT LWT feature.
|
||||
* Please note that the shared availability needs to be enabled first.
|
||||
*/
|
||||
void enableLastWill();
|
||||
|
||||
/**
|
||||
* Publishes current availability of the device on the availability topic.
|
||||
* If the device is not connected to an MQTT broker or the shared availability is not enabled then nothing happens.
|
||||
* This method is called by the HAMqtt when the connection to an MQTT broker is acquired.
|
||||
*/
|
||||
void publishAvailability() const;
|
||||
|
||||
private:
|
||||
/// The unique ID of the device. It can be a memory allocated by HADevice::setUniqueId method.
|
||||
const char* _uniqueId;
|
||||
|
||||
/// Specifies whether HADevice class owns the _uniqueId pointer.
|
||||
bool _ownsUniqueId;
|
||||
|
||||
/// JSON serializer of the HADevice class. It's allocated in the constructor.
|
||||
HASerializer* _serializer;
|
||||
|
||||
/// The availability topic allocated by HADevice::enableSharedAvailability method.
|
||||
char* _availabilityTopic;
|
||||
|
||||
/// Specifies whether the shared availability is enabled.
|
||||
bool _sharedAvailability;
|
||||
|
||||
/// Specifies whether the device is available (online / offline).
|
||||
bool _available;
|
||||
|
||||
/// Specifies whether extended unique IDs feature is enabled.
|
||||
bool _extendedUniqueIds;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,359 @@
|
||||
#include "HAMqtt.h"
|
||||
|
||||
#ifndef ARDUINOHA_TEST
|
||||
#include <PubSubClient.h>
|
||||
#endif
|
||||
|
||||
#include "HADevice.h"
|
||||
#include "device-types/HABaseDeviceType.h"
|
||||
#include "mocks/PubSubClientMock.h"
|
||||
|
||||
#define HAMQTT_INIT \
|
||||
_device(device), \
|
||||
_messageCallback(nullptr), \
|
||||
_connectedCallback(nullptr), \
|
||||
_disconnectedCallback(nullptr), \
|
||||
_stateChangedCallback(nullptr), \
|
||||
_initialized(false), \
|
||||
_discoveryPrefix(DefaultDiscoveryPrefix), \
|
||||
_dataPrefix(DefaultDataPrefix), \
|
||||
_username(nullptr), \
|
||||
_password(nullptr), \
|
||||
_lastConnectionAttemptAt(0), \
|
||||
_devicesTypesNb(0), \
|
||||
_maxDevicesTypesNb(maxDevicesTypesNb), \
|
||||
_devicesTypes(new HABaseDeviceType*[maxDevicesTypesNb]), \
|
||||
_lastWillTopic(nullptr), \
|
||||
_lastWillMessage(nullptr), \
|
||||
_lastWillRetain(false), \
|
||||
_currentState(StateDisconnected)
|
||||
|
||||
static const char* DefaultDiscoveryPrefix = "homeassistant";
|
||||
static const char* DefaultDataPrefix = "aha";
|
||||
|
||||
HAMqtt* HAMqtt::_instance = nullptr;
|
||||
|
||||
void onMessageReceived(char* topic, uint8_t* payload, unsigned int length)
|
||||
{
|
||||
if (HAMqtt::instance() == nullptr || length > UINT16_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
HAMqtt::instance()->processMessage(topic, payload, static_cast<uint16_t>(length));
|
||||
}
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
HAMqtt::HAMqtt(
|
||||
PubSubClientMock* pubSub,
|
||||
HADevice& device,
|
||||
uint8_t maxDevicesTypesNb
|
||||
) :
|
||||
_mqtt(pubSub),
|
||||
HAMQTT_INIT
|
||||
{
|
||||
_instance = this;
|
||||
}
|
||||
#else
|
||||
HAMqtt::HAMqtt(
|
||||
Client& netClient,
|
||||
HADevice& device,
|
||||
uint8_t maxDevicesTypesNb
|
||||
) :
|
||||
_mqtt(new PubSubClient(netClient)),
|
||||
HAMQTT_INIT
|
||||
{
|
||||
_instance = this;
|
||||
}
|
||||
#endif
|
||||
|
||||
HAMqtt::~HAMqtt()
|
||||
{
|
||||
delete[] _devicesTypes;
|
||||
|
||||
if (_mqtt) {
|
||||
delete _mqtt;
|
||||
}
|
||||
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const IPAddress serverIp,
|
||||
const uint16_t serverPort,
|
||||
const char* username,
|
||||
const char* password
|
||||
)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: init server "))
|
||||
ARDUINOHA_DEBUG_PRINT(serverIp)
|
||||
ARDUINOHA_DEBUG_PRINT(F(":"))
|
||||
ARDUINOHA_DEBUG_PRINTLN(serverPort)
|
||||
|
||||
if (_device.getUniqueId() == nullptr) {
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: init failed. Missing device unique ID"))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_initialized) {
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: already initialized"))
|
||||
return false;
|
||||
}
|
||||
|
||||
_username = username;
|
||||
_password = password;
|
||||
_initialized = true;
|
||||
|
||||
_mqtt->setServer(serverIp, serverPort);
|
||||
_mqtt->setCallback(onMessageReceived);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const IPAddress serverIp,
|
||||
const char* username,
|
||||
const char* password
|
||||
)
|
||||
{
|
||||
return begin(serverIp, HAMQTT_DEFAULT_PORT, username, password);
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const char* serverHostname,
|
||||
const uint16_t serverPort,
|
||||
const char* username,
|
||||
const char* password
|
||||
)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: init server "))
|
||||
ARDUINOHA_DEBUG_PRINT(serverHostname)
|
||||
ARDUINOHA_DEBUG_PRINT(F(":"))
|
||||
ARDUINOHA_DEBUG_PRINTLN(serverPort)
|
||||
|
||||
if (_device.getUniqueId() == nullptr) {
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: init failed. Missing device unique ID"))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_initialized) {
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: already initialized"))
|
||||
return false;
|
||||
}
|
||||
|
||||
_username = username;
|
||||
_password = password;
|
||||
_initialized = true;
|
||||
|
||||
_mqtt->setServer(serverHostname, serverPort);
|
||||
_mqtt->setCallback(onMessageReceived);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HAMqtt::begin(
|
||||
const char* serverHostname,
|
||||
const char* username,
|
||||
const char* password
|
||||
)
|
||||
{
|
||||
return begin(serverHostname, HAMQTT_DEFAULT_PORT, username, password);
|
||||
}
|
||||
|
||||
bool HAMqtt::disconnect()
|
||||
{
|
||||
if (!_initialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: disconnecting"))
|
||||
|
||||
_initialized = false;
|
||||
_lastConnectionAttemptAt = 0;
|
||||
_mqtt->disconnect();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HAMqtt::loop()
|
||||
{
|
||||
if (!_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool result = _mqtt->loop();
|
||||
if (_currentState != _mqtt->state()) {
|
||||
setState(static_cast<ConnectionState>(_mqtt->state()));
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
connectToServer();
|
||||
}
|
||||
}
|
||||
|
||||
bool HAMqtt::isConnected() const
|
||||
{
|
||||
return _mqtt->connected();
|
||||
}
|
||||
|
||||
void HAMqtt::setKeepAlive(uint16_t keepAlive)
|
||||
{
|
||||
_mqtt->setKeepAlive(keepAlive);
|
||||
}
|
||||
|
||||
bool HAMqtt::setBufferSize(uint16_t size)
|
||||
{
|
||||
return _mqtt->setBufferSize(size);
|
||||
}
|
||||
|
||||
void HAMqtt::addDeviceType(HABaseDeviceType* deviceType)
|
||||
{
|
||||
if (_devicesTypesNb + 1 > _maxDevicesTypesNb) {
|
||||
return;
|
||||
}
|
||||
|
||||
_devicesTypes[_devicesTypesNb++] = deviceType;
|
||||
}
|
||||
|
||||
bool HAMqtt::publish(const char* topic, const char* payload, bool retained)
|
||||
{
|
||||
if (!isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: publishing "))
|
||||
ARDUINOHA_DEBUG_PRINT(topic)
|
||||
ARDUINOHA_DEBUG_PRINT(F(", len: "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(strlen(payload))
|
||||
|
||||
_mqtt->beginPublish(topic, strlen(payload), retained);
|
||||
_mqtt->write((const uint8_t*)(payload), strlen(payload));
|
||||
return _mqtt->endPublish();
|
||||
}
|
||||
|
||||
bool HAMqtt::beginPublish(
|
||||
const char* topic,
|
||||
uint16_t payloadLength,
|
||||
bool retained
|
||||
)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: begin publish "))
|
||||
ARDUINOHA_DEBUG_PRINT(topic)
|
||||
ARDUINOHA_DEBUG_PRINT(F(", len: "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(payloadLength)
|
||||
|
||||
return _mqtt->beginPublish(topic, payloadLength, retained);
|
||||
}
|
||||
|
||||
void HAMqtt::writePayload(const char* data, const uint16_t length)
|
||||
{
|
||||
writePayload(reinterpret_cast<const uint8_t*>(data), length);
|
||||
}
|
||||
|
||||
void HAMqtt::writePayload(const uint8_t* data, const uint16_t length)
|
||||
{
|
||||
_mqtt->write(data, length);
|
||||
}
|
||||
|
||||
void HAMqtt::writePayload(const __FlashStringHelper* src)
|
||||
{
|
||||
_mqtt->print(src);
|
||||
}
|
||||
|
||||
bool HAMqtt::endPublish()
|
||||
{
|
||||
return _mqtt->endPublish();
|
||||
}
|
||||
|
||||
bool HAMqtt::subscribe(const char* topic)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: subscribing "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(topic)
|
||||
|
||||
return _mqtt->subscribe(topic);
|
||||
}
|
||||
|
||||
void HAMqtt::processMessage(const char* topic, const uint8_t* payload, uint16_t length)
|
||||
{
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: received call "))
|
||||
ARDUINOHA_DEBUG_PRINT(topic)
|
||||
ARDUINOHA_DEBUG_PRINT(F(", len: "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(length)
|
||||
|
||||
if (_messageCallback) {
|
||||
_messageCallback(topic, payload, length);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < _devicesTypesNb; i++) {
|
||||
_devicesTypes[i]->onMqttMessage(topic, payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
void HAMqtt::connectToServer()
|
||||
{
|
||||
if (_lastConnectionAttemptAt > 0 &&
|
||||
(millis() - _lastConnectionAttemptAt) < ReconnectInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastConnectionAttemptAt = millis();
|
||||
setState(StateConnecting);
|
||||
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: MQTT connecting, client ID: "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(_device.getUniqueId())
|
||||
|
||||
_mqtt->connect(
|
||||
_device.getUniqueId(),
|
||||
_username,
|
||||
_password,
|
||||
_lastWillTopic,
|
||||
0,
|
||||
_lastWillRetain,
|
||||
_lastWillMessage,
|
||||
true
|
||||
);
|
||||
|
||||
if (isConnected()) {
|
||||
setState(StateConnected);
|
||||
} else {
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: failed to connect"))
|
||||
}
|
||||
}
|
||||
|
||||
void HAMqtt::onConnectedLogic()
|
||||
{
|
||||
if (_connectedCallback) {
|
||||
_connectedCallback();
|
||||
}
|
||||
|
||||
_device.publishAvailability();
|
||||
|
||||
for (uint8_t i = 0; i < _devicesTypesNb; i++) {
|
||||
_devicesTypes[i]->onMqttConnected();
|
||||
}
|
||||
}
|
||||
|
||||
void HAMqtt::setState(ConnectionState state)
|
||||
{
|
||||
ConnectionState previousState = _currentState;
|
||||
_currentState = state;
|
||||
|
||||
ARDUINOHA_DEBUG_PRINT(F("AHA: MQTT state changed to "))
|
||||
ARDUINOHA_DEBUG_PRINT(_currentState)
|
||||
ARDUINOHA_DEBUG_PRINT(F(", previous state: "))
|
||||
ARDUINOHA_DEBUG_PRINTLN(previousState)
|
||||
|
||||
if (_currentState == StateConnected) {
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: MQTT connected"))
|
||||
onConnectedLogic();
|
||||
} else if (previousState == StateConnected && _currentState != StateConnected) {
|
||||
ARDUINOHA_DEBUG_PRINTLN(F("AHA: MQTT disconnected"))
|
||||
|
||||
if (_disconnectedCallback) {
|
||||
_disconnectedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
if (_stateChangedCallback) {
|
||||
_stateChangedCallback(_currentState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
#ifndef AHA_HAMQTT_H
|
||||
#define AHA_HAMQTT_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Client.h>
|
||||
#include <IPAddress.h>
|
||||
#include "ArduinoHADefines.h"
|
||||
|
||||
#define HAMQTT_CALLBACK(name) void (*name)()
|
||||
#define HAMQTT_STATE_CALLBACK(name) void (*name)(ConnectionState state)
|
||||
#define HAMQTT_MESSAGE_CALLBACK(name) void (*name)(const char* topic, const uint8_t* payload, uint16_t length)
|
||||
#define HAMQTT_DEFAULT_PORT 1883
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
class PubSubClientMock;
|
||||
#else
|
||||
class PubSubClient;
|
||||
#endif
|
||||
|
||||
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
|
||||
#define HAMQTT_DEFAULT_DEVICES_LIMIT 6
|
||||
#else
|
||||
#define HAMQTT_DEFAULT_DEVICES_LIMIT 24
|
||||
#endif
|
||||
|
||||
class HADevice;
|
||||
class HABaseDeviceType;
|
||||
|
||||
#if defined(ARDUINO_API_VERSION)
|
||||
using namespace arduino;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This class is a wrapper for the PubSub API.
|
||||
* It's a central point of the library where instances of all device types are stored.
|
||||
*/
|
||||
class HAMqtt
|
||||
{
|
||||
public:
|
||||
enum ConnectionState {
|
||||
StateConnecting = -5,
|
||||
StateConnectionTimeout = -4,
|
||||
StateConnectionLost = -3,
|
||||
StateConnectionFailed = -2,
|
||||
StateDisconnected = -1,
|
||||
StateConnected = 0,
|
||||
StateBadProtocol = 1,
|
||||
StateBadClientId = 2,
|
||||
StateUnavailable = 3,
|
||||
StateBadCredentials = 4,
|
||||
StateUnauthorized = 5
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns existing instance (singleton) of the HAMqtt class.
|
||||
* It may be a null pointer if the HAMqtt object was never constructed or it was destroyed.
|
||||
*/
|
||||
inline static HAMqtt* instance()
|
||||
{ return _instance; }
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
explicit HAMqtt(
|
||||
PubSubClientMock* pubSub,
|
||||
HADevice& device,
|
||||
const uint8_t maxDevicesTypesNb = HAMQTT_DEFAULT_DEVICES_LIMIT
|
||||
);
|
||||
#else
|
||||
/**
|
||||
* Creates a new instance of the HAMqtt class.
|
||||
* Please note that only one instance of the class can be initialized at the same time.
|
||||
*
|
||||
* @param netClient The EthernetClient or WiFiClient that's going to be used for the network communication.
|
||||
* @param device An instance of the HADevice class representing your device.
|
||||
* @param maxDevicesTypesNb The maximum number of device types (sensors, switches, etc.) that you're going to implement.
|
||||
*/
|
||||
explicit HAMqtt(
|
||||
Client& netClient,
|
||||
HADevice& device,
|
||||
const uint8_t maxDevicesTypesNb = HAMQTT_DEFAULT_DEVICES_LIMIT
|
||||
);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Removes singleton of the HAMqtt class.
|
||||
*/
|
||||
~HAMqtt();
|
||||
|
||||
/**
|
||||
* Sets the prefix of the Home Assistant discovery topics.
|
||||
* It needs to match the prefix set in the HA admin panel.
|
||||
* The default prefix is "homeassistant".
|
||||
*
|
||||
* @param prefix The discovery topics' prefix.
|
||||
*/
|
||||
inline void setDiscoveryPrefix(const char* prefix)
|
||||
{ _discoveryPrefix = prefix; }
|
||||
|
||||
/**
|
||||
* Returns the discovery topics' prefix.
|
||||
*/
|
||||
inline const char* getDiscoveryPrefix() const
|
||||
{ return _discoveryPrefix; }
|
||||
|
||||
/**
|
||||
* Sets prefix of the data topics.
|
||||
* It may be useful if you want to pass MQTT traffic through a bridge.
|
||||
* The default prefix is "aha".
|
||||
*
|
||||
* @param prefix The data topics' prefix.
|
||||
*/
|
||||
inline void setDataPrefix(const char* prefix)
|
||||
{ _dataPrefix = prefix; }
|
||||
|
||||
/**
|
||||
* Returns the data topics' prefix.
|
||||
*/
|
||||
inline const char* getDataPrefix() const
|
||||
{ return _dataPrefix; }
|
||||
|
||||
/**
|
||||
* Returns instance of the device assigned to the HAMqtt class.
|
||||
* It's the same object (pointer) that was passed to the HAMqtt constructor.
|
||||
*/
|
||||
inline HADevice const* getDevice() const
|
||||
{ return &_device; }
|
||||
|
||||
/**
|
||||
* Registers a new callback method that will be called when the device receives an MQTT message.
|
||||
* Please note that the callback is also fired by internal MQTT messages used by the library.
|
||||
* You should always verify the topic of the received message.
|
||||
*
|
||||
* @param callback Callback method.
|
||||
*/
|
||||
inline void onMessage(HAMQTT_MESSAGE_CALLBACK(callback))
|
||||
{ _messageCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers a new callback method that will be called each time a connection to the MQTT broker is acquired.
|
||||
* The callback is also fired after reconnecting to the broker.
|
||||
* You can use this method to register topics' subscriptions.
|
||||
*
|
||||
* @param callback Callback method.
|
||||
*/
|
||||
inline void onConnected(HAMQTT_CALLBACK(callback))
|
||||
{ _connectedCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers a new callback method that will be called each time the connection to the MQTT broker is lost.
|
||||
*
|
||||
* @param callback Callback method.
|
||||
*/
|
||||
inline void onDisconnected(HAMQTT_CALLBACK(callback))
|
||||
{ _disconnectedCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers a new callback method that will be called each time the connection state changes.
|
||||
*
|
||||
* @param callback Callback method.
|
||||
*/
|
||||
inline void onStateChanged(HAMQTT_STATE_CALLBACK(callback))
|
||||
{ _stateChangedCallback = callback; }
|
||||
|
||||
/**
|
||||
* Returns the current state of the MQTT connection.
|
||||
*/
|
||||
inline ConnectionState getState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets parameters of the MQTT connection using the IP address and port.
|
||||
* The library will try to connect to the broker in first loop cycle.
|
||||
* Please note that the library automatically reconnects to the broker if connection is lost.
|
||||
*
|
||||
* @param serverIp IP address of the MQTT broker.
|
||||
* @param serverPort Port of the MQTT broker.
|
||||
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
*/
|
||||
bool begin(
|
||||
const IPAddress serverIp,
|
||||
const uint16_t serverPort = HAMQTT_DEFAULT_PORT,
|
||||
const char* username = nullptr,
|
||||
const char* password = nullptr
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets parameters of the MQTT connection using the IP address and the default port (1883).
|
||||
* The library will try to connect to the broker in first loop cycle.
|
||||
* Please note that the library automatically reconnects to the broker if connection is lost.
|
||||
*
|
||||
* @param serverIp IP address of the MQTT broker.
|
||||
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
*/
|
||||
bool begin(
|
||||
const IPAddress serverIp,
|
||||
const char* username,
|
||||
const char* password
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets parameters of the MQTT connection using the hostname and port.
|
||||
* The library will try to connect to the broker in first loop cycle.
|
||||
* Please note that the library automatically reconnects to the broker if connection is lost.
|
||||
*
|
||||
* @param serverHostname Hostname of the MQTT broker.
|
||||
* @param serverPort Port of the MQTT broker.
|
||||
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
*/
|
||||
bool begin(
|
||||
const char* serverHostname,
|
||||
const uint16_t serverPort = HAMQTT_DEFAULT_PORT,
|
||||
const char* username = nullptr,
|
||||
const char* password = nullptr
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets parameters of the MQTT connection using the hostname and the default port (1883).
|
||||
* The library will try to connect to the broker in first loop cycle.
|
||||
* Please note that the library automatically reconnects to the broker if connection is lost.
|
||||
*
|
||||
* @param serverHostname Hostname of the MQTT broker.
|
||||
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
|
||||
*/
|
||||
bool begin(
|
||||
const char* serverHostname,
|
||||
const char* username,
|
||||
const char* password
|
||||
);
|
||||
|
||||
/**
|
||||
* Closes the MQTT connection.
|
||||
*/
|
||||
bool disconnect();
|
||||
|
||||
/**
|
||||
* This method should be called periodically inside the main loop of the firmware.
|
||||
* It's safe to call this method in some interval (like 5ms).
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/**
|
||||
* Returns true if connection to the MQTT broker is established.
|
||||
*/
|
||||
bool isConnected() const;
|
||||
|
||||
/**
|
||||
* Sets keep alive for the MQTT connection.
|
||||
* By default it's 15 seconds.
|
||||
*
|
||||
* @param keepAlive Number of seconds to keep connection alive.
|
||||
*/
|
||||
void setKeepAlive(uint16_t keepAlive);
|
||||
|
||||
/**
|
||||
* Sets the buffer size for the MQTT connection.
|
||||
* By default it's 256 bytes.
|
||||
*
|
||||
* @param size Size of the buffer.
|
||||
*/
|
||||
bool setBufferSize(uint16_t size);
|
||||
|
||||
/**
|
||||
* Adds a new device's type to the MQTT.
|
||||
* Each time the connection with MQTT broker is acquired, the HAMqtt class
|
||||
* calls "onMqttConnected" method in all devices' types instances.
|
||||
*
|
||||
* @note The HAMqtt class doesn't take ownership of the given pointer.
|
||||
* @param deviceType Instance of the device's type (HASwitch, HABinarySensor, etc.).
|
||||
*/
|
||||
void addDeviceType(HABaseDeviceType* deviceType);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with given topic and payload.
|
||||
* Message won't be published if the connection with the MQTT broker is not established.
|
||||
* In this case method returns false.
|
||||
*
|
||||
* @param topic The topic to publish.
|
||||
* @param payload The payload to publish (it may be empty const char).
|
||||
* @param retained Specifies whether message should be retained.
|
||||
*/
|
||||
bool publish(const char* topic, const char* payload, bool retained = false);
|
||||
|
||||
/**
|
||||
* Begins publishing of a message with the given properties.
|
||||
* When this method returns true the payload can be written using HAMqtt::writePayload method.
|
||||
*
|
||||
* @param topic Topic of the published message.
|
||||
* @param payloadLength Length of the payload (bytes) that's going to be published.
|
||||
* @param retained Specifies whether the published message should be retained.
|
||||
*/
|
||||
bool beginPublish(const char* topic, uint16_t payloadLength, bool retained = false);
|
||||
|
||||
/**
|
||||
* Writes given string to the TCP stream.
|
||||
* Please note that before writing any data the HAMqtt::beginPublish method
|
||||
* needs to be called.
|
||||
*
|
||||
* @param data The string to publish.
|
||||
* @param length Length of the data (bytes).
|
||||
*/
|
||||
void writePayload(const char* data, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Writes given data to the TCP stream.
|
||||
* Please note that before writing any data the HAMqtt::beginPublish method
|
||||
* needs to be called.
|
||||
*
|
||||
* @param data The data to publish.
|
||||
* @param length Length of the data (bytes).
|
||||
*/
|
||||
void writePayload(const uint8_t* data, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Writes given progmem data to the TCP stream.
|
||||
* Please note that before writing any data the HAMqtt::beginPublish method
|
||||
* needs to be called.
|
||||
*
|
||||
* @param data Progmem data to publish.
|
||||
*/
|
||||
void writePayload(const __FlashStringHelper* data);
|
||||
|
||||
/**
|
||||
* Finishes publishing of a message.
|
||||
* After calling this method the message will be processed by the broker.
|
||||
*/
|
||||
bool endPublish();
|
||||
|
||||
/**
|
||||
* Subscribes to the given topic.
|
||||
* Whenever a new message is received the onMqttMessage callback in all
|
||||
* devices types is called.
|
||||
*
|
||||
* Please note that you need to subscribe topic each time the connection
|
||||
* with the broker is acquired.
|
||||
*
|
||||
* @param topic Topic to subscribe.
|
||||
*/
|
||||
bool subscribe(const char* topic);
|
||||
|
||||
/**
|
||||
* Enables the last will message that will be produced when the device disconnects from the broker.
|
||||
* If you want to change availability of the device in Home Assistant panel
|
||||
* please use enableLastWill() method from the HADevice class instead.
|
||||
*
|
||||
* @param lastWillTopic The topic to publish.
|
||||
* @param lastWillMessage The message (payload) to publish.
|
||||
* @param lastWillRetain Specifies whether the published message should be retained.
|
||||
*/
|
||||
inline void setLastWill(
|
||||
const char* lastWillTopic,
|
||||
const char* lastWillMessage,
|
||||
bool lastWillRetain
|
||||
) {
|
||||
_lastWillTopic = lastWillTopic;
|
||||
_lastWillMessage = lastWillMessage;
|
||||
_lastWillRetain = lastWillRetain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes MQTT message received from the broker (subscription).
|
||||
*
|
||||
* @note Do not use this method on your own. It's only for the internal purpose.
|
||||
* @param topic Topic of the message.
|
||||
* @param payload Content of the message.
|
||||
* @param length Length of the message.
|
||||
*/
|
||||
void processMessage(const char* topic, const uint8_t* payload, uint16_t length);
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
inline uint8_t getDevicesTypesNb() const
|
||||
{ return _devicesTypesNb; }
|
||||
|
||||
inline HABaseDeviceType** getDevicesTypes() const
|
||||
{ return _devicesTypes; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
/// Interval between MQTT reconnects (milliseconds).
|
||||
static const uint16_t ReconnectInterval = 10000;
|
||||
|
||||
/// Living instance of the HAMqtt class. It can be nullptr.
|
||||
static HAMqtt* _instance;
|
||||
|
||||
/**
|
||||
* Attempts to connect to the MQTT broker.
|
||||
* The method uses properties passed to the "begin" method.
|
||||
*/
|
||||
void connectToServer();
|
||||
|
||||
/**
|
||||
* This method is called each time the connection with MQTT broker is acquired.
|
||||
*/
|
||||
void onConnectedLogic();
|
||||
|
||||
/**
|
||||
* Sets the state of the MQTT connection.
|
||||
*/
|
||||
void setState(ConnectionState state);
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
PubSubClientMock* _mqtt;
|
||||
#else
|
||||
/// Instance of the PubSubClient class. It's initialized in the constructor.
|
||||
PubSubClient* _mqtt;
|
||||
#endif
|
||||
|
||||
/// Instance of the HADevice passed to the constructor.
|
||||
const HADevice& _device;
|
||||
|
||||
/// The callback method that will be called when an MQTT message is received.
|
||||
HAMQTT_MESSAGE_CALLBACK(_messageCallback);
|
||||
|
||||
/// The callback method that will be called when the MQTT connection is acquired.
|
||||
HAMQTT_CALLBACK(_connectedCallback);
|
||||
|
||||
/// The callback method that will be called when the MQTT connection is lost.
|
||||
HAMQTT_CALLBACK(_disconnectedCallback);
|
||||
|
||||
/// The callback method that will be called when the MQTT connection state changes.
|
||||
HAMQTT_STATE_CALLBACK(_stateChangedCallback);
|
||||
|
||||
/// Specifies whether the HAMqtt::begin method was ever called.
|
||||
bool _initialized;
|
||||
|
||||
/// Teh discovery prefix that's used for the configuration messages.
|
||||
const char* _discoveryPrefix;
|
||||
|
||||
/// The data prefix that's used for publishing data messages.
|
||||
const char* _dataPrefix;
|
||||
|
||||
/// The username used for the authentication. It's set in the HAMqtt::begin method.
|
||||
const char* _username;
|
||||
|
||||
/// The password used for the authentication. It's set in the HAMqtt::begin method.
|
||||
const char* _password;
|
||||
|
||||
/// Time of the last connection attemps (milliseconds since boot).
|
||||
uint32_t _lastConnectionAttemptAt;
|
||||
|
||||
/// The amount of registered devices types.
|
||||
uint8_t _devicesTypesNb;
|
||||
|
||||
/// The maximum amount of devices types that can be registered.
|
||||
uint8_t _maxDevicesTypesNb;
|
||||
|
||||
/// Pointers of all registered devices types (array of pointers).
|
||||
HABaseDeviceType** _devicesTypes;
|
||||
|
||||
/// The last will topic set by HAMqtt::setLastWill
|
||||
const char* _lastWillTopic;
|
||||
|
||||
/// The last will message set by HAMqtt::setLastWill
|
||||
const char* _lastWillMessage;
|
||||
|
||||
/// The last will retain set by HAMqtt::setLastWill
|
||||
bool _lastWillRetain;
|
||||
|
||||
/// The last known state of the MQTT connection.
|
||||
ConnectionState _currentState;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,206 @@
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../HAMqtt.h"
|
||||
#include "../HADevice.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HABaseDeviceType::HABaseDeviceType(
|
||||
const __FlashStringHelper* componentName,
|
||||
const char* uniqueId
|
||||
) :
|
||||
_componentName(componentName),
|
||||
_uniqueId(uniqueId),
|
||||
_name(nullptr),
|
||||
_objectId(nullptr),
|
||||
_serializer(nullptr),
|
||||
_availability(AvailabilityDefault)
|
||||
{
|
||||
if (mqtt()) {
|
||||
mqtt()->addDeviceType(this);
|
||||
}
|
||||
}
|
||||
|
||||
void HABaseDeviceType::setAvailability(bool online)
|
||||
{
|
||||
_availability = (online ? AvailabilityOnline : AvailabilityOffline);
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
HAMqtt* HABaseDeviceType::mqtt()
|
||||
{
|
||||
return HAMqtt::instance();
|
||||
}
|
||||
|
||||
void HABaseDeviceType::subscribeTopic(
|
||||
const char* uniqueId,
|
||||
const __FlashStringHelper* topic
|
||||
)
|
||||
{
|
||||
const uint16_t topicLength = HASerializer::calculateDataTopicLength(
|
||||
uniqueId,
|
||||
topic
|
||||
);
|
||||
if (topicLength == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char fullTopic[topicLength];
|
||||
if (!HASerializer::generateDataTopic(
|
||||
fullTopic,
|
||||
uniqueId,
|
||||
topic
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HAMqtt::instance()->subscribe(fullTopic);
|
||||
}
|
||||
|
||||
void HABaseDeviceType::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
(void)topic;
|
||||
(void)payload;
|
||||
(void)length;
|
||||
}
|
||||
|
||||
void HABaseDeviceType::destroySerializer()
|
||||
{
|
||||
if (_serializer) {
|
||||
delete _serializer;
|
||||
_serializer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HABaseDeviceType::publishConfig()
|
||||
{
|
||||
buildSerializer();
|
||||
|
||||
if (_serializer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t topicLength = HASerializer::calculateConfigTopicLength(
|
||||
componentName(),
|
||||
uniqueId()
|
||||
);
|
||||
const uint16_t dataLength = _serializer->calculateSize();
|
||||
|
||||
if (topicLength > 0 && dataLength > 0) {
|
||||
char topic[topicLength];
|
||||
HASerializer::generateConfigTopic(
|
||||
topic,
|
||||
componentName(),
|
||||
uniqueId()
|
||||
);
|
||||
|
||||
if (mqtt()->beginPublish(topic, dataLength, true)) {
|
||||
_serializer->flush();
|
||||
mqtt()->endPublish();
|
||||
}
|
||||
}
|
||||
|
||||
destroySerializer();
|
||||
}
|
||||
|
||||
void HABaseDeviceType::publishAvailability()
|
||||
{
|
||||
const HADevice* device = mqtt()->getDevice();
|
||||
if (
|
||||
!device ||
|
||||
device->isSharedAvailabilityEnabled() ||
|
||||
!isAvailabilityConfigured()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishOnDataTopic(
|
||||
AHATOFSTR(HAAvailabilityTopic),
|
||||
_availability == AvailabilityOnline
|
||||
? AHATOFSTR(HAOnline)
|
||||
: AHATOFSTR(HAOffline),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HABaseDeviceType::publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const __FlashStringHelper* payload,
|
||||
bool retained
|
||||
)
|
||||
{
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
topic,
|
||||
reinterpret_cast<const uint8_t*>(payload),
|
||||
strlen_P(AHAFROMFSTR(payload)),
|
||||
retained,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HABaseDeviceType::publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const char* payload,
|
||||
bool retained
|
||||
)
|
||||
{
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
topic,
|
||||
reinterpret_cast<const uint8_t*>(payload),
|
||||
strlen(payload),
|
||||
retained
|
||||
);
|
||||
}
|
||||
|
||||
bool HABaseDeviceType::publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length,
|
||||
bool retained,
|
||||
bool isProgmemData
|
||||
)
|
||||
{
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint16_t topicLength = HASerializer::calculateDataTopicLength(
|
||||
uniqueId(),
|
||||
topic
|
||||
);
|
||||
if (topicLength == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char fullTopic[topicLength];
|
||||
if (!HASerializer::generateDataTopic(
|
||||
fullTopic,
|
||||
uniqueId(),
|
||||
topic
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mqtt()->beginPublish(fullTopic, length, retained)) {
|
||||
if (isProgmemData) {
|
||||
mqtt()->writePayload(AHATOFSTR(payload));
|
||||
} else {
|
||||
mqtt()->writePayload(payload, length);
|
||||
}
|
||||
|
||||
return mqtt()->endPublish();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
#ifndef AHA_HABASEDEVICETYPE_H
|
||||
#define AHA_HABASEDEVICETYPE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "../ArduinoHADefines.h"
|
||||
|
||||
class HAMqtt;
|
||||
class HASerializer;
|
||||
|
||||
class HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
enum NumberPrecision {
|
||||
/// No digits after the decimal point.
|
||||
PrecisionP0 = 0,
|
||||
|
||||
/// One digit after the decimal point.
|
||||
PrecisionP1,
|
||||
|
||||
/// Two digits after the decimal point.
|
||||
PrecisionP2,
|
||||
|
||||
/// Three digits after the decimal point.
|
||||
PrecisionP3
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new device type instance and registers it in the HAMqtt class.
|
||||
*
|
||||
* @param componentName The name of the Home Assistant component (e.g. `binary_sensor`).
|
||||
* You can find all available component names in the Home Assistant documentation.
|
||||
* The component name needs to be stored in the flash memory.
|
||||
* @param uniqueId The unique ID of the device type. It needs to be unique in a scope of the HADevice.
|
||||
*/
|
||||
HABaseDeviceType(
|
||||
const __FlashStringHelper* componentName,
|
||||
const char* uniqueId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns unique ID of the device type.
|
||||
*/
|
||||
inline const char* uniqueId() const
|
||||
{ return _uniqueId; }
|
||||
|
||||
/**
|
||||
* Returns component name defined by the device type.
|
||||
* It's used for the MQTT discovery topic.
|
||||
*/
|
||||
inline const __FlashStringHelper* componentName() const
|
||||
{ return _componentName; }
|
||||
|
||||
/**
|
||||
* Returns `true` if the availability was configured for this device type.
|
||||
*/
|
||||
inline bool isAvailabilityConfigured() const
|
||||
{ return (_availability != AvailabilityDefault); }
|
||||
|
||||
/**
|
||||
* Returns online state of the device type.
|
||||
*/
|
||||
inline bool isOnline() const
|
||||
{ return (_availability == AvailabilityOnline); }
|
||||
|
||||
/**
|
||||
* Sets name of the device type that will be used as a label in the HA panel.
|
||||
* Keep the name short to save the resources.
|
||||
*
|
||||
* @param name The device type name.
|
||||
*/
|
||||
inline void setName(const char* name)
|
||||
{ _name = name; }
|
||||
|
||||
/**
|
||||
* Returns name of the deviced type that was assigned via setName method.
|
||||
* It can be nullptr if there is no name assigned.
|
||||
*/
|
||||
inline const char* getName() const
|
||||
{ return _name; }
|
||||
|
||||
/**
|
||||
* Sets object ID that will be used by HA to generate entity ID.
|
||||
* Keep the ID short to save the resources.
|
||||
*
|
||||
* @param objectId The object ID.
|
||||
*/
|
||||
inline void setObjectId(const char* objectId)
|
||||
{ _objectId = objectId; }
|
||||
|
||||
/**
|
||||
* Returns the object ID that was set by setObjectId method.
|
||||
* It can be nullptr if there is no ID assigned.
|
||||
*/
|
||||
inline const char* getObjectId() const
|
||||
{ return _objectId; }
|
||||
|
||||
/**
|
||||
* Sets availability of the device type.
|
||||
* Setting the initial availability enables availability reporting for this device type.
|
||||
* Please note that not all device types support this feature.
|
||||
* Follow HA documentation of a specific device type to get more information.
|
||||
*
|
||||
* @param online Specifies whether the device type is online.
|
||||
*/
|
||||
virtual void setAvailability(bool online);
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
inline HASerializer* getSerializer() const
|
||||
{ return _serializer; }
|
||||
|
||||
inline void buildSerializerTest()
|
||||
{ buildSerializer(); }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns instance of the HAMqtt class.
|
||||
*/
|
||||
static HAMqtt* mqtt();
|
||||
|
||||
/**
|
||||
* Subscribes to the given data topic.
|
||||
*
|
||||
* @param uniqueId THe unique ID of the device type assigned via the constructor.
|
||||
* @param topic Topic to subscribe (progmem string).
|
||||
*/
|
||||
static void subscribeTopic(
|
||||
const char* uniqueId,
|
||||
const __FlashStringHelper* topic
|
||||
);
|
||||
|
||||
/**
|
||||
* This method should build serializer that will be used for publishing the configuration.
|
||||
* The serializer is built each time the MQTT connection is acquired.
|
||||
* Follow implementation of the existing device types to get better understanding of the logic.
|
||||
*/
|
||||
virtual void buildSerializer() { };
|
||||
|
||||
/**
|
||||
* This method is called each time the MQTT connection is acquired.
|
||||
* Each device type should publish its configuration and availability.
|
||||
* It can be also used for subscribing to MQTT topics.
|
||||
*/
|
||||
virtual void onMqttConnected() = 0;
|
||||
|
||||
/**
|
||||
* This method is called each time the device receives a MQTT message.
|
||||
* It can be any MQTT message so the method should always verify the topic.
|
||||
*
|
||||
* @param topic The topic on which the message was produced.
|
||||
* @param payload The payload of the message. It can be nullptr.
|
||||
* @param length The length of the payload.
|
||||
*/
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
);
|
||||
|
||||
/**
|
||||
* Destroys the existing serializer.
|
||||
*/
|
||||
void destroySerializer();
|
||||
|
||||
/**
|
||||
* Publishes configuration of this device type on the HA discovery topic.
|
||||
*/
|
||||
void publishConfig();
|
||||
|
||||
/**
|
||||
* Publishes current availability of the device type.
|
||||
* The message is only produced if the availability is configured for this device type.
|
||||
*/
|
||||
void publishAvailability();
|
||||
|
||||
/**
|
||||
* Publishes the given flash string on the data topic.
|
||||
*
|
||||
* @param topic The topic to publish on (progmem string).
|
||||
* @param payload The message's payload (progmem string).
|
||||
* @param retained Specifies whether the message should be retained.
|
||||
*/
|
||||
bool publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const __FlashStringHelper* payload,
|
||||
bool retained = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Publishes the given string on the data topic.
|
||||
*
|
||||
* @param topic The topic to publish on (progmem string).
|
||||
* @param payload The message's payload.
|
||||
* @param retained Specifies whether the message should be retained.
|
||||
*/
|
||||
bool publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const char* payload,
|
||||
bool retained = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Publishes the given data on the data topic.
|
||||
*
|
||||
* @param topic The topic to publish on (progmem string).
|
||||
* @param payload The message's payload.
|
||||
* @param length The length of the payload.
|
||||
* @param retained Specifies whether the message should be retained.
|
||||
* @param isProgmemData Specifies whether the given data is stored in the flash memory.
|
||||
*/
|
||||
bool publishOnDataTopic(
|
||||
const __FlashStringHelper* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length,
|
||||
bool retained = false,
|
||||
bool isProgmemData = false
|
||||
);
|
||||
|
||||
/// The component name that was assigned via the constructor.
|
||||
const __FlashStringHelper* const _componentName;
|
||||
|
||||
/// The unique ID that was assigned via the constructor.
|
||||
const char* _uniqueId;
|
||||
|
||||
/// The name that was set using setName method. It can be nullptr.
|
||||
const char* _name;
|
||||
|
||||
/// The object ID that was set using setObjectId method. It can be nullptr.
|
||||
const char* _objectId;
|
||||
|
||||
/// HASerializer that belongs to this device type. It can be nullptr.
|
||||
HASerializer* _serializer;
|
||||
|
||||
private:
|
||||
enum Availability {
|
||||
AvailabilityDefault = 0,
|
||||
AvailabilityOnline,
|
||||
AvailabilityOffline
|
||||
};
|
||||
|
||||
/// The current availability of this device type. AvailabilityDefault means that the initial availability was never set.
|
||||
Availability _availability;
|
||||
friend class HAMqtt;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "HABinarySensor.h"
|
||||
#ifndef EX_ARDUINOHA_BINARY_SENSOR
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HABinarySensor::HABinarySensor(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentBinarySensor), uniqueId),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_currentState(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HABinarySensor::setState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HABinarySensor::setExpireAfter(uint16_t expireAfter)
|
||||
{
|
||||
if (expireAfter > 0) {
|
||||
_expireAfter.setBaseValue(expireAfter);
|
||||
} else {
|
||||
_expireAfter.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void HABinarySensor::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 9); // 9 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_expireAfter.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAExpireAfterProperty),
|
||||
&_expireAfter,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
}
|
||||
|
||||
void HABinarySensor::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
bool HABinarySensor::publishState(const bool state)
|
||||
{
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
#ifndef AHA_HABINARYSENSOR_H
|
||||
#define AHA_HABINARYSENSOR_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_BINARY_SENSOR
|
||||
|
||||
/**
|
||||
* HABinarySensor represents a binary sensor that allows publishing on/off state to the Home Assistant panel.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/binary_sensor.mqtt/
|
||||
*/
|
||||
class HABinarySensor : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the button. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HABinarySensor(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Changes state of the sensor and publish MQTT message.
|
||||
* Please note that if a new value is the same as the previous one the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the sensor (`true` - on, `false` - off).
|
||||
* @param force Forces to update the state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets the number of seconds after the sensor’s state expires, if it’s not updated.
|
||||
* By default the sensors state never expires.
|
||||
*
|
||||
* @param expireAfter The number of seconds.
|
||||
*/
|
||||
void setExpireAfter(uint16_t expireAfter);
|
||||
|
||||
/**
|
||||
* Sets the current state of the sensor without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the state before the connection with the MQTT broker is acquired.
|
||||
*
|
||||
* @param state New state of the sensor.
|
||||
*/
|
||||
inline void setCurrentState(const bool state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns the last known state of the sensor.
|
||||
*/
|
||||
inline bool getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/binary_sensor/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the sensor.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(bool state);
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the sensor. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// It defines the number of seconds after the sensor’s state expires, if it’s not updated. By default the sensors state never expires.
|
||||
HANumeric _expireAfter;
|
||||
|
||||
/// Current state of the sensor. By default it's false.
|
||||
bool _currentState;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "HAButton.h"
|
||||
#ifndef EX_ARDUINOHA_BUTTON
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HAButton::HAButton(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentButton), uniqueId),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HAButton::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 9); // 9 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
// optional property
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAButton::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAButton::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
|
||||
if (_commandCallback && HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
_commandCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef AHA_HABUTTON_H
|
||||
#define AHA_HABUTTON_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_BUTTON
|
||||
|
||||
#define HABUTTON_CALLBACK(name) void (*name)(HAButton* sender)
|
||||
|
||||
/**
|
||||
* HAButton represents a button that's displayed in the Home Assistant panel and
|
||||
* triggers some logic on your Arduino/ESP device once clicked.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/button.mqtt/
|
||||
*/
|
||||
class HAButton : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the button. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HAButton(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/button/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the button.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the button's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the press command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same button.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onCommand(HABUTTON_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The command callback that will be called once clicking the button in HA panel.
|
||||
HABUTTON_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "HACamera.h"
|
||||
#ifndef EX_ARDUINOHA_CAMERA
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HACamera::HACamera(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentCamera), uniqueId),
|
||||
_encoding(EncodingBinary),
|
||||
_icon(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HACamera::publishImage(const uint8_t* data, const uint16_t length)
|
||||
{
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HATopic), data, length, true);
|
||||
}
|
||||
|
||||
void HACamera::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 8); // 8 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAEncodingProperty),
|
||||
getEncodingProperty(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HATopic));
|
||||
}
|
||||
|
||||
void HACamera::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HACamera::getEncodingProperty() const
|
||||
{
|
||||
switch (_encoding) {
|
||||
case EncodingBase64:
|
||||
return AHATOFSTR(HAEncodingBase64);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef AHA_HACAMERA_H
|
||||
#define AHA_HACAMERA_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_CAMERA
|
||||
|
||||
/**
|
||||
* HACamera allows to display an image in the Home Assistant panel.
|
||||
* It can be used for publishing an image from the ESP32-Cam module or any other
|
||||
* module that's equipped with a camera.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/camera.mqtt/
|
||||
*/
|
||||
class HACamera : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
enum ImageEncoding {
|
||||
EncodingBinary = 1,
|
||||
EncodingBase64
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the camera. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HACamera(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Publishes MQTT message with the given image data as a message content.
|
||||
* It updates image displayed in the Home Assistant panel.
|
||||
*
|
||||
* @param data Image data (raw binary data or base64)
|
||||
* @param length The length of the data.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishImage(const uint8_t* data, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Sets encoding of the image content.
|
||||
* Bu default Home Assistant expects raw binary data (e.g. JPEG binary data).
|
||||
*
|
||||
* @param encoding The image's data encoding.
|
||||
*/
|
||||
inline void setEncoding(const ImageEncoding encoding)
|
||||
{ _encoding = encoding; }
|
||||
|
||||
/**
|
||||
* Sets icon of the camera.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns progmem string representing the encoding property.
|
||||
*/
|
||||
const __FlashStringHelper* getEncodingProperty() const;
|
||||
|
||||
/// The encoding of the image's data. By default it's `HACamera::EncodingBinary`.
|
||||
ImageEncoding _encoding;
|
||||
|
||||
/// The icon of the camera. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "HACover.h"
|
||||
#ifndef EX_ARDUINOHA_COVER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HACover::HACover(const char* uniqueId, const Features features) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentCover), uniqueId),
|
||||
_features(features),
|
||||
_currentState(StateUnknown),
|
||||
_currentPosition(DefaultPosition),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HACover::setState(const CoverState state, const bool force)
|
||||
{
|
||||
if (!force && _currentState == state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HACover::setPosition(const int16_t position, const bool force)
|
||||
{
|
||||
if (!force && _currentPosition == position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishPosition(position)) {
|
||||
_currentPosition = position;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HACover::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 12); // 12 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
|
||||
if (_features & PositionFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAPositionTopic));
|
||||
}
|
||||
}
|
||||
|
||||
void HACover::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
publishPosition(_currentPosition);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HACover::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HACover::publishState(CoverState state)
|
||||
{
|
||||
if (state == StateUnknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (state) {
|
||||
case StateClosed:
|
||||
stateStr = AHATOFSTR(HAClosedState);
|
||||
break;
|
||||
|
||||
case StateClosing:
|
||||
stateStr = AHATOFSTR(HAClosingState);
|
||||
break;
|
||||
|
||||
case StateOpen:
|
||||
stateStr = AHATOFSTR(HAOpenState);
|
||||
break;
|
||||
|
||||
case StateOpening:
|
||||
stateStr = AHATOFSTR(HAOpeningState);
|
||||
break;
|
||||
|
||||
case StateStopped:
|
||||
stateStr = AHATOFSTR(HAStoppedState);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), stateStr, true);
|
||||
}
|
||||
|
||||
bool HACover::publishPosition(int16_t position)
|
||||
{
|
||||
if (position == DefaultPosition || !(_features & PositionFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[6 + 1] = {0}; // int16_t digits with null terminator
|
||||
HANumeric(position, 0).toStr(str);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAPositionTopic), str, true);
|
||||
}
|
||||
|
||||
void HACover::handleCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_commandCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HACloseCommand, length) == 0) {
|
||||
_commandCallback(CommandClose, this);
|
||||
} else if (memcmp_P(cmd, HAOpenCommand, length) == 0) {
|
||||
_commandCallback(CommandOpen, this);
|
||||
} else if (memcmp_P(cmd, HAStopCommand, length) == 0) {
|
||||
_commandCallback(CommandStop, this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,210 @@
|
||||
#ifndef AHA_HACOVER_H
|
||||
#define AHA_HACOVER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_COVER
|
||||
|
||||
#define HACOVER_CALLBACK(name) void (*name)(CoverCommand cmd, HACover* sender)
|
||||
|
||||
/**
|
||||
* HACover allows to control a cover (such as blinds, a roller shutter or a garage door).
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/cover.mqtt/
|
||||
*/
|
||||
class HACover : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
static const int16_t DefaultPosition = -32768;
|
||||
|
||||
enum CoverState {
|
||||
StateUnknown = 0,
|
||||
StateClosed,
|
||||
StateClosing,
|
||||
StateOpen,
|
||||
StateOpening,
|
||||
StateStopped
|
||||
};
|
||||
|
||||
enum CoverCommand {
|
||||
CommandOpen,
|
||||
CommandClose,
|
||||
CommandStop
|
||||
};
|
||||
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
PositionFeature = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the cover. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the fan.
|
||||
*/
|
||||
HACover(const char* uniqueId, const Features features = DefaultFeatures);
|
||||
|
||||
/**
|
||||
* Changes state of the cover and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the cover.
|
||||
* @param force Forces to update state without comparing it to previous known state.
|
||||
* @returns Returns true if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const CoverState state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the position of the cover and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param position The new position of the cover (0-100).
|
||||
* @param force Forces to update the state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setPosition(const int16_t position, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets the current state of the cover without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the state before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param state The new state of the cover.
|
||||
*/
|
||||
inline void setCurrentState(const CoverState state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the cover.
|
||||
* By default the state is set to CoverState::StateUnknown
|
||||
*/
|
||||
inline CoverState getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets the current position of the cover without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the position before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param position The new position of the cover (0-100).
|
||||
*/
|
||||
inline void setCurrentPosition(const int16_t position)
|
||||
{ _currentPosition = position; }
|
||||
|
||||
/**
|
||||
* Returns the last known position of the cover.
|
||||
* By default position is set to HACover::DefaultPosition
|
||||
*/
|
||||
inline int16_t getCurrentPosition() const
|
||||
{ return _currentPosition; }
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/cover/
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the cover.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the cover's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the cover state.
|
||||
* In this mode the cover state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same cover.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onCommand(HACOVER_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const CoverState state);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given position.
|
||||
*
|
||||
* @param position The position to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishPosition(const int16_t position);
|
||||
|
||||
/**
|
||||
* Parses the given command and executes the cover's callback with proper enum's property.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/// Features enabled for the cover.
|
||||
const uint8_t _features;
|
||||
|
||||
/// The current state of the cover. By default it's `HACover::StateUnknown`.
|
||||
CoverState _currentState;
|
||||
|
||||
/// The current position of the cover. By default it's `HACover::DefaultPosition`.
|
||||
int16_t _currentPosition;
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the cover (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The command callback that will be called when clicking the cover's button in the HA panel.
|
||||
HACOVER_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "HADeviceTracker.h"
|
||||
#ifndef EX_ARDUINOHA_DEVICE_TRACKER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HADeviceTracker::HADeviceTracker(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceTracker), uniqueId),
|
||||
_icon(nullptr),
|
||||
_sourceType(SourceTypeUnknown),
|
||||
_currentState(StateUnknown)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HADeviceTracker::setState(const TrackerState state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HADeviceTracker::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 8); // 8 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASourceTypeProperty),
|
||||
getSourceTypeProperty(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
}
|
||||
|
||||
void HADeviceTracker::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
bool HADeviceTracker::publishState(const TrackerState state)
|
||||
{
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (state) {
|
||||
case StateHome:
|
||||
stateStr = AHATOFSTR(HAHome);
|
||||
break;
|
||||
|
||||
case StateNotHome:
|
||||
stateStr = AHATOFSTR(HANotHome);
|
||||
break;
|
||||
|
||||
case StateNotAvailable:
|
||||
stateStr = AHATOFSTR(HAOffline);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), stateStr, true);
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HADeviceTracker::getSourceTypeProperty() const
|
||||
{
|
||||
switch (_sourceType) {
|
||||
case SourceTypeGPS:
|
||||
return AHATOFSTR(HAGPSType);
|
||||
|
||||
case SourceTypeRouter:
|
||||
return AHATOFSTR(HARouterType);
|
||||
|
||||
case SourceTypeBluetooth:
|
||||
return AHATOFSTR(HABluetoothType);
|
||||
|
||||
case SourceTypeBluetoothLE:
|
||||
return AHATOFSTR(HABluetoothLEType);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,114 @@
|
||||
#ifndef AHA_HADEVICETRACKER_H
|
||||
#define AHA_HADEVICETRACKER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_DEVICE_TRACKER
|
||||
|
||||
/**
|
||||
* HADeviceTracker allows to implement a custom device's tracker.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/device_tracker.mqtt/
|
||||
*/
|
||||
class HADeviceTracker : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/// Available source types of the tracker.
|
||||
enum SourceType {
|
||||
SourceTypeUnknown = 0,
|
||||
SourceTypeGPS,
|
||||
SourceTypeRouter,
|
||||
SourceTypeBluetooth,
|
||||
SourceTypeBluetoothLE
|
||||
};
|
||||
|
||||
/// Available states that can be reported to the HA panel.
|
||||
enum TrackerState {
|
||||
StateUnknown = 0,
|
||||
StateHome,
|
||||
StateNotHome,
|
||||
StateNotAvailable
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the tracker. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HADeviceTracker(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Changes the state of the tracker and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state The new state of the tracker.
|
||||
* @param force Forces to update the state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const TrackerState state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets the current state of the tracker without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state The new state of the tracker.
|
||||
*/
|
||||
inline void setCurrentState(const TrackerState state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns the last known state of the tracker.
|
||||
* If setState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline TrackerState getState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets icon of the tracker.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets the source type of the tracker.
|
||||
*
|
||||
* @param type The source type (gps, router, bluetooth, bluetooth LE).
|
||||
*/
|
||||
inline void setSourceType(const SourceType type)
|
||||
{ _sourceType = type; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(TrackerState state);
|
||||
|
||||
/**
|
||||
* Returns progmem string representing source type of the tracker.
|
||||
*/
|
||||
const __FlashStringHelper* getSourceTypeProperty() const;
|
||||
|
||||
/// The icon of the tracker. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The source type of the tracker. By default it's `HADeviceTracker::SourceTypeUnknown`.
|
||||
SourceType _sourceType;
|
||||
|
||||
/// The current state of the device's tracker. By default its `HADeviceTracker::StateUnknown`.
|
||||
TrackerState _currentState;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,209 @@
|
||||
#include "HADeviceTrigger.h"
|
||||
#ifndef EX_ARDUINOHA_DEVICE_TRIGGER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HADeviceTrigger::HADeviceTrigger(const char* type, const char* subtype) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceAutomation), nullptr),
|
||||
_type(type),
|
||||
_subtype(subtype),
|
||||
_isProgmemType(false),
|
||||
_isProgmemSubtype(false)
|
||||
{
|
||||
buildUniqueId();
|
||||
}
|
||||
|
||||
HADeviceTrigger::HADeviceTrigger(TriggerType type, const char* subtype) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceAutomation), nullptr),
|
||||
_type(determineProgmemType(type)),
|
||||
_subtype(subtype),
|
||||
_isProgmemType(true),
|
||||
_isProgmemSubtype(false)
|
||||
{
|
||||
buildUniqueId();
|
||||
}
|
||||
|
||||
HADeviceTrigger::HADeviceTrigger(const char* type, TriggerSubtype subtype) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceAutomation), nullptr),
|
||||
_type(type),
|
||||
_subtype(determineProgmemSubtype(subtype)),
|
||||
_isProgmemType(false),
|
||||
_isProgmemSubtype(true)
|
||||
{
|
||||
buildUniqueId();
|
||||
}
|
||||
|
||||
HADeviceTrigger::HADeviceTrigger(TriggerType type, TriggerSubtype subtype) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentDeviceAutomation), nullptr),
|
||||
_type(determineProgmemType(type)),
|
||||
_subtype(determineProgmemSubtype(subtype)),
|
||||
_isProgmemType(true),
|
||||
_isProgmemSubtype(true)
|
||||
{
|
||||
buildUniqueId();
|
||||
}
|
||||
|
||||
HADeviceTrigger::~HADeviceTrigger()
|
||||
{
|
||||
if (_uniqueId) {
|
||||
delete _uniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
bool HADeviceTrigger::trigger()
|
||||
{
|
||||
if (!_type || !_subtype) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HATopic), "");
|
||||
}
|
||||
|
||||
void HADeviceTrigger::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 5); // 5 - max properties nb
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAAutomationTypeProperty),
|
||||
AHATOFSTR(HATrigger),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HATypeProperty),
|
||||
_type,
|
||||
_isProgmemType
|
||||
? HASerializer::ProgmemPropertyValue
|
||||
: HASerializer::ConstCharPropertyValue
|
||||
);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASubtypeProperty),
|
||||
_subtype,
|
||||
_isProgmemSubtype
|
||||
? HASerializer::ProgmemPropertyValue
|
||||
: HASerializer::ConstCharPropertyValue
|
||||
);
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->topic(AHATOFSTR(HATopic));
|
||||
}
|
||||
|
||||
void HADeviceTrigger::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
uint16_t HADeviceTrigger::calculateIdSize() const
|
||||
{
|
||||
if (!_type || !_subtype) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint16_t typeSize = _isProgmemType ? strlen_P(_type) : strlen(_type);
|
||||
const uint16_t subtypeSize = _isProgmemSubtype
|
||||
? strlen_P(_subtype)
|
||||
: strlen(_subtype);
|
||||
|
||||
// plus underscore separator and null terminator
|
||||
return typeSize + subtypeSize + 2;
|
||||
}
|
||||
|
||||
void HADeviceTrigger::buildUniqueId()
|
||||
{
|
||||
const uint16_t idSize = calculateIdSize();
|
||||
if (idSize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* id = new char[idSize];
|
||||
|
||||
if (_isProgmemType) {
|
||||
strcpy_P(id, _type);
|
||||
} else {
|
||||
strcpy(id, _type);
|
||||
}
|
||||
|
||||
strcat_P(id, HASerializerUnderscore);
|
||||
|
||||
if (_isProgmemSubtype) {
|
||||
strcat_P(id, _subtype);
|
||||
} else {
|
||||
strcat(id, _subtype);
|
||||
}
|
||||
|
||||
_uniqueId = id;
|
||||
}
|
||||
|
||||
const char* HADeviceTrigger::determineProgmemType(TriggerType type) const
|
||||
{
|
||||
switch (type) {
|
||||
case ButtonShortPressType:
|
||||
return HAButtonShortPressType;
|
||||
|
||||
case ButtonShortReleaseType:
|
||||
return HAButtonShortReleaseType;
|
||||
|
||||
case ButtonLongPressType:
|
||||
return HAButtonLongPressType;
|
||||
|
||||
case ButtonLongReleaseType:
|
||||
return HAButtonLongReleaseType;
|
||||
|
||||
case ButtonDoublePressType:
|
||||
return HAButtonDoublePressType;
|
||||
|
||||
case ButtonTriplePressType:
|
||||
return HAButtonTriplePressType;
|
||||
|
||||
case ButtonQuadruplePressType:
|
||||
return HAButtonQuadruplePressType;
|
||||
|
||||
case ButtonQuintuplePressType:
|
||||
return HAButtonQuintuplePressType;
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const char* HADeviceTrigger::determineProgmemSubtype(
|
||||
TriggerSubtype subtype
|
||||
) const
|
||||
{
|
||||
switch (subtype) {
|
||||
case TurnOnSubtype:
|
||||
return HATurnOnSubtype;
|
||||
|
||||
case TurnOffSubtype:
|
||||
return HATurnOffSubtype;
|
||||
|
||||
case Button1Subtype:
|
||||
return HAButton1Subtype;
|
||||
|
||||
case Button2Subtype:
|
||||
return HAButton2Subtype;
|
||||
|
||||
case Button3Subtype:
|
||||
return HAButton3Subtype;
|
||||
|
||||
case Button4Subtype:
|
||||
return HAButton4Subtype;
|
||||
|
||||
case Button5Subtype:
|
||||
return HAButton5Subtype;
|
||||
|
||||
case Button6Subtype:
|
||||
return HAButton6Subtype;
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,176 @@
|
||||
#ifndef AHA_HADEVICETRIGGER_H
|
||||
#define AHA_HADEVICETRIGGER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_DEVICE_TRIGGER
|
||||
|
||||
/**
|
||||
* HADeviceTrigger allows to a custom trigger that can be used in the Home Assistant automation.
|
||||
* For example, it can be a wall switch that produces `press` and `long_press` actions.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/device_trigger.mqtt/
|
||||
*/
|
||||
class HADeviceTrigger : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/// Built-in types of the trigger.
|
||||
enum TriggerType {
|
||||
ButtonShortPressType = 1,
|
||||
ButtonShortReleaseType,
|
||||
ButtonLongPressType,
|
||||
ButtonLongReleaseType,
|
||||
ButtonDoublePressType,
|
||||
ButtonTriplePressType,
|
||||
ButtonQuadruplePressType,
|
||||
ButtonQuintuplePressType
|
||||
};
|
||||
|
||||
/// Built-in subtypes of the trigger.
|
||||
enum TriggerSubtype {
|
||||
TurnOnSubtype = 1,
|
||||
TurnOffSubtype,
|
||||
Button1Subtype,
|
||||
Button2Subtype,
|
||||
Button3Subtype,
|
||||
Button4Subtype,
|
||||
Button5Subtype,
|
||||
Button6Subtype
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the device trigger with a custom type and subtype.
|
||||
* For example, it can be `click` as the type and `btn0` as the subtype.
|
||||
* Please note that combination of the type and subtype needs to be unique in a scope of your device.
|
||||
*
|
||||
* @param type String representation of the type.
|
||||
* @param subtype String representation of the subtype.
|
||||
*/
|
||||
HADeviceTrigger(const char* type, const char* subtype);
|
||||
|
||||
/**
|
||||
* Creates the device trigger with a built-in type and a custom subtype.
|
||||
* For example, it can be `HADeviceTrigger::ButtonShortPressType` as the type and `btn0` as the subtype.
|
||||
* Please note that combination of the type and subtype needs to be unique in a scope of your device.
|
||||
*
|
||||
* @param type Built-in type of the trigger.
|
||||
* @param subtype String representation of the subtype.
|
||||
*/
|
||||
HADeviceTrigger(TriggerType type, const char* subtype);
|
||||
|
||||
/**
|
||||
* Creates the device trigger with a custom type and a built-in subtype.
|
||||
* For example, it can be `click` as the type and `HADeviceTrigger::Button1Subtype` as the subtype.
|
||||
* Please note that combination of the type and subtype needs to be unique in a scope of your device.
|
||||
*
|
||||
* @param type String representation of the subtype.
|
||||
* @param subtype Built-in subtype of the trigger.
|
||||
*/
|
||||
HADeviceTrigger(const char* type, TriggerSubtype subtype);
|
||||
|
||||
/**
|
||||
* Creates the device trigger with a built-in type and built-in subtype.
|
||||
* For example, it can be `HADeviceTrigger::ButtonShortPressType` as the type and `HADeviceTrigger::Button1Subtype` as the subtype.
|
||||
* Please note that combination of the type and subtype needs to be unique in a scope of your device.
|
||||
*
|
||||
* @param type Built-in type of the trigger.
|
||||
* @param subtype Built-in subtype of the trigger.
|
||||
*/
|
||||
HADeviceTrigger(TriggerType type, TriggerSubtype subtype);
|
||||
|
||||
/**
|
||||
* Frees memory allocated by the class.
|
||||
*/
|
||||
~HADeviceTrigger();
|
||||
|
||||
/**
|
||||
* Publishes MQTT message with the trigger event.
|
||||
* The published message is not retained.
|
||||
*
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool trigger();
|
||||
|
||||
/**
|
||||
* Returns the type of the trigger.
|
||||
* If the built-in type is used the returned value points to the flash memory.
|
||||
* Use `HADeviceTrigger::isProgmemType` to verify if the returned value is the progmem pointer.
|
||||
*
|
||||
* @returns Pointer to the type.
|
||||
*/
|
||||
inline const char* getType() const
|
||||
{ return _type; }
|
||||
|
||||
/**
|
||||
* Returns `true` if the built-in type was assigned to the trigger.
|
||||
*/
|
||||
inline bool isProgmemType() const
|
||||
{ return _isProgmemType; }
|
||||
|
||||
/**
|
||||
* Returns the subtype of the trigger.
|
||||
* If the built-in subtype is used the returned value points to the flash memory.
|
||||
* Use `HADeviceTrigger::isProgmemSubtype` to verify if the returned value is the progmem pointer.
|
||||
*
|
||||
* @returns Pointer to the subtype.
|
||||
*/
|
||||
inline const char* getSubtype() const
|
||||
{ return _subtype; }
|
||||
|
||||
/**
|
||||
* Returns `true` if the built-in subtype was assigned to the trigger.
|
||||
*/
|
||||
inline bool isProgmemSubtype() const
|
||||
{ return _isProgmemSubtype; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Calculates desired size of the unique ID based on the type and subtype that were passed to the constructor.
|
||||
*/
|
||||
uint16_t calculateIdSize() const;
|
||||
|
||||
/**
|
||||
* Builds the unique ID of the device's type based on the type and subtype that were passed to the constructor.
|
||||
*/
|
||||
void buildUniqueId();
|
||||
|
||||
/**
|
||||
* Returns pointer to the flash memory that represents the given type.
|
||||
*
|
||||
* @param subtype Built-in type enum's value.
|
||||
* @returns Pointer to the flash memory if the given type is supported.
|
||||
* For the unsupported type the nullptr is returned.
|
||||
*/
|
||||
const char* determineProgmemType(TriggerType type) const;
|
||||
|
||||
/**
|
||||
* Returns pointer to the flash memory that represents the given subtype.
|
||||
*
|
||||
* @param subtype Built-in subtype enum's value.
|
||||
* @returns Pointer to the flash memory if the given subtype is supported.
|
||||
* For the unsupported subtype the nullptr is returned.
|
||||
*/
|
||||
const char* determineProgmemSubtype(TriggerSubtype subtype) const;
|
||||
|
||||
private:
|
||||
/// Pointer to the trigger's type. It can be pointer to the flash memory.
|
||||
const char* _type;
|
||||
|
||||
/// Pointer to the trigger's subtype. It can be pointer to the flash memory.
|
||||
const char* _subtype;
|
||||
|
||||
/// Specifies whether the type points to the flash memory.
|
||||
bool _isProgmemType;
|
||||
|
||||
/// Specifies whether the subtype points to the flash memory.
|
||||
bool _isProgmemSubtype;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,194 @@
|
||||
#include "HAFan.h"
|
||||
#ifndef EX_ARDUINOHA_FAN
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HAFan::HAFan(const char* uniqueId, const uint8_t features) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentFan), uniqueId),
|
||||
_features(features),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_speedRangeMax(),
|
||||
_speedRangeMin(),
|
||||
_currentState(false),
|
||||
_currentSpeed(0),
|
||||
_stateCallback(nullptr),
|
||||
_speedCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HAFan::setState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAFan::setSpeed(const uint16_t speed, const bool force)
|
||||
{
|
||||
if (!force && speed == _currentSpeed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishSpeed(speed)) {
|
||||
_currentSpeed = speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HAFan::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 14); // 14 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_features & SpeedsFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAPercentageStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HAPercentageCommandTopic));
|
||||
|
||||
if (_speedRangeMax.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASpeedRangeMaxProperty),
|
||||
&_speedRangeMax,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_speedRangeMin.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASpeedRangeMinProperty),
|
||||
&_speedRangeMin,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAFan::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
publishSpeed(_currentSpeed);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
|
||||
if (_features & SpeedsFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAPercentageCommandTopic));
|
||||
}
|
||||
}
|
||||
|
||||
void HAFan::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleStateCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAPercentageCommandTopic)
|
||||
)) {
|
||||
handleSpeedCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HAFan::publishState(const bool state)
|
||||
{
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAFan::publishSpeed(const uint16_t speed)
|
||||
{
|
||||
if (!(_features & SpeedsFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[5 + 1] = {0}; // uint16_t digits with null terminator
|
||||
HANumeric(speed, 0).toStr(str);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAPercentageStateTopic), str, true);
|
||||
}
|
||||
|
||||
void HAFan::handleStateCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
if (!_stateCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_stateCallback(state, this);
|
||||
}
|
||||
|
||||
void HAFan::handleSpeedCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_speedCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HANumeric& number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isUInt16()) {
|
||||
_speedCallback(number.toUInt16(), this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,245 @@
|
||||
#ifndef AHA_HAFAN_H
|
||||
#define AHA_HAFAN_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_FAN
|
||||
|
||||
#define HAFAN_STATE_CALLBACK(name) void (*name)(bool state, HAFan* sender)
|
||||
#define HAFAN_SPEED_CALLBACK(name) void (*name)(uint16_t speed, HAFan* sender)
|
||||
|
||||
/**
|
||||
* HAFan allows adding a controllable fan in the Home Assistant panel.
|
||||
* The library supports only the state and speed of the fan.
|
||||
* If you want more features please open a new GitHub issue.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/fan.mqtt/
|
||||
*/
|
||||
class HAFan : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
SpeedsFeature = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the fan. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the fan.
|
||||
*/
|
||||
HAFan(const char* uniqueId, const uint8_t features = DefaultFeatures);
|
||||
|
||||
/**
|
||||
* Changes state of the fan and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the fan.
|
||||
* @param force Forces to update state without comparing it to previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the speed of the fan and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param speed The new speed of the fan. It should be in range of min and max value.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setSpeed(const uint16_t speed, const bool force = false);
|
||||
|
||||
/**
|
||||
* Alias for `setState(true)`.
|
||||
*/
|
||||
inline bool turnOn()
|
||||
{ return setState(true); }
|
||||
|
||||
/**
|
||||
* Alias for `setState(false)`.
|
||||
*/
|
||||
inline bool turnOff()
|
||||
{ return setState(false); }
|
||||
|
||||
/**
|
||||
* Sets current state of the fan without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state New state of the fan.
|
||||
*/
|
||||
inline void setCurrentState(const bool state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the fan.
|
||||
* By default it's `false`.
|
||||
*/
|
||||
inline bool getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets the current speed of the fan without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the speed before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param speed The new speed of the fan. It should be in range of min and max value.
|
||||
*/
|
||||
inline void setCurrentSpeed(const uint16_t speed)
|
||||
{ _currentSpeed = speed; }
|
||||
|
||||
/**
|
||||
* Returns the last known speed of the fan.
|
||||
* By default speed is set to `0`.
|
||||
*/
|
||||
inline uint16_t getCurrentSpeed() const
|
||||
{ return _currentSpeed; }
|
||||
|
||||
/**
|
||||
* Sets icon of the fan.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the fan's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the fan state.
|
||||
* In this mode the fan state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Sets the maximum of numeric output range (representing 100%).
|
||||
* The number of speeds within the speed_range / 100 will determine the percentage step.
|
||||
* By default the maximum range is `100`.
|
||||
*
|
||||
* @param max The maximum of numeric output range.
|
||||
*/
|
||||
inline void setSpeedRangeMax(const uint16_t max)
|
||||
{ _speedRangeMax.setBaseValue(max); }
|
||||
|
||||
/**
|
||||
* Sets the minimum of numeric output range (off is not included, so speed_range_min - 1 represents 0 %).
|
||||
* The number of speeds within the speed_range / 100 will determine the percentage step.
|
||||
* By default the minimum range is `1`.
|
||||
*
|
||||
* @param min The minimum of numeric output range.
|
||||
*/
|
||||
inline void setSpeedRangeMin(const uint16_t min)
|
||||
{ _speedRangeMin.setBaseValue(min); }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the state command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same fan.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the state must be reported back to HA using the HAFan::setState method.
|
||||
*/
|
||||
inline void onStateCommand(HAFAN_STATE_CALLBACK(callback))
|
||||
{ _stateCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the speed command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same fan.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the speed must be reported back to HA using the HAFan::setSpeed method.
|
||||
*/
|
||||
inline void onSpeedCommand(HAFAN_SPEED_CALLBACK(callback))
|
||||
{ _speedCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const bool state);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given speed.
|
||||
*
|
||||
* @param speed The speed to publish. It should be in range of min and max value.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishSpeed(const uint16_t speed);
|
||||
|
||||
/**
|
||||
* Parses the given state command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleStateCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given speed command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleSpeedCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/// Features enabled for the fan.
|
||||
const uint8_t _features;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the fan (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The maximum of numeric output range.
|
||||
HANumeric _speedRangeMax;
|
||||
|
||||
/// The minimum of numeric output range.
|
||||
HANumeric _speedRangeMin;
|
||||
|
||||
/// The current state of the fan. By default it's `false`.
|
||||
bool _currentState;
|
||||
|
||||
/// The current speed of the fan. By default it's `0`.
|
||||
uint16_t _currentSpeed;
|
||||
|
||||
/// The callback that will be called when the state command is received from the HA.
|
||||
HAFAN_STATE_CALLBACK(_stateCallback);
|
||||
|
||||
/// The callback that will be called when the speed command is received from the HA.
|
||||
HAFAN_SPEED_CALLBACK(_speedCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,756 @@
|
||||
#include "HAHVAC.h"
|
||||
#ifndef EX_ARDUINOHA_HVAC
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HAUtils.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
const uint8_t HAHVAC::DefaultFanModes = AutoFanMode | LowFanMode | MediumFanMode | HighFanMode;
|
||||
const uint8_t HAHVAC::DefaultSwingModes = OnSwingMode | OffSwingMode;
|
||||
const uint8_t HAHVAC::DefaultModes = AutoMode | OffMode | CoolMode | HeatMode | DryMode | FanOnlyMode;
|
||||
|
||||
HAHVAC::HAHVAC(
|
||||
const char* uniqueId,
|
||||
const uint16_t features,
|
||||
const NumberPrecision precision
|
||||
) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentClimate), uniqueId),
|
||||
_features(features),
|
||||
_precision(precision),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_currentTemperature(),
|
||||
_action(UnknownAction),
|
||||
_temperatureUnit(DefaultUnit),
|
||||
_minTemp(),
|
||||
_maxTemp(),
|
||||
_tempStep(),
|
||||
_auxCallback(nullptr),
|
||||
_auxState(false),
|
||||
_powerCallback(nullptr),
|
||||
_fanMode(UnknownFanMode),
|
||||
_fanModes(DefaultFanModes),
|
||||
_fanModesSerializer(nullptr),
|
||||
_fanModeCallback(nullptr),
|
||||
_swingMode(UnknownSwingMode),
|
||||
_swingModes(DefaultSwingModes),
|
||||
_swingModesSerializer(nullptr),
|
||||
_swingModeCallback(nullptr),
|
||||
_mode(UnknownMode),
|
||||
_modes(DefaultModes),
|
||||
_modesSerializer(nullptr),
|
||||
_modeCallback(nullptr),
|
||||
_targetTemperature(),
|
||||
_targetTemperatureCallback(nullptr)
|
||||
{
|
||||
if (_features & FanFeature) {
|
||||
_fanModesSerializer = new HASerializerArray(4);
|
||||
}
|
||||
|
||||
if (_features & SwingFeature) {
|
||||
_swingModesSerializer = new HASerializerArray(2);
|
||||
}
|
||||
|
||||
if (_features & ModesFeature) {
|
||||
_modesSerializer = new HASerializerArray(6);
|
||||
}
|
||||
}
|
||||
|
||||
HAHVAC::~HAHVAC()
|
||||
{
|
||||
if (_fanModesSerializer) {
|
||||
delete _fanModesSerializer;
|
||||
}
|
||||
|
||||
if (_swingModesSerializer) {
|
||||
delete _swingModesSerializer;
|
||||
}
|
||||
|
||||
if (_modesSerializer) {
|
||||
delete _modesSerializer;
|
||||
}
|
||||
}
|
||||
|
||||
bool HAHVAC::setCurrentTemperature(const HANumeric& temperature, const bool force)
|
||||
{
|
||||
if (temperature.getPrecision() != _precision) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!force && temperature == _currentTemperature) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishCurrentTemperature(temperature)) {
|
||||
_currentTemperature = temperature;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setAction(const Action action, const bool force)
|
||||
{
|
||||
if (!force && action == _action) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishAction(action)) {
|
||||
_action = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setAuxState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _auxState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishAuxState(state)) {
|
||||
_auxState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setFanMode(const FanMode mode, const bool force)
|
||||
{
|
||||
if (!force && mode == _fanMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishFanMode(mode)) {
|
||||
_fanMode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setSwingMode(const SwingMode mode, const bool force)
|
||||
{
|
||||
if (!force && mode == _swingMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishSwingMode(mode)) {
|
||||
_swingMode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setMode(const Mode mode, const bool force)
|
||||
{
|
||||
if (!force && mode == _mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishMode(mode)) {
|
||||
_mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HAHVAC::setTargetTemperature(const HANumeric& temperature, const bool force)
|
||||
{
|
||||
if (temperature.getPrecision() != _precision) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!force && temperature == _targetTemperature) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishTargetTemperature(temperature)) {
|
||||
_targetTemperature = temperature;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HAHVAC::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 28); // 28 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_features & ActionFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAActionTopic));
|
||||
}
|
||||
|
||||
if (_features & AuxHeatingFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAAuxCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HAAuxStateTopic));
|
||||
}
|
||||
|
||||
if (_features & PowerFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAPowerCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & FanFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAFanModeCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HAFanModeStateTopic));
|
||||
|
||||
if (_fanModes != DefaultFanModes) {
|
||||
_fanModesSerializer->clear();
|
||||
|
||||
if (_fanModes & AutoFanMode) {
|
||||
_fanModesSerializer->add(HAFanModeAuto);
|
||||
}
|
||||
|
||||
if (_fanModes & LowFanMode) {
|
||||
_fanModesSerializer->add(HAFanModeLow);
|
||||
}
|
||||
|
||||
if (_fanModes & MediumFanMode) {
|
||||
_fanModesSerializer->add(HAFanModeMedium);
|
||||
}
|
||||
|
||||
if (_fanModes & HighFanMode) {
|
||||
_fanModesSerializer->add(HAFanModeHigh);
|
||||
}
|
||||
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAFanModesProperty),
|
||||
_fanModesSerializer,
|
||||
HASerializer::ArrayPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & SwingFeature) {
|
||||
_serializer->topic(AHATOFSTR(HASwingModeCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HASwingModeStateTopic));
|
||||
|
||||
if (_swingModes != DefaultSwingModes) {
|
||||
_swingModesSerializer->clear();
|
||||
|
||||
if (_swingModes & OnSwingMode) {
|
||||
_swingModesSerializer->add(HASwingModeOn);
|
||||
}
|
||||
|
||||
if (_swingModes & OffSwingMode) {
|
||||
_swingModesSerializer->add(HASwingModeOff);
|
||||
}
|
||||
|
||||
_serializer->set(
|
||||
AHATOFSTR(HASwingModesProperty),
|
||||
_swingModesSerializer,
|
||||
HASerializer::ArrayPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & ModesFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAModeCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HAModeStateTopic));
|
||||
|
||||
if (_modes != DefaultModes) {
|
||||
_modesSerializer->clear();
|
||||
|
||||
if (_modes & AutoMode) {
|
||||
_modesSerializer->add(HAModeAuto);
|
||||
}
|
||||
|
||||
if (_modes & OffMode) {
|
||||
_modesSerializer->add(HAModeOff);
|
||||
}
|
||||
|
||||
if (_modes & CoolMode) {
|
||||
_modesSerializer->add(HAModeCool);
|
||||
}
|
||||
|
||||
if (_modes & HeatMode) {
|
||||
_modesSerializer->add(HAModeHeat);
|
||||
}
|
||||
|
||||
if (_modes & DryMode) {
|
||||
_modesSerializer->add(HAModeDry);
|
||||
}
|
||||
|
||||
if (_modes & FanOnlyMode) {
|
||||
_modesSerializer->add(HAModeFanOnly);
|
||||
}
|
||||
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAModesProperty),
|
||||
_modesSerializer,
|
||||
HASerializer::ArrayPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & TargetTemperatureFeature) {
|
||||
_serializer->topic(AHATOFSTR(HATemperatureCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HATemperatureStateTopic));
|
||||
_serializer->set(
|
||||
AHATOFSTR(HATemperatureCommandTemplateProperty),
|
||||
getCommandWithFloatTemplate(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
}
|
||||
|
||||
if (_temperatureUnit != DefaultUnit) {
|
||||
const __FlashStringHelper *unitStr = _temperatureUnit == CelsiusUnit
|
||||
? AHATOFSTR(HATemperatureUnitC)
|
||||
: AHATOFSTR(HATemperatureUnitF);
|
||||
|
||||
_serializer->set(
|
||||
AHATOFSTR(HATemperatureUnitProperty),
|
||||
unitStr,
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
}
|
||||
|
||||
if (_minTemp.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMinTempProperty),
|
||||
&_minTemp,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_maxTemp.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMaxTempProperty),
|
||||
&_maxTemp,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_tempStep.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HATempStepProperty),
|
||||
&_tempStep,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->topic(AHATOFSTR(HACurrentTemperatureTopic));
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
}
|
||||
|
||||
void HAHVAC::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishCurrentTemperature(_currentTemperature);
|
||||
publishAction(_action);
|
||||
publishAuxState(_auxState);
|
||||
publishFanMode(_fanMode);
|
||||
publishSwingMode(_swingMode);
|
||||
publishMode(_mode);
|
||||
publishTargetTemperature(_targetTemperature);
|
||||
}
|
||||
|
||||
if (_features & AuxHeatingFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAAuxCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & PowerFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAPowerCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & FanFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAFanModeCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & SwingFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HASwingModeCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & ModesFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAModeCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & TargetTemperatureFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HATemperatureCommandTopic));
|
||||
}
|
||||
}
|
||||
|
||||
void HAHVAC::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAAuxCommandTopic)
|
||||
)) {
|
||||
handleAuxStateCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAPowerCommandTopic)
|
||||
)) {
|
||||
handlePowerCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAFanModeCommandTopic)
|
||||
)) {
|
||||
handleFanModeCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HASwingModeCommandTopic)
|
||||
)) {
|
||||
handleSwingModeCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAModeCommandTopic)
|
||||
)) {
|
||||
handleModeCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HATemperatureCommandTopic)
|
||||
)) {
|
||||
handleTargetTemperatureCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HAHVAC::publishCurrentTemperature(const HANumeric& temperature)
|
||||
{
|
||||
if (!temperature.isSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t size = temperature.calculateSize();
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[size + 1]; // with null terminator
|
||||
str[size] = 0;
|
||||
temperature.toStr(str);
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HACurrentTemperatureTopic),
|
||||
str,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishAction(const Action action)
|
||||
{
|
||||
if (action == UnknownAction || !(_features & ActionFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (action) {
|
||||
case OffAction:
|
||||
stateStr = AHATOFSTR(HAActionOff);
|
||||
break;
|
||||
|
||||
case HeatingAction:
|
||||
stateStr = AHATOFSTR(HAActionHeating);
|
||||
break;
|
||||
|
||||
case CoolingAction:
|
||||
stateStr = AHATOFSTR(HAActionCooling);
|
||||
break;
|
||||
|
||||
case DryingAction:
|
||||
stateStr = AHATOFSTR(HAActionDrying);
|
||||
break;
|
||||
|
||||
case IdleAction:
|
||||
stateStr = AHATOFSTR(HAActionIdle);
|
||||
break;
|
||||
|
||||
case FanAction:
|
||||
stateStr = AHATOFSTR(HAActionFan);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAActionTopic),
|
||||
stateStr,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishAuxState(const bool state)
|
||||
{
|
||||
if (!(_features & AuxHeatingFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAAuxStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishFanMode(const FanMode mode)
|
||||
{
|
||||
if (mode == UnknownFanMode || !(_features & FanFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (mode) {
|
||||
case AutoFanMode:
|
||||
stateStr = AHATOFSTR(HAFanModeAuto);
|
||||
break;
|
||||
|
||||
case LowFanMode:
|
||||
stateStr = AHATOFSTR(HAFanModeLow);
|
||||
break;
|
||||
|
||||
case MediumFanMode:
|
||||
stateStr = AHATOFSTR(HAFanModeMedium);
|
||||
break;
|
||||
|
||||
case HighFanMode:
|
||||
stateStr = AHATOFSTR(HAFanModeHigh);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAFanModeStateTopic),
|
||||
stateStr,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishSwingMode(const SwingMode mode)
|
||||
{
|
||||
if (mode == UnknownSwingMode || !(_features & SwingFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (mode) {
|
||||
case OnSwingMode:
|
||||
stateStr = AHATOFSTR(HASwingModeOn);
|
||||
break;
|
||||
|
||||
case OffSwingMode:
|
||||
stateStr = AHATOFSTR(HASwingModeOff);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HASwingModeStateTopic),
|
||||
stateStr,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishMode(const Mode mode)
|
||||
{
|
||||
if (mode == UnknownMode || !(_features & ModesFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const __FlashStringHelper *stateStr = nullptr;
|
||||
switch (mode) {
|
||||
case AutoMode:
|
||||
stateStr = AHATOFSTR(HAModeAuto);
|
||||
break;
|
||||
|
||||
case OffMode:
|
||||
stateStr = AHATOFSTR(HAModeOff);
|
||||
break;
|
||||
|
||||
case CoolMode:
|
||||
stateStr = AHATOFSTR(HAModeCool);
|
||||
break;
|
||||
|
||||
case HeatMode:
|
||||
stateStr = AHATOFSTR(HAModeHeat);
|
||||
break;
|
||||
|
||||
case DryMode:
|
||||
stateStr = AHATOFSTR(HAModeDry);
|
||||
break;
|
||||
|
||||
case FanOnlyMode:
|
||||
stateStr = AHATOFSTR(HAModeFanOnly);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAModeStateTopic),
|
||||
stateStr,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HAHVAC::publishTargetTemperature(const HANumeric& temperature)
|
||||
{
|
||||
if (!temperature.isSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t size = temperature.calculateSize();
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[size + 1]; // with null terminator
|
||||
str[size] = 0;
|
||||
temperature.toStr(str);
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HATemperatureStateTopic),
|
||||
str,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
void HAHVAC::handleAuxStateCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
if (!_auxCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_auxCallback(state, this);
|
||||
}
|
||||
|
||||
void HAHVAC::handlePowerCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
if (!_powerCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_powerCallback(state, this);
|
||||
}
|
||||
|
||||
void HAHVAC::handleFanModeCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_fanModeCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HAFanModeAuto, length) == 0) {
|
||||
_fanModeCallback(AutoFanMode, this);
|
||||
} else if (memcmp_P(cmd, HAFanModeLow, length) == 0) {
|
||||
_fanModeCallback(LowFanMode, this);
|
||||
} else if (memcmp_P(cmd, HAFanModeMedium, length) == 0) {
|
||||
_fanModeCallback(MediumFanMode, this);
|
||||
} else if (memcmp_P(cmd, HAFanModeHigh, length) == 0) {
|
||||
_fanModeCallback(HighFanMode, this);
|
||||
}
|
||||
}
|
||||
|
||||
void HAHVAC::handleSwingModeCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_swingModeCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HASwingModeOn, length) == 0) {
|
||||
_swingModeCallback(OnSwingMode, this);
|
||||
} else if (memcmp_P(cmd, HASwingModeOff, length) == 0) {
|
||||
_swingModeCallback(OffSwingMode, this);
|
||||
}
|
||||
}
|
||||
|
||||
void HAHVAC::handleModeCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_modeCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HAModeAuto, length) == 0) {
|
||||
_modeCallback(AutoMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeOff, length) == 0) {
|
||||
_modeCallback(OffMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeCool, length) == 0) {
|
||||
_modeCallback(CoolMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeHeat, length) == 0) {
|
||||
_modeCallback(HeatMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeDry, length) == 0) {
|
||||
_modeCallback(DryMode, this);
|
||||
} else if (memcmp_P(cmd, HAModeFanOnly, length) == 0) {
|
||||
_modeCallback(FanOnlyMode, this);
|
||||
}
|
||||
}
|
||||
|
||||
void HAHVAC::handleTargetTemperatureCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_targetTemperatureCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
HANumeric number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isSet()) {
|
||||
number.setPrecision(_precision);
|
||||
_targetTemperatureCallback(number, this);
|
||||
}
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HAHVAC::getCommandWithFloatTemplate()
|
||||
{
|
||||
switch (_precision) {
|
||||
case PrecisionP1:
|
||||
return AHATOFSTR(HAValueTemplateFloatP1);
|
||||
|
||||
case PrecisionP2:
|
||||
return AHATOFSTR(HAValueTemplateFloatP2);
|
||||
|
||||
case PrecisionP3:
|
||||
return AHATOFSTR(HAValueTemplateFloatP3);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,704 @@
|
||||
#ifndef AHA_HAHVAC_H
|
||||
#define AHA_HAHVAC_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_HVAC
|
||||
|
||||
#define _SET_CURRENT_TEMPERATURE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline bool setCurrentTemperature(const type temperature, const bool force = false) \
|
||||
{ return setCurrentTemperature(HANumeric(temperature, _precision), force); }
|
||||
|
||||
#define _SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline void setCurrentCurrentTemperature(const type temperature) \
|
||||
{ setCurrentCurrentTemperature(HANumeric(temperature, _precision)); }
|
||||
|
||||
#define _SET_TARGET_TEMPERATURE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline bool setTargetTemperature(const type temperature, const bool force = false) \
|
||||
{ return setTargetTemperature(HANumeric(temperature, _precision), force); }
|
||||
|
||||
#define _SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline void setCurrentTargetTemperature(const type temperature) \
|
||||
{ setCurrentTargetTemperature(HANumeric(temperature, _precision)); }
|
||||
|
||||
#define HAHVAC_CALLBACK_BOOL(name) void (*name)(bool state, HAHVAC* sender)
|
||||
#define HAHVAC_CALLBACK_TARGET_TEMP(name) void (*name)(HANumeric temperature, HAHVAC* sender)
|
||||
#define HAHVAC_CALLBACK_FAN_MODE(name) void (*name)(FanMode mode, HAHVAC* sender)
|
||||
#define HAHVAC_CALLBACK_SWING_MODE(name) void (*name)(SwingMode mode, HAHVAC* sender)
|
||||
#define HAHVAC_CALLBACK_MODE(name) void (*name)(Mode mode, HAHVAC* sender)
|
||||
|
||||
class HASerializerArray;
|
||||
|
||||
/**
|
||||
* HAHVAC lets you control your HVAC devices.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/climate.mqtt/
|
||||
*/
|
||||
class HAHVAC : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
static const uint8_t DefaultFanModes;
|
||||
static const uint8_t DefaultSwingModes;
|
||||
static const uint8_t DefaultModes;
|
||||
|
||||
/// The list of features available in the HVAC. They're used in the constructor.
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
ActionFeature = 1,
|
||||
AuxHeatingFeature = 2,
|
||||
PowerFeature = 4,
|
||||
FanFeature = 8,
|
||||
SwingFeature = 16,
|
||||
ModesFeature = 32,
|
||||
TargetTemperatureFeature = 64
|
||||
};
|
||||
|
||||
/// The list of available actions of the HVAC.
|
||||
enum Action {
|
||||
UnknownAction = 0,
|
||||
OffAction,
|
||||
HeatingAction,
|
||||
CoolingAction,
|
||||
DryingAction,
|
||||
IdleAction,
|
||||
FanAction
|
||||
};
|
||||
|
||||
/// The list of available fan modes.
|
||||
enum FanMode {
|
||||
UnknownFanMode = 0,
|
||||
AutoFanMode = 1,
|
||||
LowFanMode = 2,
|
||||
MediumFanMode = 4,
|
||||
HighFanMode = 8
|
||||
};
|
||||
|
||||
/// The list of available swing modes.
|
||||
enum SwingMode {
|
||||
UnknownSwingMode = 0,
|
||||
OnSwingMode = 1,
|
||||
OffSwingMode = 2
|
||||
};
|
||||
|
||||
/// The list of available HVAC's modes.
|
||||
enum Mode {
|
||||
UnknownMode = 0,
|
||||
AutoMode = 1,
|
||||
OffMode = 2,
|
||||
CoolMode = 4,
|
||||
HeatMode = 8,
|
||||
DryMode = 16,
|
||||
FanOnlyMode = 32
|
||||
};
|
||||
|
||||
/// Temperature units available in the HVAC.
|
||||
enum TemperatureUnit {
|
||||
DefaultUnit = 1,
|
||||
CelsiusUnit,
|
||||
FahrenheitUnit
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the HVAC. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the HVAC.
|
||||
* @param precision The precision of temperatures reported by the HVAC.
|
||||
*/
|
||||
HAHVAC(
|
||||
const char* uniqueId,
|
||||
const uint16_t features = DefaultFeatures,
|
||||
const NumberPrecision precision = PrecisionP1
|
||||
);
|
||||
|
||||
/**
|
||||
* Frees memory allocated for the arrays serialization.
|
||||
*/
|
||||
~HAHVAC();
|
||||
|
||||
/**
|
||||
* Changes current temperature of the HVAC and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param temperature New current temperature.
|
||||
* @param force Forces to update the temperature without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setCurrentTemperature(const HANumeric& temperature, const bool force = false);
|
||||
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(float)
|
||||
|
||||
#ifdef ARDUINOHA_INT_OVERLOAD
|
||||
_SET_CURRENT_TEMPERATURE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Changes action of the HVAC and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param action New action.
|
||||
* @param force Forces to update the action without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setAction(const Action action, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes state of the aux heating and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state The new state.
|
||||
* @param force Forces to update the state without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setAuxState(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes mode of the fan of the HVAC and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param mode New fan's mode.
|
||||
* @param force Forces to update the mode without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setFanMode(const FanMode mode, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes swing mode of the HVAC and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param mode New swing mode.
|
||||
* @param force Forces to update the mode without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setSwingMode(const SwingMode mode, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes mode of the HVAC and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param mode New HVAC's mode.
|
||||
* @param force Forces to update the mode without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setMode(const Mode mode, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes target temperature of the HVAC and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param temperature Target temperature to set.
|
||||
* @param force Forces to update the mode without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setTargetTemperature(const HANumeric& temperature, const bool force = false);
|
||||
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(int8_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(int16_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(int32_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(uint8_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(uint16_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(uint32_t)
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(float)
|
||||
|
||||
#ifdef ARDUINOHA_INT_OVERLOAD
|
||||
_SET_TARGET_TEMPERATURE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets current temperature of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change temperature before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param temperature New current temperature.
|
||||
*/
|
||||
inline void setCurrentCurrentTemperature(const HANumeric& temperature)
|
||||
{ if (temperature.getPrecision() == _precision) { _currentTemperature = temperature; } }
|
||||
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(float)
|
||||
|
||||
#ifdef ARDUINOHA_INT_OVERLOAD
|
||||
_SET_CURRENT_CURRENT_TEMPERATURE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns last known current temperature of the HVAC.
|
||||
* If setCurrentTemperature method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline const HANumeric& getCurrentTemperature() const
|
||||
{ return _currentTemperature; }
|
||||
|
||||
/**
|
||||
* Sets action of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the action before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param action New action.
|
||||
*/
|
||||
inline void setCurrentAction(const Action action)
|
||||
{ _action = action; }
|
||||
|
||||
/**
|
||||
* Returns last known action of the HVAC.
|
||||
* If setAction method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline Action getCurrentAction() const
|
||||
{ return _action; }
|
||||
|
||||
/**
|
||||
* Sets aux heating state without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state The new state.
|
||||
*/
|
||||
inline void setCurrentAuxState(const bool state)
|
||||
{ _auxState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the aux heating.
|
||||
* If setAuxState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline bool getCurrentAuxState() const
|
||||
{ return _auxState; }
|
||||
|
||||
/**
|
||||
* Sets fan's mode of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the mode before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param mode New fan's mode.
|
||||
*/
|
||||
inline void setCurrentFanMode(const FanMode mode)
|
||||
{ _fanMode = mode; }
|
||||
|
||||
/**
|
||||
* Returns last known fan's mode of the HVAC.
|
||||
* If setFanMode method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline FanMode getCurrentFanMode() const
|
||||
{ return _fanMode; }
|
||||
|
||||
/**
|
||||
* Sets available fan modes.
|
||||
*
|
||||
* @param modes The modes to set (for example: `HAHVAC::AutoFanMode | HAHVAC::HighFanMode`).
|
||||
*/
|
||||
inline void setFanModes(const uint8_t modes)
|
||||
{ _fanModes = modes; }
|
||||
|
||||
/**
|
||||
* Sets swing mode of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the mode before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param mode New swing mode.
|
||||
*/
|
||||
inline void setCurrentSwingMode(const SwingMode mode)
|
||||
{ _swingMode = mode; }
|
||||
|
||||
/**
|
||||
* Returns last known swing mode of the HVAC.
|
||||
* If setSwingMode method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline SwingMode getCurrentSwingMode() const
|
||||
{ return _swingMode; }
|
||||
|
||||
/**
|
||||
* Sets available swing modes.
|
||||
*
|
||||
* @param modes The modes to set (for example: `HAHVAC::OnSwingMode`).
|
||||
*/
|
||||
inline void setSwingModes(const uint8_t modes)
|
||||
{ _swingModes = modes; }
|
||||
|
||||
/**
|
||||
* Sets mode of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the mode before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param mode New HVAC's mode.
|
||||
*/
|
||||
inline void setCurrentMode(const Mode mode)
|
||||
{ _mode = mode; }
|
||||
|
||||
/**
|
||||
* Returns last known mode of the HVAC.
|
||||
* If setMode method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline Mode getCurrentMode() const
|
||||
{ return _mode; }
|
||||
|
||||
/**
|
||||
* Sets available HVAC's modes.
|
||||
*
|
||||
* @param modes The modes to set (for example: `HAHVAC::CoolMode | HAHVAC::HeatMode`).
|
||||
*/
|
||||
inline void setModes(const uint8_t modes)
|
||||
{ _modes = modes; }
|
||||
|
||||
/**
|
||||
* Sets target temperature of the HVAC without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the target before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param temperature Target temperature to set.
|
||||
*/
|
||||
inline void setCurrentTargetTemperature(const HANumeric& temperature)
|
||||
{ if (temperature.getPrecision() == _precision) { _targetTemperature = temperature; } }
|
||||
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(float)
|
||||
|
||||
#ifdef ARDUINOHA_INT_OVERLOAD
|
||||
_SET_CURRENT_TARGET_TEMPERATURE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns last known target temperature of the HVAC.
|
||||
* If setTargetTemperature method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline const HANumeric& getCurrentTargetTemperature() const
|
||||
{ return _targetTemperature; }
|
||||
|
||||
/**
|
||||
* Sets icon of the HVAC.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the HVAC's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Changes the temperature unit.
|
||||
*
|
||||
* @param unit See the TemperatureUnit enum above.
|
||||
*/
|
||||
inline void setTemperatureUnit(TemperatureUnit unit)
|
||||
{ _temperatureUnit = unit; }
|
||||
|
||||
/**
|
||||
* Sets the minimum temperature that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param min The minimum value.
|
||||
*/
|
||||
inline void setMinTemp(const float min)
|
||||
{ _minTemp = HANumeric(min, _precision); }
|
||||
|
||||
/**
|
||||
* Sets the maximum temperature that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param min The maximum value.
|
||||
*/
|
||||
inline void setMaxTemp(const float max)
|
||||
{ _maxTemp = HANumeric(max, _precision); }
|
||||
|
||||
/**
|
||||
* Sets the step of the temperature that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param step The setp value. By default it's `1`.
|
||||
*/
|
||||
inline void setTempStep(const float step)
|
||||
{ _tempStep = HANumeric(step, _precision); }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the aux state command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The aux state must be reported back to HA using the HAHVAC::setAuxState method.
|
||||
*/
|
||||
inline void onAuxStateCommand(HAHVAC_CALLBACK_BOOL(callback))
|
||||
{ _auxCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the power command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onPowerCommand(HAHVAC_CALLBACK_BOOL(callback))
|
||||
{ _powerCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the fan mode command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The fan mode must be reported back to HA using the HAHVAC::setFanMode method.
|
||||
*/
|
||||
inline void onFanModeCommand(HAHVAC_CALLBACK_FAN_MODE(callback))
|
||||
{ _fanModeCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the swing mode command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The swing mode must be reported back to HA using the HAHVAC::setSwingMode method.
|
||||
*/
|
||||
inline void onSwingModeCommand(HAHVAC_CALLBACK_SWING_MODE(callback))
|
||||
{ _swingModeCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the HVAC mode command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The mode must be reported back to HA using the HAHVAC::setMode method.
|
||||
*/
|
||||
inline void onModeCommand(HAHVAC_CALLBACK_MODE(callback))
|
||||
{ _modeCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the target temperature is set via HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same HVAC.
|
||||
*
|
||||
* @param callback
|
||||
* @note The target temperature must be reported back to HA using the HAHVAC::setTargetTemperature method.
|
||||
*/
|
||||
inline void onTargetTemperatureCommand(HAHVAC_CALLBACK_TARGET_TEMP(callback))
|
||||
{ _targetTemperatureCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given current temperature.
|
||||
*
|
||||
* @param temperature The temperature to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishCurrentTemperature(const HANumeric& temperature);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given action.
|
||||
*
|
||||
* @param action The action to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishAction(const Action action);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given aux heating state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishAuxState(const bool state);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given fan mode.
|
||||
*
|
||||
* @param mode The mode to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishFanMode(const FanMode mode);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given swing mode.
|
||||
*
|
||||
* @param mode The mode to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishSwingMode(const SwingMode mode);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given mode.
|
||||
*
|
||||
* @param mode The mode to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishMode(const Mode mode);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given target temperature.
|
||||
*
|
||||
* @param temperature The temperature to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishTargetTemperature(const HANumeric& temperature);
|
||||
|
||||
/**
|
||||
* Parses the given aux state command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleAuxStateCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given power command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handlePowerCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given fan mode command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleFanModeCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given swing mode command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleSwingModeCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given HVAC's mode command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleModeCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given HVAC's target temperature command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleTargetTemperatureCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Returns progmem string representing value template for the command
|
||||
* that contains floating point numbers.
|
||||
*/
|
||||
const __FlashStringHelper* getCommandWithFloatTemplate();
|
||||
|
||||
/// Features enabled for the HVAC.
|
||||
const uint16_t _features;
|
||||
|
||||
/// The precision of temperatures. By default it's `HANumber::PrecisionP1`.
|
||||
const NumberPrecision _precision;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The current temperature of the HVAC. By default it's not set.
|
||||
HANumeric _currentTemperature;
|
||||
|
||||
/// The current action of the HVAC. By default it's `HAHVAC::UnknownAction`.
|
||||
Action _action;
|
||||
|
||||
/// The temperature unit for the HVAC. By default it's `HAHVAC::DefaultUnit`.
|
||||
TemperatureUnit _temperatureUnit;
|
||||
|
||||
/// The minimum temperature that can be set.
|
||||
HANumeric _minTemp;
|
||||
|
||||
/// The maximum temperature that can be set.
|
||||
HANumeric _maxTemp;
|
||||
|
||||
/// The step of the temperature that can be set.
|
||||
HANumeric _tempStep;
|
||||
|
||||
/// Callback that will be called when the aux state command is received from the HA.
|
||||
HAHVAC_CALLBACK_BOOL(_auxCallback);
|
||||
|
||||
/// The state of the aux heating. By default it's `false`.
|
||||
bool _auxState;
|
||||
|
||||
/// Callback that will be called when the power command is received from the HA.
|
||||
HAHVAC_CALLBACK_BOOL(_powerCallback);
|
||||
|
||||
/// The current mode of the fan. By default it's `HAHVAC::UnknownFanMode`.
|
||||
FanMode _fanMode;
|
||||
|
||||
/// The supported fan modes. By default it's `HAHVAC::DefaultFanModes`.
|
||||
uint8_t _fanModes;
|
||||
|
||||
/// The serializer for the fan modes. It's `nullptr` if the fan feature is disabled.
|
||||
HASerializerArray* _fanModesSerializer;
|
||||
|
||||
/// Callback that will be called when the fan mode command is received from the HA.
|
||||
HAHVAC_CALLBACK_FAN_MODE(_fanModeCallback);
|
||||
|
||||
/// The current swing mode. By default it's `HAHVAC::UnknownSwingMode`.
|
||||
SwingMode _swingMode;
|
||||
|
||||
/// The supported swing modes. By default it's `HAHVAC::DefaultSwingModes`.
|
||||
uint8_t _swingModes;
|
||||
|
||||
/// The serializer for the swing modes. It's `nullptr` if the swing feature is disabled.
|
||||
HASerializerArray* _swingModesSerializer;
|
||||
|
||||
/// Callback that will be called when the swing mode command is received from the HA.
|
||||
HAHVAC_CALLBACK_SWING_MODE(_swingModeCallback);
|
||||
|
||||
/// The current mode. By default it's `HAHVAC::UnknownMode`.
|
||||
Mode _mode;
|
||||
|
||||
/// The supported modes. By default it's `HAHVAC::DefaultModes`.
|
||||
uint8_t _modes;
|
||||
|
||||
/// The serializer for the modes. It's `nullptr` if the modes feature is disabled.
|
||||
HASerializerArray* _modesSerializer;
|
||||
|
||||
/// Callback that will be called when the mode command is received from the HA.
|
||||
HAHVAC_CALLBACK_MODE(_modeCallback);
|
||||
|
||||
/// The target temperature of the HVAC. By default it's not set.
|
||||
HANumeric _targetTemperature;
|
||||
|
||||
/// Callback that will be called when the target temperature is changed via the HA panel.
|
||||
HAHVAC_CALLBACK_TARGET_TEMP(_targetTemperatureCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,373 @@
|
||||
#include "HALight.h"
|
||||
#ifndef EX_ARDUINOHA_LIGHT
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
const uint8_t HALight::RGBStringMaxLength = 3*4; // 4 characters per color
|
||||
|
||||
void HALight::RGBColor::fromBuffer(const uint8_t* data, const uint16_t length)
|
||||
{
|
||||
if (length > RGBStringMaxLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t firstCommaPos = 0;
|
||||
uint8_t secondCommaPos = 0;
|
||||
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
if (data[i] == ',') {
|
||||
if (firstCommaPos == 0) {
|
||||
firstCommaPos = i;
|
||||
} else if (secondCommaPos == 0) {
|
||||
secondCommaPos = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCommaPos == 0 || secondCommaPos == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t redLen = firstCommaPos;
|
||||
const uint8_t greenLen = secondCommaPos - firstCommaPos - 1; // minus comma
|
||||
const uint8_t blueLen = length - redLen - greenLen - 2; // minus two commas
|
||||
|
||||
const HANumeric& r = HANumeric::fromStr(data, redLen);
|
||||
const HANumeric& g = HANumeric::fromStr(&data[redLen + 1], greenLen);
|
||||
const HANumeric& b = HANumeric::fromStr(&data[redLen + greenLen + 2], blueLen);
|
||||
|
||||
if (r.isUInt8() && g.isUInt8() && b.isUInt8()) {
|
||||
red = r.toUInt8();
|
||||
green = g.toUInt8();
|
||||
blue = b.toUInt8();
|
||||
isSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
HALight::HALight(const char* uniqueId, const uint8_t features) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentLight), uniqueId),
|
||||
_features(features),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_brightnessScale(),
|
||||
_currentState(false),
|
||||
_currentBrightness(0),
|
||||
_minMireds(),
|
||||
_maxMireds(),
|
||||
_currentColorTemperature(0),
|
||||
_currentRGBColor(),
|
||||
_stateCallback(nullptr),
|
||||
_brightnessCallback(nullptr),
|
||||
_colorTemperatureCallback(nullptr),
|
||||
_rgbColorCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HALight::setState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HALight::setBrightness(const uint8_t brightness, const bool force)
|
||||
{
|
||||
if (!force && brightness == _currentBrightness) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishBrightness(brightness)) {
|
||||
_currentBrightness = brightness;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HALight::setColorTemperature(const uint16_t temperature, const bool force)
|
||||
{
|
||||
if (!force && temperature == _currentColorTemperature) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishColorTemperature(temperature)) {
|
||||
_currentColorTemperature = temperature;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HALight::setRGBColor(const RGBColor& color, const bool force)
|
||||
{
|
||||
if (!force && color == _currentRGBColor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishRGBColor(color)) {
|
||||
_currentRGBColor = color;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HALight::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 19); // 19 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_features & BrightnessFeature) {
|
||||
_serializer->topic(AHATOFSTR(HABrightnessStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HABrightnessCommandTopic));
|
||||
|
||||
if (_brightnessScale.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HABrightnessScaleProperty),
|
||||
&_brightnessScale,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & ColorTemperatureFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAColorTemperatureStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HAColorTemperatureCommandTopic));
|
||||
|
||||
if (_minMireds.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMinMiredsProperty),
|
||||
&_minMireds,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_maxMireds.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMaxMiredsProperty),
|
||||
&_maxMireds,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (_features & RGBFeature) {
|
||||
_serializer->topic(AHATOFSTR(HARGBCommandTopic));
|
||||
_serializer->topic(AHATOFSTR(HARGBStateTopic));
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HALight::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
publishBrightness(_currentBrightness);
|
||||
publishColorTemperature(_currentColorTemperature);
|
||||
publishRGBColor(_currentRGBColor);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
|
||||
if (_features & BrightnessFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HABrightnessCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & ColorTemperatureFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HAColorTemperatureCommandTopic));
|
||||
}
|
||||
|
||||
if (_features & RGBFeature) {
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HARGBCommandTopic));
|
||||
}
|
||||
}
|
||||
|
||||
void HALight::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleStateCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HABrightnessCommandTopic)
|
||||
)) {
|
||||
handleBrightnessCommand(payload, length);
|
||||
} else if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HAColorTemperatureCommandTopic)
|
||||
)) {
|
||||
handleColorTemperatureCommand(payload, length);
|
||||
} else if (
|
||||
HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HARGBCommandTopic)
|
||||
)
|
||||
) {
|
||||
handleRGBCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HALight::publishState(const bool state)
|
||||
{
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
bool HALight::publishBrightness(const uint8_t brightness)
|
||||
{
|
||||
if (!(_features & BrightnessFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[3 + 1] = {0}; // uint8_t digits with null terminator
|
||||
HANumeric(brightness, 0).toStr(str);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HABrightnessStateTopic), str, true);
|
||||
}
|
||||
|
||||
bool HALight::publishColorTemperature(const uint16_t temperature)
|
||||
{
|
||||
if (!(_features & ColorTemperatureFeature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[5 + 1] = {0}; // uint16_t digits with null terminator
|
||||
HANumeric(temperature, 0).toStr(str);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAColorTemperatureStateTopic), str, true);
|
||||
}
|
||||
|
||||
bool HALight::publishRGBColor(const RGBColor& color)
|
||||
{
|
||||
if (!(_features & RGBFeature) || !color.isSet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[RGBStringMaxLength] = {0};
|
||||
uint16_t len = 0;
|
||||
|
||||
// append red color with comma
|
||||
len += HANumeric(color.red, 0).toStr(&str[0]);
|
||||
str[len++] = ',';
|
||||
|
||||
// append green color with comma
|
||||
len += HANumeric(color.green, 0).toStr(&str[len]);
|
||||
str[len++] = ',';
|
||||
|
||||
// append blue color
|
||||
HANumeric(color.blue, 0).toStr(&str[len]);
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HARGBStateTopic), str, true);
|
||||
}
|
||||
|
||||
void HALight::handleStateCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
if (!_stateCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_stateCallback(state, this);
|
||||
}
|
||||
|
||||
void HALight::handleBrightnessCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_brightnessCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HANumeric& number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isUInt8()) {
|
||||
_brightnessCallback(number.toUInt8(), this);
|
||||
}
|
||||
}
|
||||
|
||||
void HALight::handleColorTemperatureCommand(
|
||||
const uint8_t* cmd,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (!_colorTemperatureCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HANumeric& number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isUInt16()) {
|
||||
_colorTemperatureCallback(number.toUInt16(), this);
|
||||
}
|
||||
}
|
||||
|
||||
void HALight::handleRGBCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_rgbColorCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
RGBColor color;
|
||||
color.fromBuffer(cmd, length);
|
||||
|
||||
if (color.isSet) {
|
||||
_rgbColorCallback(color, this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,421 @@
|
||||
#ifndef AHA_HALIGHT_H
|
||||
#define AHA_HALIGHT_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_LIGHT
|
||||
|
||||
#define HALIGHT_STATE_CALLBACK(name) void (*name)(bool state, HALight* sender)
|
||||
#define HALIGHT_BRIGHTNESS_CALLBACK(name) void (*name)(uint8_t brightness, HALight* sender)
|
||||
#define HALIGHT_COLOR_TEMP_CALLBACK(name) void (*name)(uint16_t temperature, HALight* sender)
|
||||
#define HALIGHT_RGB_COLOR_CALLBACK(name) void (*name)(HALight::RGBColor color, HALight* sender)
|
||||
|
||||
/**
|
||||
* HALight allows adding a controllable light in the Home Assistant panel.
|
||||
* The library supports only the state, brightness, color temperature and RGB color.
|
||||
* If you need more features please open a new GitHub issue.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/light.mqtt/
|
||||
*/
|
||||
class HALight : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
static const uint8_t RGBStringMaxLength;
|
||||
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
BrightnessFeature = 1,
|
||||
ColorTemperatureFeature = 2,
|
||||
RGBFeature = 4
|
||||
};
|
||||
|
||||
struct RGBColor {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
bool isSet;
|
||||
|
||||
RGBColor() :
|
||||
red(0), green(0), blue(0), isSet(false) { }
|
||||
|
||||
RGBColor(uint8_t r, uint8_t g, uint8_t b) :
|
||||
red(r), green(g), blue(b), isSet(true) { }
|
||||
|
||||
void operator= (const RGBColor& a) {
|
||||
red = a.red;
|
||||
green = a.green;
|
||||
blue = a.blue;
|
||||
isSet = a.isSet;
|
||||
}
|
||||
|
||||
bool operator== (const RGBColor& a) const {
|
||||
return (
|
||||
red == a.red &&
|
||||
green == a.green &&
|
||||
blue == a.blue
|
||||
);
|
||||
}
|
||||
|
||||
bool operator!= (const RGBColor& a) const {
|
||||
return (
|
||||
red != a.red ||
|
||||
green != a.green ||
|
||||
blue != a.blue
|
||||
);
|
||||
}
|
||||
|
||||
void fromBuffer(const uint8_t* data, const uint16_t length);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the light. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the light.
|
||||
* You can enable multiple features by using OR bitwise operator, for example:
|
||||
* `HALight::BrightnessFeature | HALight::ColorTemperatureFeature`
|
||||
*/
|
||||
HALight(const char* uniqueId, const uint8_t features = DefaultFeatures);
|
||||
|
||||
/**
|
||||
* Changes state of the light and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the light.
|
||||
* @param force Forces to update state without comparing it to previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the brightness of the light and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param brightness The new brightness of the light.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setBrightness(const uint8_t brightness, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the color temperature of the light and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param temperature The new color temperature of the light.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setColorTemperature(const uint16_t temperature, const bool force = false);
|
||||
|
||||
/**
|
||||
* Changes the RGB color of the light and publishes MQTT message.
|
||||
* Please note that if a new color is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param color The new RGB color of the light.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setRGBColor(const RGBColor& color, const bool force = false);
|
||||
|
||||
/**
|
||||
* Alias for `setState(true)`.
|
||||
*/
|
||||
inline bool turnOn()
|
||||
{ return setState(true); }
|
||||
|
||||
/**
|
||||
* Alias for `setState(false)`.
|
||||
*/
|
||||
inline bool turnOff()
|
||||
{ return setState(false); }
|
||||
|
||||
/**
|
||||
* Sets current state of the light without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state New state of the light.
|
||||
*/
|
||||
inline void setCurrentState(const bool state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the light.
|
||||
* By default it's `false`.
|
||||
*/
|
||||
inline bool getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets the current brightness of the light without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the brightness before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param brightness The new brightness of the light.
|
||||
*/
|
||||
inline void setCurrentBrightness(const uint8_t brightness)
|
||||
{ _currentBrightness = brightness; }
|
||||
|
||||
/**
|
||||
* Returns the last known brightness of the light.
|
||||
* By default brightness is set to `0`.
|
||||
*/
|
||||
inline uint8_t getCurrentBrightness() const
|
||||
{ return _currentBrightness; }
|
||||
|
||||
/**
|
||||
* Sets the current color temperature of the light without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the color temperature before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param colorTemp The new color temperature (mireds).
|
||||
*/
|
||||
inline void setCurrentColorTemperature(const uint16_t temperature)
|
||||
{ _currentColorTemperature = temperature; }
|
||||
|
||||
/**
|
||||
* Returns the last known color temperature of the light.
|
||||
* By default temperature is set to `0`.
|
||||
*/
|
||||
inline uint16_t getCurrentColorTemperature() const
|
||||
{ return _currentColorTemperature; }
|
||||
|
||||
/**
|
||||
* Sets the current RGB color of the light without pushing the value to Home Assistant.
|
||||
* This method may be useful if you want to change the color before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param color The new RGB color.
|
||||
*/
|
||||
inline void setCurrentRGBColor(const RGBColor& color)
|
||||
{ _currentRGBColor = color; }
|
||||
|
||||
/**
|
||||
* Returns the last known RGB color of the light.
|
||||
* By default the RGB color is set to `0,0,0`.
|
||||
*/
|
||||
inline const RGBColor& getCurrentRGBColor() const
|
||||
{ return _currentRGBColor; }
|
||||
|
||||
/**
|
||||
* Sets icon of the light.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the light's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the light state.
|
||||
* In this mode the light state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Sets the maximum brightness value that can be set via HA panel.
|
||||
* By default it's `255`.
|
||||
*
|
||||
* @param scale The maximum value of the brightness.
|
||||
*/
|
||||
inline void setBrightnessScale(const uint8_t scale)
|
||||
{ _brightnessScale.setBaseValue(scale); }
|
||||
|
||||
/**
|
||||
* Sets the minimum color temperature (mireds) value that can be set via HA panel.
|
||||
* By default it's `153`.
|
||||
*
|
||||
* @param mireds The minimum value of the brightness.
|
||||
*/
|
||||
inline void setMinMireds(const uint16_t mireds)
|
||||
{ _minMireds.setBaseValue(mireds); }
|
||||
|
||||
/**
|
||||
* Sets the maximum color temperature (mireds) value that can be set via HA panel.
|
||||
* By default it's `500`.
|
||||
*
|
||||
* @param mireds The maximum value of the brightness.
|
||||
*/
|
||||
inline void setMaxMireds(const uint16_t mireds)
|
||||
{ _maxMireds.setBaseValue(mireds); }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the state command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same light.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the state must be reported back to HA using the HALight::setState method.
|
||||
*/
|
||||
inline void onStateCommand(HALIGHT_STATE_CALLBACK(callback))
|
||||
{ _stateCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the brightness command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same light.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the brightness must be reported back to HA using the HALight::setBrightness method.
|
||||
*/
|
||||
inline void onBrightnessCommand(HALIGHT_BRIGHTNESS_CALLBACK(callback))
|
||||
{ _brightnessCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the color temperature command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same light.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the color temperature must be reported back to HA using the HALight::setColorTemperature method.
|
||||
*/
|
||||
inline void onColorTemperatureCommand(HALIGHT_COLOR_TEMP_CALLBACK(callback))
|
||||
{ _colorTemperatureCallback = callback; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the RGB color command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same light.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the color must be reported back to HA using the HALight::setRGBColor method.
|
||||
*/
|
||||
inline void onRGBColorCommand(HALIGHT_RGB_COLOR_CALLBACK(callback))
|
||||
{ _rgbColorCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const bool state);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given brightness.
|
||||
*
|
||||
* @param brightness The brightness to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishBrightness(const uint8_t brightness);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given color temperature (mireds).
|
||||
*
|
||||
* @param temperature The color temperature to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishColorTemperature(const uint16_t temperature);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given RGB color.
|
||||
*
|
||||
* @param color The color to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishRGBColor(const RGBColor& color);
|
||||
|
||||
/**
|
||||
* Parses the given state command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleStateCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given brightness command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleBrightnessCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given color temperature command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleColorTemperatureCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Parses the given RGB color command and executes the callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleRGBCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/// Features enabled for the light.
|
||||
const uint8_t _features;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the light (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The maximum value of the brightness. By default it's 255.
|
||||
HANumeric _brightnessScale;
|
||||
|
||||
/// The current state of the light. By default it's `false`.
|
||||
bool _currentState;
|
||||
|
||||
/// The current brightness of the light. By default it's `0`.
|
||||
uint8_t _currentBrightness;
|
||||
|
||||
/// The minimum color temperature (mireds). By default the value is not set.
|
||||
HANumeric _minMireds;
|
||||
|
||||
/// The maximum color temperature (mireds). By default the value is not set.
|
||||
HANumeric _maxMireds;
|
||||
|
||||
/// The current color temperature (mireds). By default the value is not set.
|
||||
uint16_t _currentColorTemperature;
|
||||
|
||||
/// The current RBB color. By default the value is not set.
|
||||
RGBColor _currentRGBColor;
|
||||
|
||||
/// The callback that will be called when the state command is received from the HA.
|
||||
HALIGHT_STATE_CALLBACK(_stateCallback);
|
||||
|
||||
/// The callback that will be called when the brightness command is received from the HA.
|
||||
HALIGHT_BRIGHTNESS_CALLBACK(_brightnessCallback);
|
||||
|
||||
/// The callback that will be called when the color temperature command is received from the HA.
|
||||
HALIGHT_COLOR_TEMP_CALLBACK(_colorTemperatureCallback);
|
||||
|
||||
/// The callback that will be called when the RGB command is received from the HA.
|
||||
HALIGHT_RGB_COLOR_CALLBACK(_rgbColorCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "HALock.h"
|
||||
#ifndef EX_ARDUINOHA_LOCK
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HALock::HALock(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentLock), uniqueId),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_currentState(StateUnknown),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HALock::setState(const LockState state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HALock::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 10); // 10 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HALock::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HALock::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HALock::publishState(const LockState state)
|
||||
{
|
||||
if (state == StateUnknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state == StateLocked ? HAStateLocked : HAStateUnlocked),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
void HALock::handleCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_commandCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HALockCommand, length) == 0) {
|
||||
_commandCallback(CommandLock, this);
|
||||
} else if (memcmp_P(cmd, HAUnlockCommand, length) == 0) {
|
||||
_commandCallback(CommandUnlock, this);
|
||||
} else if (memcmp_P(cmd, HAOpenCommand, length) == 0) {
|
||||
_commandCallback(CommandOpen, this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,148 @@
|
||||
#ifndef AHA_HALOCK_H
|
||||
#define AHA_HALOCK_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_LOCK
|
||||
|
||||
#define HALOCK_CALLBACK(name) void (*name)(LockCommand command, HALock* sender)
|
||||
|
||||
/**
|
||||
* HALock allows to implement a custom lock (for example: door lock)
|
||||
* that can be controlled from the Home Assistant panel.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/lock.mqtt/
|
||||
*/
|
||||
class HALock : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/// Available states of the lock that can be reported to the HA panel.
|
||||
enum LockState {
|
||||
StateUnknown = 0,
|
||||
StateLocked,
|
||||
StateUnlocked
|
||||
};
|
||||
|
||||
/// Commands that will be produced by the HA panel.
|
||||
enum LockCommand {
|
||||
CommandLock = 1,
|
||||
CommandUnlock,
|
||||
CommandOpen
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the lock. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HALock(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Changes state of the lock and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the lock.
|
||||
* @param force Forces to update state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const LockState state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Sets current state of the lock without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state New state of the lock.
|
||||
*/
|
||||
inline void setCurrentState(const LockState state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the lock.
|
||||
* If setState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline LockState getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets icon of the lock.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the lock's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the lock state.
|
||||
* In this mode the lock state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the lock/unlock/open command from the HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same lock.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onCommand(HALOCK_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const LockState state);
|
||||
|
||||
/**
|
||||
* Parses the given command and executes the lock's callback with proper enum's property.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/// The icon of the lock. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the lock (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The current state of the lock. By default it's `HALock::StateUnknown`.
|
||||
LockState _currentState;
|
||||
|
||||
/// The callback that will be called when lock/unlock/open command is received from the HA.
|
||||
HALOCK_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,214 @@
|
||||
#include "HANumber.h"
|
||||
#ifndef EX_ARDUINOHA_NUMBER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HANumber::HANumber(const char* uniqueId, const NumberPrecision precision) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentNumber), uniqueId),
|
||||
_precision(precision),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_mode(ModeAuto),
|
||||
_unitOfMeasurement(nullptr),
|
||||
_minValue(),
|
||||
_maxValue(),
|
||||
_step(),
|
||||
_currentState(),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HANumber::setState(const HANumeric& state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HANumber::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 16); // 16 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(AHATOFSTR(HAUnitOfMeasurementProperty), _unitOfMeasurement);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAModeProperty),
|
||||
getModeProperty(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HACommandTemplateProperty),
|
||||
getCommandTemplate(),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
|
||||
if (_minValue.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMinProperty),
|
||||
&_minValue,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_maxValue.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMaxProperty),
|
||||
&_maxValue,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_step.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAStepProperty),
|
||||
&_step,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HANumber::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HANumber::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
handleCommand(payload, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool HANumber::publishState(const HANumeric& state)
|
||||
{
|
||||
if (!state.isSet()) {
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(HAStateNone),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
const uint8_t size = state.calculateSize();
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[size + 1]; // with null terminator
|
||||
str[size] = 0;
|
||||
state.toStr(str);
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
str,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
void HANumber::handleCommand(const uint8_t* cmd, const uint16_t length)
|
||||
{
|
||||
if (!_commandCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp_P(cmd, HAStateNone, length) == 0) {
|
||||
_commandCallback(HANumeric(), this);
|
||||
} else {
|
||||
HANumeric number = HANumeric::fromStr(cmd, length);
|
||||
if (number.isSet()) {
|
||||
number.setPrecision(_precision);
|
||||
_commandCallback(number, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HANumber::getModeProperty() const
|
||||
{
|
||||
switch (_mode) {
|
||||
case ModeBox:
|
||||
return AHATOFSTR(HAModeBox);
|
||||
|
||||
case ModeSlider:
|
||||
return AHATOFSTR(HAModeSlider);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const __FlashStringHelper* HANumber::getCommandTemplate()
|
||||
{
|
||||
switch (_precision) {
|
||||
case PrecisionP1:
|
||||
return AHATOFSTR(HAValueTemplateFloatP1);
|
||||
|
||||
case PrecisionP2:
|
||||
return AHATOFSTR(HAValueTemplateFloatP2);
|
||||
|
||||
case PrecisionP3:
|
||||
return AHATOFSTR(HAValueTemplateFloatP3);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,263 @@
|
||||
#ifndef AHA_HANUMBER_H
|
||||
#define AHA_HANUMBER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_NUMBER
|
||||
|
||||
#define _SET_STATE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline bool setState(const type state, const bool force = false) \
|
||||
{ return setState(HANumeric(state, _precision), force); }
|
||||
|
||||
#define _SET_CURRENT_STATE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline void setCurrentState(const type state) \
|
||||
{ setCurrentState(HANumeric(state, _precision)); }
|
||||
|
||||
#define HANUMBER_CALLBACK(name) void (*name)(HANumeric number, HANumber* sender)
|
||||
|
||||
/**
|
||||
* HANumber adds a slider or a box in the Home Assistant panel
|
||||
* that controls the numeric value stored on your device.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/number.mqtt/
|
||||
*/
|
||||
class HANumber : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/// Represents mode of the number.
|
||||
enum Mode {
|
||||
ModeAuto = 0,
|
||||
ModeBox,
|
||||
ModeSlider
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates instance of the HANumber entity with the given numbers precision.
|
||||
* The given precision applies to the state, min, max and step values.
|
||||
*
|
||||
* @param uniqueId The unique ID of the number. It needs to be unique in a scope of your device.
|
||||
* @param precision Precision of the floating point number that will be displayed in the HA panel.
|
||||
*/
|
||||
HANumber(const char* uniqueId, const NumberPrecision precision = PrecisionP0);
|
||||
|
||||
/**
|
||||
* Changes state of the number and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the number.
|
||||
* @param force Forces to update state without comparing it to a previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const HANumeric& state, const bool force = false);
|
||||
|
||||
_SET_STATE_OVERLOAD(int8_t)
|
||||
_SET_STATE_OVERLOAD(int16_t)
|
||||
_SET_STATE_OVERLOAD(int32_t)
|
||||
_SET_STATE_OVERLOAD(uint8_t)
|
||||
_SET_STATE_OVERLOAD(uint16_t)
|
||||
_SET_STATE_OVERLOAD(uint32_t)
|
||||
_SET_STATE_OVERLOAD(float)
|
||||
|
||||
#ifdef ARDUINOHA_INT_OVERLOAD
|
||||
_SET_STATE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets current state of the number without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state New state of the number.
|
||||
*/
|
||||
inline void setCurrentState(const HANumeric& state)
|
||||
{ if (state.getPrecision() == _precision) { _currentState = state; } }
|
||||
|
||||
_SET_CURRENT_STATE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_STATE_OVERLOAD(float)
|
||||
|
||||
#ifdef ARDUINOHA_INT_OVERLOAD
|
||||
_SET_CURRENT_STATE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns last known state of the number.
|
||||
* If setState method wasn't called the initial value will be returned.
|
||||
*/
|
||||
inline const HANumeric& getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/number/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the number.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the number's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the number state.
|
||||
* In this mode the number state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Sets mode of the number.
|
||||
* It controls how the number should be displayed in the UI.
|
||||
* By default it's `HANumber::ModeAuto`.
|
||||
*
|
||||
* @param mode Mode to set.
|
||||
*/
|
||||
inline void setMode(const Mode mode)
|
||||
{ _mode = mode; }
|
||||
|
||||
/**
|
||||
* Defines the units of measurement of the number, if any.
|
||||
*
|
||||
* @param units For example: °C, %
|
||||
*/
|
||||
inline void setUnitOfMeasurement(const char* unitOfMeasurement)
|
||||
{ _unitOfMeasurement = unitOfMeasurement; }
|
||||
|
||||
/**
|
||||
* Sets the minimum value that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param min The minimal value. By default the value is not set.
|
||||
*/
|
||||
inline void setMin(const float min)
|
||||
{ _minValue = HANumeric(min, _precision); }
|
||||
|
||||
/**
|
||||
* Sets the maximum value that can be set from the Home Assistant panel.
|
||||
*
|
||||
* @param min The maximum value. By default the value is not set.
|
||||
*/
|
||||
inline void setMax(const float max)
|
||||
{ _maxValue = HANumeric(max, _precision); }
|
||||
|
||||
/**
|
||||
* Sets step of the slider's movement in the Home Assistant panel.
|
||||
*
|
||||
* @param step The step value. Smallest value `0.001`. By default the value is not set.
|
||||
*/
|
||||
inline void setStep(const float step)
|
||||
{ _step = HANumeric(step, _precision); }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the number is changed in the HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same number.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the number must be reported back to HA using the HANumber::setState method.
|
||||
*/
|
||||
inline void onCommand(HANUMBER_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const HANumeric& state);
|
||||
|
||||
/**
|
||||
* Parses the given command and executes the number's callback with proper value.
|
||||
*
|
||||
* @param cmd The data of the command.
|
||||
* @param length Length of the command.
|
||||
*/
|
||||
void handleCommand(const uint8_t* cmd, const uint16_t length);
|
||||
|
||||
/**
|
||||
* Returns progmem string representing mode of the number
|
||||
*/
|
||||
const __FlashStringHelper* getModeProperty() const;
|
||||
|
||||
/**
|
||||
* Returns progmem string representing value template for the command.
|
||||
*/
|
||||
const __FlashStringHelper* getCommandTemplate();
|
||||
|
||||
/// The precision of the number. By default it's `HANumber::PrecisionP0`.
|
||||
const NumberPrecision _precision;
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the number. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the number (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// Controls how the number should be displayed in the UI. By default it's `HANumber::ModeAuto`.
|
||||
Mode _mode;
|
||||
|
||||
/// The unit of measurement for the sensor. It can be nullptr.
|
||||
const char* _unitOfMeasurement;
|
||||
|
||||
/// The minimal value that can be set from the HA panel.
|
||||
HANumeric _minValue;
|
||||
|
||||
/// The maximum value that can be set from the HA panel.
|
||||
HANumeric _maxValue;
|
||||
|
||||
/// The step of the slider's movement.
|
||||
HANumeric _step;
|
||||
|
||||
/// The current state of the number. By default the value is not set.
|
||||
HANumeric _currentState;
|
||||
|
||||
/// The callback that will be called when the command is received from the HA.
|
||||
HANUMBER_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "HAScene.h"
|
||||
#ifndef EX_ARDUINOHA_SCENE
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HAScene::HAScene(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentScene), uniqueId),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HAScene::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 8); // 8 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
// optional property
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
// HA 2022.10 throws an exception if this property is not set
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAPayloadOnProperty),
|
||||
AHATOFSTR(HAStateOn),
|
||||
HASerializer::ProgmemPropertyValue
|
||||
);
|
||||
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAScene::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HAScene::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
|
||||
if (_commandCallback && HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
_commandCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef AHA_HASCENE_H
|
||||
#define AHA_HASCENE_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SCENE
|
||||
|
||||
#define HASCENE_CALLBACK(name) void (*name)(HAScene* sender)
|
||||
|
||||
/**
|
||||
* HAScene adds a new scene to the Home Assistant that triggers your callback once activated.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/scene.mqtt/
|
||||
*/
|
||||
class HAScene : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the scene. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HAScene(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Sets icon of the scene.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the scene's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called when the scene is activated in the HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same scene.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
inline void onCommand(HASCENE_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/// The icon of the scene. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The command callback that will be called when scene is activated from the HA panel.
|
||||
HASCENE_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,208 @@
|
||||
#include "HASelect.h"
|
||||
#ifndef EX_ARDUINOHA_SELECT
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HASelect::HASelect(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentSelect), uniqueId),
|
||||
_options(nullptr),
|
||||
_currentState(-1),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HASelect::~HASelect()
|
||||
{
|
||||
if (_options) {
|
||||
const uint8_t optionsNb = _options->getItemsNb();
|
||||
const HASerializerArray::ItemType* options = _options->getItems();
|
||||
|
||||
if (optionsNb > 1) {
|
||||
for (uint8_t i = 0; i < optionsNb; i++) {
|
||||
delete options[i];
|
||||
}
|
||||
}
|
||||
|
||||
delete _options;
|
||||
}
|
||||
}
|
||||
|
||||
void HASelect::setOptions(const char* options)
|
||||
{
|
||||
if (!options || _options) { // options can be set only once
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t optionsNb = countOptionsInString(options);
|
||||
if (optionsNb == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t optionsLen = strlen(options) + 1; // include null terminator
|
||||
_options = new HASerializerArray(optionsNb, false);
|
||||
|
||||
if (optionsNb == 1) {
|
||||
_options->add(options);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t optionLen = 0;
|
||||
for (uint16_t i = 0; i < optionsLen; i++) {
|
||||
if (options[i] == ';' || options[i] == 0) {
|
||||
if (optionLen == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
char* option = new char[optionLen + 1]; // including null terminator
|
||||
option[optionLen] = 0;
|
||||
memcpy(option, &options[i - optionLen], optionLen);
|
||||
|
||||
_options->add(option);
|
||||
optionLen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
optionLen++;
|
||||
}
|
||||
}
|
||||
|
||||
bool HASelect::setState(const int8_t state, const bool force)
|
||||
{
|
||||
if (!force && _currentState == state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* HASelect::getCurrentOption() const
|
||||
{
|
||||
return _options->getItem(getCurrentState());
|
||||
}
|
||||
|
||||
void HASelect::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId() || !_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 11); // 11 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptionsProperty),
|
||||
_options,
|
||||
HASerializer::ArrayPropertyType
|
||||
);
|
||||
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HASelect::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HASelect::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
if (_commandCallback && HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
const uint8_t optionsNb = _options->getItemsNb();
|
||||
const HASerializerArray::ItemType* options = _options->getItems();
|
||||
|
||||
for (uint8_t i = 0; i < optionsNb; i++) {
|
||||
if (memcmp(payload, options[i], length) == 0) {
|
||||
_commandCallback(i, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HASelect::publishState(const int8_t state)
|
||||
{
|
||||
if (!_options || state >= _options->getItemsNb()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state < 0) {
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), AHATOFSTR(HAStateNone), true);
|
||||
}
|
||||
|
||||
const char* item = _options->getItems()[state];
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), item, true);
|
||||
}
|
||||
|
||||
uint8_t HASelect::countOptionsInString(const char* options) const
|
||||
{
|
||||
// the given string is treated as a single option if there are no semicolons
|
||||
uint8_t optionsNb = 1;
|
||||
const uint16_t optionsLen = strlen(options);
|
||||
|
||||
if (optionsLen == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < optionsLen; i++) {
|
||||
if (options[i] == ';') {
|
||||
optionsNb++;
|
||||
}
|
||||
}
|
||||
|
||||
return optionsNb;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
#ifndef AHA_HASELECT_H
|
||||
#define AHA_HASELECT_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SELECT
|
||||
|
||||
class HASerializerArray;
|
||||
|
||||
#define HASELECT_CALLBACK(name) void (*name)(int8_t index, HASelect* sender)
|
||||
|
||||
/**
|
||||
* HASelect adds a dropdown with options in the Home Assistant panel.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/button.mqtt/
|
||||
*/
|
||||
class HASelect : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the select. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HASelect(const char* uniqueId);
|
||||
~HASelect();
|
||||
|
||||
/**
|
||||
* Sets the list of available options that will be listed in the dropdown.
|
||||
* The input string should contain options separated using semicolons.
|
||||
* For example: `setOptions("Option A;Option B;Option C");
|
||||
*
|
||||
* @param options The list of options that are separated by semicolons.
|
||||
* @note The options list can be set only once.
|
||||
*/
|
||||
void setOptions(const char* options);
|
||||
|
||||
/**
|
||||
* Changes state of the select and publishes MQTT message.
|
||||
* State represents the index of the option that was set using the setOptions method.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the select. You can set `-1` to reset the select.
|
||||
* @param force Forces to update state without comparing it to previous known state.
|
||||
* @returns Returns true if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const int8_t state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Returns the selected option based on the most recent state of the select.
|
||||
* You can utilize this method to get the string representation of the option (e.g. for printing).
|
||||
* If no option is selected, null is returned.
|
||||
*/
|
||||
const char* getCurrentOption() const;
|
||||
|
||||
/**
|
||||
* Sets the current state of the select without publishing it to Home Assistant.
|
||||
* State represents the index of the option that was set using the setOptions method.
|
||||
* This method may be useful if you want to change the state before the connection
|
||||
* with the MQTT broker is acquired.
|
||||
*
|
||||
* @param state The new state of the cover.
|
||||
*/
|
||||
inline void setCurrentState(const int8_t state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the select.
|
||||
* State represents the index of the option that was set using the setOptions method.
|
||||
* By default the state is set to `-1`.
|
||||
*/
|
||||
inline int8_t getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets icon of the select.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the select's command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the select state.
|
||||
* In this mode the select state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the option is changed from the HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same select.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the selected option must be reported back to HA using the HASelect::setState method.
|
||||
*/
|
||||
inline void onCommand(HASELECT_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
inline HASerializerArray* getOptions() const
|
||||
{ return _options; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const int8_t state);
|
||||
|
||||
/**
|
||||
* Counts the amount of options in the given string.
|
||||
*/
|
||||
uint8_t countOptionsInString(const char* options) const;
|
||||
|
||||
/// Array of options for the serializer.
|
||||
HASerializerArray* _options;
|
||||
|
||||
/// Stores the current state (the current option's index). By default it's `-1`.
|
||||
int8_t _currentState;
|
||||
|
||||
/// The icon of the select. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the select (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The command callback that will be called when option is changed via the HA panel.
|
||||
HASELECT_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "HASensor.h"
|
||||
#ifndef EX_ARDUINOHA_SENSOR
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HASensor::HASensor(const char* uniqueId, const uint16_t features) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentSensor), uniqueId),
|
||||
_features(features),
|
||||
_deviceClass(nullptr),
|
||||
_stateClass(nullptr),
|
||||
_forceUpdate(false),
|
||||
_icon(nullptr),
|
||||
_unitOfMeasurement(nullptr),
|
||||
_expireAfter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HASensor::setValue(const char* value)
|
||||
{
|
||||
if (!value) {
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), AHATOFSTR(HAStateNone), true);
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), value, true);
|
||||
}
|
||||
|
||||
bool HASensor::setJsonAttributes(const char* json)
|
||||
{
|
||||
return publishOnDataTopic(AHATOFSTR(HAJsonAttributesTopic), json, true);
|
||||
}
|
||||
|
||||
void HASensor::setExpireAfter(uint16_t expireAfter)
|
||||
{
|
||||
if (expireAfter > 0) {
|
||||
_expireAfter.setBaseValue(expireAfter);
|
||||
} else {
|
||||
_expireAfter.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void HASensor::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 13); // 13 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _deviceClass);
|
||||
_serializer->set(AHATOFSTR(HAStateClassProperty), _stateClass);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
_serializer->set(AHATOFSTR(HAUnitOfMeasurementProperty), _unitOfMeasurement);
|
||||
|
||||
if (_forceUpdate) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAForceUpdateProperty),
|
||||
&_forceUpdate,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_expireAfter.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAExpireAfterProperty),
|
||||
&_expireAfter,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_features & JsonAttributesFeature) {
|
||||
_serializer->topic(AHATOFSTR(HAJsonAttributesTopic));
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
}
|
||||
|
||||
void HASensor::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,130 @@
|
||||
#ifndef AHA_HASENSOR_H
|
||||
#define AHA_HASENSOR_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
#include "../utils/HANumeric.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SENSOR
|
||||
|
||||
/**
|
||||
* HASensor allows to publish textual sensor values that will be displayed in the HA panel.
|
||||
* If you need to publish numbers then HASensorNumber is what you're looking for.
|
||||
*
|
||||
* @note It's not possible to define a sensor that publishes mixed values (e.g. string + integer values).
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/sensor.mqtt/
|
||||
*/
|
||||
class HASensor : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
enum Features {
|
||||
DefaultFeatures = 0,
|
||||
JsonAttributesFeature = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @param uniqueId The unique ID of the sensor. It needs to be unique in a scope of your device.
|
||||
* @param features Features that should be enabled for the sensor.
|
||||
*/
|
||||
HASensor(const char* uniqueId, const uint16_t features = DefaultFeatures);
|
||||
|
||||
/**
|
||||
* Publishes the MQTT message with the given value.
|
||||
* Unlike the other device types, the HASensor doesn't store the previous value that was set.
|
||||
* It means that the MQTT message is produced each time the setValue method is called.
|
||||
*
|
||||
* @param value String representation of the sensor's value.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setValue(const char* value);
|
||||
|
||||
/**
|
||||
* Publishes the given JSON data on the JSON attributes topic.
|
||||
* The `JsonAttributesFeature` has to be enabled prior to setting the value.
|
||||
*
|
||||
* @param json JSON attributes.
|
||||
*/
|
||||
bool setJsonAttributes(const char* json);
|
||||
|
||||
/**
|
||||
* Sets the number of seconds after the sensor’s state expires, if it’s not updated.
|
||||
* By default the sensors state never expires.
|
||||
*
|
||||
* @param expireAfter The number of seconds.
|
||||
*/
|
||||
void setExpireAfter(uint16_t expireAfter);
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/sensor/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _deviceClass = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets class of the state for the long term stats.
|
||||
* See: https://developers.home-assistant.io/docs/core/entity/sensor/#long-term-statistics
|
||||
*
|
||||
* @param stateClass The class name.
|
||||
*/
|
||||
inline void setStateClass(const char* stateClass)
|
||||
{ _stateClass = stateClass; }
|
||||
|
||||
/**
|
||||
* Forces HA panel to process each incoming value (MQTT message).
|
||||
* It's useful if you want to have meaningful value graphs in history.
|
||||
*
|
||||
* @param forceUpdate
|
||||
*/
|
||||
inline void setForceUpdate(bool forceUpdate)
|
||||
{ _forceUpdate = forceUpdate; }
|
||||
|
||||
/**
|
||||
* Sets icon of the sensor.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param class The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Defines the units of measurement of the sensor, if any.
|
||||
*
|
||||
* @param units For example: °C, %
|
||||
*/
|
||||
inline void setUnitOfMeasurement(const char* unitOfMeasurement)
|
||||
{ _unitOfMeasurement = unitOfMeasurement; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override final;
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/// Features enabled for the sensor.
|
||||
const uint16_t _features;
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _deviceClass;
|
||||
|
||||
/// The state class for the long term stats. It can be nullptr. See: https://developers.home-assistant.io/docs/core/entity/sensor/#long-term-statistics
|
||||
const char* _stateClass;
|
||||
|
||||
/// The force update flag for the HA panel.
|
||||
bool _forceUpdate;
|
||||
|
||||
/// The icon of the sensor. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The unit of measurement for the sensor. It can be nullptr.
|
||||
const char* _unitOfMeasurement;
|
||||
|
||||
/// It defines the number of seconds after the sensor’s state expires, if it’s not updated. By default the sensors state never expires.
|
||||
HANumeric _expireAfter;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "HASensorNumber.h"
|
||||
#ifndef EX_ARDUINOHA_SENSOR
|
||||
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HASensorNumber::HASensorNumber(
|
||||
const char* uniqueId,
|
||||
const NumberPrecision precision,
|
||||
const uint16_t features
|
||||
) :
|
||||
HASensor(uniqueId, features),
|
||||
_precision(precision),
|
||||
_currentValue()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HASensorNumber::setValue(const HANumeric& value, const bool force)
|
||||
{
|
||||
if (value.getPrecision() != _precision) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!force && value == _currentValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishValue(value)) {
|
||||
_currentValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HASensorNumber::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HASensor::onMqttConnected();
|
||||
publishValue(_currentValue);
|
||||
}
|
||||
|
||||
bool HASensorNumber::publishValue(const HANumeric& value)
|
||||
{
|
||||
if (!value.isSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t size = value.calculateSize();
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char str[size + 1]; // with null terminator
|
||||
str[size] = 0;
|
||||
value.toStr(str);
|
||||
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
str,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
#ifndef AHA_HASENSORNUMBER_H
|
||||
#define AHA_HASENSORNUMBER_H
|
||||
|
||||
#include "HASensor.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SENSOR
|
||||
|
||||
#define _SET_VALUE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline bool setValue(const type value, const bool force = false) \
|
||||
{ return setValue(HANumeric(value, _precision), force); }
|
||||
|
||||
#define _SET_CURRENT_VALUE_OVERLOAD(type) \
|
||||
/** @overload */ \
|
||||
inline void setCurrentValue(const type value) \
|
||||
{ setCurrentValue(HANumeric(value, _precision)); }
|
||||
|
||||
/**
|
||||
* HASensorInteger allows to publish numeric values of a sensor that will be displayed in the HA panel.
|
||||
*
|
||||
* @note You can find more information about this class in HASensor documentation.
|
||||
*/
|
||||
class HASensorNumber : public HASensor
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the sensor. It needs to be unique in a scope of your device.
|
||||
* @param precision Precision of the floating point number that will be displayed in the HA panel.
|
||||
* @param features Features that should be enabled for the sensor.
|
||||
*/
|
||||
HASensorNumber(
|
||||
const char* uniqueId,
|
||||
const NumberPrecision precision = PrecisionP0,
|
||||
const uint16_t features = DefaultFeatures
|
||||
);
|
||||
|
||||
/**
|
||||
* Changes value of the sensor and publish MQTT message.
|
||||
* Please note that if a new value is the same as the previous one the MQTT message won't be published.
|
||||
*
|
||||
* @param value New value of the sensor. THe precision of the value needs to match precision of the sensor.
|
||||
* @param force Forces to update the value without comparing it to a previous known value.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool setValue(const HANumeric& value, const bool force = false);
|
||||
|
||||
_SET_VALUE_OVERLOAD(int8_t)
|
||||
_SET_VALUE_OVERLOAD(int16_t)
|
||||
_SET_VALUE_OVERLOAD(int32_t)
|
||||
_SET_VALUE_OVERLOAD(uint8_t)
|
||||
_SET_VALUE_OVERLOAD(uint16_t)
|
||||
_SET_VALUE_OVERLOAD(uint32_t)
|
||||
_SET_VALUE_OVERLOAD(float)
|
||||
|
||||
#ifdef ARDUINOHA_INT_OVERLOAD
|
||||
_SET_VALUE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets the current value of the sensor without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change the value before the connection with the MQTT broker is acquired.
|
||||
*
|
||||
* @param value New value of the sensor.
|
||||
*/
|
||||
inline void setCurrentValue(const HANumeric& value)
|
||||
{ if (value.getPrecision() == _precision) { _currentValue = value; } }
|
||||
|
||||
_SET_CURRENT_VALUE_OVERLOAD(int8_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(int16_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(int32_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(uint8_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(uint16_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(uint32_t)
|
||||
_SET_CURRENT_VALUE_OVERLOAD(float)
|
||||
|
||||
#ifdef ARDUINOHA_INT_OVERLOAD
|
||||
_SET_CURRENT_VALUE_OVERLOAD(int)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns the last known value of the sensor.
|
||||
* By default the value is not set.
|
||||
*/
|
||||
inline const HANumeric& getCurrentValue() const
|
||||
{ return _currentValue; }
|
||||
|
||||
protected:
|
||||
virtual void onMqttConnected() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given value.
|
||||
*
|
||||
* @param state The value to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishValue(const HANumeric& value);
|
||||
|
||||
/// The precision of the sensor. By default it's `HASensorNumber::PrecisionP0`.
|
||||
const NumberPrecision _precision;
|
||||
|
||||
/// The current value of the sensor. By default the value is not set.
|
||||
HANumeric _currentValue;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "HASwitch.h"
|
||||
#ifndef EX_ARDUINOHA_SWITCH
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HASwitch::HASwitch(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentSwitch), uniqueId),
|
||||
_class(nullptr),
|
||||
_icon(nullptr),
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_currentState(false),
|
||||
_commandCallback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HASwitch::setState(const bool state, const bool force)
|
||||
{
|
||||
if (!force && state == _currentState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publishState(state)) {
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HASwitch::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 11); // 11 - max properties nb
|
||||
_serializer->set(AHATOFSTR(HANameProperty), _name);
|
||||
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
|
||||
_serializer->set(HASerializer::WithUniqueId);
|
||||
_serializer->set(AHATOFSTR(HADeviceClassProperty), _class);
|
||||
_serializer->set(AHATOFSTR(HAIconProperty), _icon);
|
||||
|
||||
// optional property
|
||||
if (_retain) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HARetainProperty),
|
||||
&_retain,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_optimistic) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAOptimisticProperty),
|
||||
&_optimistic,
|
||||
HASerializer::BoolPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->set(HASerializer::WithAvailability);
|
||||
_serializer->topic(AHATOFSTR(HAStateTopic));
|
||||
_serializer->topic(AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HASwitch::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
publishAvailability();
|
||||
|
||||
if (!_retain) {
|
||||
publishState(_currentState);
|
||||
}
|
||||
|
||||
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
|
||||
}
|
||||
|
||||
void HASwitch::onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
)
|
||||
{
|
||||
(void)payload;
|
||||
|
||||
if (_commandCallback && HASerializer::compareDataTopics(
|
||||
topic,
|
||||
uniqueId(),
|
||||
AHATOFSTR(HACommandTopic)
|
||||
)) {
|
||||
bool state = length == strlen_P(HAStateOn);
|
||||
_commandCallback(state, this);
|
||||
}
|
||||
}
|
||||
|
||||
bool HASwitch::publishState(const bool state)
|
||||
{
|
||||
return publishOnDataTopic(
|
||||
AHATOFSTR(HAStateTopic),
|
||||
AHATOFSTR(state ? HAStateOn : HAStateOff),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,150 @@
|
||||
#ifndef AHA_HASWITCH_H
|
||||
#define AHA_HASWITCH_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_SWITCH
|
||||
|
||||
#define HASWITCH_CALLBACK(name) void (*name)(bool state, HASwitch* sender)
|
||||
|
||||
/**
|
||||
* HASwitch allows to display on/off switch in the HA panel and receive commands on your device.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/switch.mqtt/
|
||||
*/
|
||||
class HASwitch : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the sensor. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HASwitch(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Changes state of the switch and publishes MQTT message.
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the switch.
|
||||
* @param force Forces to update state without comparing it to previous known state.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const bool state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Alias for `setState(true)`.
|
||||
*/
|
||||
inline bool turnOn()
|
||||
{ return setState(true); }
|
||||
|
||||
/**
|
||||
* Alias for `setState(false)`.
|
||||
*/
|
||||
inline bool turnOff()
|
||||
{ return setState(false); }
|
||||
|
||||
/**
|
||||
* Sets current state of the switch without publishing it to Home Assistant.
|
||||
* This method may be useful if you want to change state before connection
|
||||
* with MQTT broker is acquired.
|
||||
*
|
||||
* @param state New state of the switch.
|
||||
*/
|
||||
inline void setCurrentState(const bool state)
|
||||
{ _currentState = state; }
|
||||
|
||||
/**
|
||||
* Returns last known state of the switch.
|
||||
* By default it's `false`.
|
||||
*/
|
||||
inline bool getCurrentState() const
|
||||
{ return _currentState; }
|
||||
|
||||
/**
|
||||
* Sets class of the device.
|
||||
* You can find list of available values here: https://www.home-assistant.io/integrations/switch/#device-class
|
||||
*
|
||||
* @param deviceClass The class name.
|
||||
*/
|
||||
inline void setDeviceClass(const char* deviceClass)
|
||||
{ _class = deviceClass; }
|
||||
|
||||
/**
|
||||
* Sets icon of the sensor.
|
||||
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
|
||||
*
|
||||
* @param icon The icon name.
|
||||
*/
|
||||
inline void setIcon(const char* icon)
|
||||
{ _icon = icon; }
|
||||
|
||||
/**
|
||||
* Sets retain flag for the switch command.
|
||||
* If set to `true` the command produced by Home Assistant will be retained.
|
||||
*
|
||||
* @param retain
|
||||
*/
|
||||
inline void setRetain(const bool retain)
|
||||
{ _retain = retain; }
|
||||
|
||||
/**
|
||||
* Sets optimistic flag for the switch state.
|
||||
* In this mode the switch state doesn't need to be reported back to the HA panel when a command is received.
|
||||
* By default the optimistic mode is disabled.
|
||||
*
|
||||
* @param optimistic The optimistic mode (`true` - enabled, `false` - disabled).
|
||||
*/
|
||||
inline void setOptimistic(const bool optimistic)
|
||||
{ _optimistic = optimistic; }
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the on/off command from HA is received.
|
||||
* Please note that it's not possible to register multiple callbacks for the same switch.
|
||||
*
|
||||
* @param callback
|
||||
* @note In non-optimistic mode, the state must be reported back to HA using the HASwitch::setState method.
|
||||
*/
|
||||
inline void onCommand(HASWITCH_CALLBACK(callback))
|
||||
{ _commandCallback = callback; }
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
virtual void onMqttMessage(
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
const uint16_t length
|
||||
) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Publishes the MQTT message with the given state.
|
||||
*
|
||||
* @param state The state to publish.
|
||||
* @returns Returns `true` if the MQTT message has been published successfully.
|
||||
*/
|
||||
bool publishState(const bool state);
|
||||
|
||||
/// The device class. It can be nullptr.
|
||||
const char* _class;
|
||||
|
||||
/// The icon of the button. It can be nullptr.
|
||||
const char* _icon;
|
||||
|
||||
/// The retain flag for the HA commands.
|
||||
bool _retain;
|
||||
|
||||
/// The optimistic mode of the switch (`true` - enabled, `false` - disabled).
|
||||
bool _optimistic;
|
||||
|
||||
/// The current state of the switch. By default it's `false`.
|
||||
bool _currentState;
|
||||
|
||||
/// The callback that will be called when switch command is received from the HA.
|
||||
HASWITCH_CALLBACK(_commandCallback);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "HATagScanner.h"
|
||||
#ifndef EX_ARDUINOHA_TAG_SCANNER
|
||||
|
||||
#include "../HAMqtt.h"
|
||||
#include "../utils/HASerializer.h"
|
||||
|
||||
HATagScanner::HATagScanner(const char* uniqueId) :
|
||||
HABaseDeviceType(AHATOFSTR(HAComponentTag), uniqueId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool HATagScanner::tagScanned(const char* tag)
|
||||
{
|
||||
if (!tag || strlen(tag) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return publishOnDataTopic(AHATOFSTR(HATopic), tag);
|
||||
}
|
||||
|
||||
void HATagScanner::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_serializer = new HASerializer(this, 2); // 2 - max properties nb
|
||||
_serializer->set(HASerializer::WithDevice);
|
||||
_serializer->topic(AHATOFSTR(HATopic));
|
||||
}
|
||||
|
||||
void HATagScanner::onMqttConnected()
|
||||
{
|
||||
if (!uniqueId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef AHA_HATAGSCANNER_H
|
||||
#define AHA_HATAGSCANNER_H
|
||||
|
||||
#include "HABaseDeviceType.h"
|
||||
|
||||
#ifndef EX_ARDUINOHA_TAG_SCANNER
|
||||
|
||||
/**
|
||||
* HATagScanner allow to produce scan events that can be used in the HA automation.
|
||||
*
|
||||
* @note
|
||||
* You can find more information about this entity in the Home Assistant documentation:
|
||||
* https://www.home-assistant.io/integrations/tag.mqtt/
|
||||
*/
|
||||
class HATagScanner : public HABaseDeviceType
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param uniqueId The unique ID of the scanner. It needs to be unique in a scope of your device.
|
||||
*/
|
||||
HATagScanner(const char* uniqueId);
|
||||
|
||||
/**
|
||||
* Sends "tag scanned" event to the MQTT (Home Assistant).
|
||||
* Based on this event HA may perform user-defined automation.
|
||||
*
|
||||
* @param tag Value of the scanned tag.
|
||||
* @returns Returns `true` if MQTT message has been published successfully.
|
||||
*/
|
||||
bool tagScanned(const char* tag);
|
||||
|
||||
protected:
|
||||
virtual void buildSerializer() override;
|
||||
virtual void onMqttConnected() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,102 @@
|
||||
#ifndef AHA_AUNITHELPERS_H
|
||||
#define AHA_AUNITHELPERS_H
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
|
||||
#ifdef AUNITER
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(__AVR__)
|
||||
#include <MemoryUsage.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define initMqttTest(testDeviceId) \
|
||||
PubSubClientMock* mock = new PubSubClientMock(); \
|
||||
HADevice device(testDeviceId); \
|
||||
HAMqtt mqtt(mock, device); \
|
||||
mqtt.setDataPrefix("testData"); \
|
||||
mqtt.begin("testHost", "testUser", "testPass");
|
||||
|
||||
#define assertNoMqttMessage() \
|
||||
assertTrue(mock->getFlushedMessagesNb() == 0);
|
||||
|
||||
#define assertMqttMessage(index, eTopic, eMessage, eRetained) { \
|
||||
const __FlashStringHelper* messageP = F(eMessage); \
|
||||
size_t messageLen = strlen_P(reinterpret_cast<const char *>(messageP)); \
|
||||
assertTrue(mock->getFlushedMessagesNb() > 0); \
|
||||
assertTrue(mock->getFlushedMessagesNb() > index); \
|
||||
MqttMessage* publishedMessage = mock->getFlushedMessages()[index]; \
|
||||
assertEqual(eTopic, publishedMessage->topic); \
|
||||
assertEqual(messageP, publishedMessage->buffer); \
|
||||
assertEqual(messageLen, publishedMessage->bufferSize - 1); \
|
||||
assertEqual(static_cast<bool>(eRetained), publishedMessage->retained); \
|
||||
}
|
||||
|
||||
#define assertSingleMqttMessage(eTopic, eMessage, eRetained) { \
|
||||
assertEqual(1, mock->getFlushedMessagesNb()); \
|
||||
assertMqttMessage(0, eTopic, eMessage, eRetained) \
|
||||
}
|
||||
|
||||
#define assertEntityConfig(mock, entity, expectedJson) \
|
||||
{ \
|
||||
mqtt.loop(); \
|
||||
assertMqttMessage(0, AHATOFSTR(ConfigTopic), expectedJson, true) \
|
||||
assertTrue(entity.getSerializer() == nullptr); \
|
||||
}
|
||||
|
||||
#define assertEntityConfigOnTopic(mock, entity, topic, expectedJson) \
|
||||
{ \
|
||||
mqtt.loop(); \
|
||||
assertMqttMessage(0, topic, expectedJson, true) \
|
||||
assertTrue(entity.getSerializer() == nullptr); \
|
||||
}
|
||||
|
||||
#ifdef AUNITER
|
||||
|
||||
#if defined(__AVR__)
|
||||
#define AHA_FREERAM mu_freeRam()
|
||||
#elif defined(ESP8266) || defined(ESP32)
|
||||
#define AHA_FREERAM ESP.getFreeHeap()
|
||||
#else
|
||||
#define AHA_FREERAM 0
|
||||
#endif
|
||||
|
||||
#define AHA_LEAKTRACKSTART \
|
||||
int freeRam = AHA_FREERAM;
|
||||
|
||||
#define AHA_LEAKTRACKEND \
|
||||
int diff = freeRam - AHA_FREERAM; \
|
||||
if (diff < 0) { diff *= -1; } \
|
||||
if (diff != 0) { \
|
||||
Serial.print(Test::getName().getFString()); \
|
||||
Serial.print(F(" memory leak: ")); \
|
||||
Serial.print(diff); \
|
||||
Serial.println(F("b")); \
|
||||
Test::fail(); \
|
||||
}
|
||||
#else
|
||||
// EpoxyDuino doesn't support memory tracking
|
||||
#define AHA_LEAKTRACKSTART
|
||||
#define AHA_LEAKTRACKEND
|
||||
#endif
|
||||
|
||||
#define AHA_TEST(suiteName, name) \
|
||||
class suiteName##_##name : public aunit::TestOnce { \
|
||||
public: \
|
||||
suiteName##_##name(); \
|
||||
void once() override; \
|
||||
void loop() override { \
|
||||
AHA_LEAKTRACKSTART \
|
||||
once(); \
|
||||
if (isNotDone()) { pass(); } \
|
||||
AHA_LEAKTRACKEND \
|
||||
} \
|
||||
} suiteName##_##name##_instance; \
|
||||
suiteName##_##name :: suiteName##_##name() { \
|
||||
init(AUNIT_F(#suiteName "_" #name)); \
|
||||
} \
|
||||
void suiteName##_##name :: once()
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,271 @@
|
||||
#include "PubSubClientMock.h"
|
||||
#ifdef ARDUINOHA_TEST
|
||||
|
||||
#include "../ArduinoHADefines.h"
|
||||
|
||||
PubSubClientMock::PubSubClientMock() :
|
||||
_pendingMessage(nullptr),
|
||||
_flushedMessages(nullptr),
|
||||
_keepAlive(15),
|
||||
_bufferSize(256),
|
||||
_state(-1),
|
||||
_flushedMessagesNb(0),
|
||||
_subscriptions(nullptr),
|
||||
_subscriptionsNb(0),
|
||||
callback(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PubSubClientMock::~PubSubClientMock()
|
||||
{
|
||||
if (_pendingMessage) {
|
||||
delete _pendingMessage;
|
||||
}
|
||||
|
||||
clearFlushedMessages();
|
||||
clearSubscriptions();
|
||||
}
|
||||
|
||||
bool PubSubClientMock::loop()
|
||||
{
|
||||
return connected();
|
||||
}
|
||||
|
||||
void PubSubClientMock::disconnect()
|
||||
{
|
||||
_connection.connected = false;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::connected()
|
||||
{
|
||||
return _connection.connected;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::connect(
|
||||
const char *id,
|
||||
const char *user,
|
||||
const char *pass,
|
||||
const char* willTopic,
|
||||
uint8_t willQos,
|
||||
bool willRetain,
|
||||
const char* willMessage,
|
||||
bool cleanSession
|
||||
)
|
||||
{
|
||||
(void)willQos;
|
||||
(void)cleanSession;
|
||||
|
||||
_connection.connected = true;
|
||||
_connection.id = id;
|
||||
_connection.user = user;
|
||||
_connection.pass = pass;
|
||||
|
||||
_lastWill.topic = willTopic;
|
||||
_lastWill.message = willMessage;
|
||||
_lastWill.retain = willRetain;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::connectDummy()
|
||||
{
|
||||
_connection.connected = true;
|
||||
_connection.id = "dummyId";
|
||||
_connection.user = nullptr;
|
||||
_connection.pass = nullptr;
|
||||
|
||||
_lastWill.topic = nullptr;
|
||||
_lastWill.message = nullptr;
|
||||
_lastWill.retain = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PubSubClientMock& PubSubClientMock::setServer(IPAddress ip, uint16_t port)
|
||||
{
|
||||
_connection.ip = ip;
|
||||
_connection.port = port;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubSubClientMock& PubSubClientMock::setServer(
|
||||
const char * domain,
|
||||
uint16_t port
|
||||
)
|
||||
{
|
||||
_connection.domain = domain;
|
||||
_connection.port = port;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubSubClientMock& PubSubClientMock::setCallback(MQTT_CALLBACK_SIGNATURE)
|
||||
{
|
||||
this->callback = callback;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::beginPublish(
|
||||
const char* topic,
|
||||
unsigned int plength,
|
||||
bool retained
|
||||
)
|
||||
{
|
||||
if (!connected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_pendingMessage) {
|
||||
delete _pendingMessage;
|
||||
}
|
||||
|
||||
_pendingMessage = new MqttMessage();
|
||||
_pendingMessage->retained = retained;
|
||||
|
||||
{
|
||||
size_t size = strlen(topic) + 1;
|
||||
_pendingMessage->topic = new char[size];
|
||||
_pendingMessage->topicSize = size;
|
||||
|
||||
memset(_pendingMessage->topic, 0, size);
|
||||
memcpy(_pendingMessage->topic, topic, size);
|
||||
}
|
||||
|
||||
{
|
||||
size_t size = plength + 1;
|
||||
_pendingMessage->buffer = new char[size];
|
||||
_pendingMessage->bufferSize = size;
|
||||
|
||||
memset(_pendingMessage->buffer, 0, size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t PubSubClientMock::write(const uint8_t *buffer, size_t size)
|
||||
{
|
||||
if (!_pendingMessage || !_pendingMessage->buffer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
strncat(_pendingMessage->buffer, (const char*)buffer, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t PubSubClientMock::print(const __FlashStringHelper* buffer)
|
||||
{
|
||||
const size_t len = strlen_P(reinterpret_cast<const char*>(buffer));
|
||||
char data[len + 1]; // including null terminator
|
||||
strcpy_P(data, reinterpret_cast<const char*>(buffer));
|
||||
|
||||
return write((const uint8_t*)(data), len);
|
||||
}
|
||||
|
||||
int PubSubClientMock::endPublish()
|
||||
{
|
||||
if (!_pendingMessage) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t messageSize = _pendingMessage->bufferSize;
|
||||
uint8_t index = _flushedMessagesNb;
|
||||
|
||||
_flushedMessagesNb++;
|
||||
_flushedMessages = static_cast<MqttMessage**>(
|
||||
realloc(_flushedMessages, _flushedMessagesNb * sizeof(MqttMessage*))
|
||||
);
|
||||
|
||||
_flushedMessages[index] = _pendingMessage; // handover memory responsibility
|
||||
_pendingMessage = nullptr; // do not call destructor
|
||||
|
||||
return messageSize;
|
||||
}
|
||||
|
||||
bool PubSubClientMock::subscribe(const char* topic)
|
||||
{
|
||||
uint8_t index = _subscriptionsNb;
|
||||
|
||||
_subscriptionsNb++;
|
||||
_subscriptions = static_cast<MqttSubscription**>(
|
||||
realloc(_subscriptions, _subscriptionsNb * sizeof(MqttSubscription*))
|
||||
);
|
||||
|
||||
size_t topicSize = strlen(topic) + 1;
|
||||
MqttSubscription* subscription = new MqttSubscription();
|
||||
subscription->topic = new char[topicSize];
|
||||
memcpy(subscription->topic, topic, topicSize);
|
||||
|
||||
_subscriptions[index] = subscription;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PubSubClientMock::clearFlushedMessages()
|
||||
{
|
||||
if (_flushedMessages) {
|
||||
for (uint8_t i = 0; i < _flushedMessagesNb; i++) {
|
||||
delete _flushedMessages[i];
|
||||
}
|
||||
|
||||
delete _flushedMessages;
|
||||
}
|
||||
|
||||
_flushedMessagesNb = 0;
|
||||
}
|
||||
|
||||
void PubSubClientMock::clearSubscriptions()
|
||||
{
|
||||
if (_subscriptions) {
|
||||
for (uint8_t i = 0; i < _subscriptionsNb; i++) {
|
||||
delete _subscriptions[i];
|
||||
}
|
||||
|
||||
delete _subscriptions;
|
||||
}
|
||||
|
||||
_subscriptionsNb = 0;
|
||||
}
|
||||
|
||||
void PubSubClientMock::fakeMessage(const char* topic, const char* message)
|
||||
{
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t len = strlen(message);
|
||||
uint8_t data[len];
|
||||
memcpy(data, message, len);
|
||||
|
||||
callback(const_cast<char*>(topic), data, len);
|
||||
}
|
||||
|
||||
void PubSubClientMock::fakeMessage(
|
||||
const __FlashStringHelper* topic,
|
||||
const char* message
|
||||
)
|
||||
{
|
||||
char topicStr[strlen_P(AHAFROMFSTR(topic)) + 1];
|
||||
topicStr[0] = 0;
|
||||
strcpy_P(topicStr, AHAFROMFSTR(topic));
|
||||
|
||||
fakeMessage(topicStr, message);
|
||||
}
|
||||
|
||||
void PubSubClientMock::fakeMessage(
|
||||
const __FlashStringHelper* topic,
|
||||
const __FlashStringHelper* message
|
||||
)
|
||||
{
|
||||
char topicStr[strlen_P(AHAFROMFSTR(topic)) + 1];
|
||||
topicStr[0] = 0;
|
||||
strcpy_P(topicStr, AHAFROMFSTR(topic));
|
||||
|
||||
char messageStr[strlen_P(AHAFROMFSTR(message)) + 1];
|
||||
messageStr[0] = 0;
|
||||
strcpy_P(messageStr, AHAFROMFSTR(message));
|
||||
|
||||
fakeMessage(topicStr, messageStr);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,187 @@
|
||||
#ifndef AHA_PUBSUBCLIENTMOCK_H
|
||||
#define AHA_PUBSUBCLIENTMOCK_H
|
||||
|
||||
#ifdef ARDUINOHA_TEST
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
#include <functional>
|
||||
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
|
||||
#else
|
||||
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
|
||||
#endif
|
||||
|
||||
struct MqttMessage
|
||||
{
|
||||
char* topic;
|
||||
size_t topicSize;
|
||||
char* buffer;
|
||||
size_t bufferSize;
|
||||
bool retained;
|
||||
|
||||
MqttMessage() :
|
||||
topic(nullptr),
|
||||
topicSize(0),
|
||||
buffer(nullptr),
|
||||
bufferSize(0),
|
||||
retained(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~MqttMessage()
|
||||
{
|
||||
if (topic) {
|
||||
delete topic;
|
||||
}
|
||||
|
||||
if (buffer) {
|
||||
delete buffer;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct MqttSubscription {
|
||||
char* topic;
|
||||
|
||||
MqttSubscription() :
|
||||
topic(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~MqttSubscription()
|
||||
{
|
||||
if (topic) {
|
||||
delete topic;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct MqttConnection
|
||||
{
|
||||
bool connected;
|
||||
const char* domain;
|
||||
IPAddress ip;
|
||||
uint16_t port;
|
||||
const char* id;
|
||||
const char* user;
|
||||
const char* pass;
|
||||
|
||||
MqttConnection() :
|
||||
connected(false),
|
||||
domain(nullptr),
|
||||
port(0),
|
||||
id(nullptr),
|
||||
user(nullptr),
|
||||
pass(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
struct MqttWill
|
||||
{
|
||||
const char* topic;
|
||||
const char* message;
|
||||
bool retain;
|
||||
|
||||
MqttWill() :
|
||||
topic(nullptr),
|
||||
message(nullptr),
|
||||
retain(false)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
class PubSubClientMock
|
||||
{
|
||||
public:
|
||||
PubSubClientMock();
|
||||
~PubSubClientMock();
|
||||
|
||||
bool loop();
|
||||
void disconnect();
|
||||
bool connected();
|
||||
bool connect(
|
||||
const char *id,
|
||||
const char *user,
|
||||
const char *pass,
|
||||
const char* willTopic,
|
||||
uint8_t willQos,
|
||||
bool willRetain,
|
||||
const char* willMessage,
|
||||
bool cleanSession
|
||||
);
|
||||
bool connectDummy();
|
||||
PubSubClientMock& setServer(IPAddress ip, uint16_t port);
|
||||
PubSubClientMock& setServer(const char* domain, uint16_t port);
|
||||
PubSubClientMock& setCallback(MQTT_CALLBACK_SIGNATURE);
|
||||
|
||||
bool beginPublish(const char* topic, unsigned int plength, bool retained);
|
||||
size_t write(const uint8_t *buffer, size_t size);
|
||||
size_t print(const __FlashStringHelper* buffer);
|
||||
int endPublish();
|
||||
bool subscribe(const char* topic);
|
||||
|
||||
inline void setKeepAlive(uint16_t keepAlive)
|
||||
{ _keepAlive = keepAlive; }
|
||||
|
||||
inline uint16_t getKeepAlive() const
|
||||
{ return _keepAlive; }
|
||||
|
||||
inline bool setBufferSize(uint16_t bufferSize)
|
||||
{ _bufferSize = bufferSize; return true; }
|
||||
|
||||
inline uint16_t getBufferSize() const
|
||||
{ return _bufferSize; }
|
||||
|
||||
inline void setState(int16_t state)
|
||||
{ _state = state; }
|
||||
|
||||
inline int16_t state() const
|
||||
{ return _state; }
|
||||
|
||||
inline uint8_t getFlushedMessagesNb() const
|
||||
{ return _flushedMessagesNb; }
|
||||
|
||||
inline MqttMessage** getFlushedMessages() const
|
||||
{ return _flushedMessages; }
|
||||
|
||||
inline uint8_t getSubscriptionsNb() const
|
||||
{ return _subscriptionsNb; }
|
||||
|
||||
inline MqttSubscription** getSubscriptions() const
|
||||
{ return _subscriptions; }
|
||||
|
||||
inline const MqttConnection& getConnection() const
|
||||
{ return _connection; }
|
||||
|
||||
inline const MqttWill& getLastWill() const
|
||||
{ return _lastWill; }
|
||||
|
||||
void clearFlushedMessages();
|
||||
void clearSubscriptions();
|
||||
void fakeMessage(const char* topic, const char* message);
|
||||
void fakeMessage(const __FlashStringHelper* topic, const char* message);
|
||||
void fakeMessage(const __FlashStringHelper* topic, const __FlashStringHelper* message);
|
||||
|
||||
private:
|
||||
MqttMessage* _pendingMessage;
|
||||
MqttMessage** _flushedMessages;
|
||||
uint16_t _keepAlive;
|
||||
uint16_t _bufferSize;
|
||||
int16_t _state;
|
||||
uint8_t _flushedMessagesNb;
|
||||
MqttSubscription** _subscriptions;
|
||||
uint8_t _subscriptionsNb;
|
||||
MqttConnection _connection;
|
||||
MqttWill _lastWill;
|
||||
MQTT_CALLBACK_SIGNATURE;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,200 @@
|
||||
#include <Arduino.h>
|
||||
#include "HADictionary.h"
|
||||
|
||||
// components
|
||||
const char HAComponentBinarySensor[] PROGMEM = {"binary_sensor"};
|
||||
const char HAComponentButton[] PROGMEM = {"button"};
|
||||
const char HAComponentCamera[] PROGMEM = {"camera"};
|
||||
const char HAComponentCover[] PROGMEM = {"cover"};
|
||||
const char HAComponentDeviceTracker[] PROGMEM = {"device_tracker"};
|
||||
const char HAComponentDeviceAutomation[] PROGMEM = {"device_automation"};
|
||||
const char HAComponentLock[] PROGMEM = {"lock"};
|
||||
const char HAComponentNumber[] PROGMEM = {"number"};
|
||||
const char HAComponentSelect[] PROGMEM = {"select"};
|
||||
const char HAComponentSensor[] PROGMEM = {"sensor"};
|
||||
const char HAComponentSwitch[] PROGMEM = {"switch"};
|
||||
const char HAComponentTag[] PROGMEM = {"tag"};
|
||||
const char HAComponentScene[] PROGMEM = {"scene"};
|
||||
const char HAComponentFan[] PROGMEM = {"fan"};
|
||||
const char HAComponentLight[] PROGMEM = {"light"};
|
||||
const char HAComponentClimate[] PROGMEM = {"climate"};
|
||||
|
||||
// decorators
|
||||
const char HASerializerSlash[] PROGMEM = {"/"};
|
||||
const char HASerializerJsonDataPrefix[] PROGMEM = {"{"};
|
||||
const char HASerializerJsonDataSuffix[] PROGMEM = {"}"};
|
||||
const char HASerializerJsonPropertyPrefix[] PROGMEM = {"\""};
|
||||
const char HASerializerJsonPropertySuffix[] PROGMEM = {"\":"};
|
||||
const char HASerializerJsonEscapeChar[] PROGMEM = {"\""};
|
||||
const char HASerializerJsonPropertiesSeparator[] PROGMEM = {","};
|
||||
const char HASerializerJsonArrayPrefix[] PROGMEM = {"["};
|
||||
const char HASerializerJsonArraySuffix[] PROGMEM = {"]"};
|
||||
const char HASerializerUnderscore[] PROGMEM = {"_"};
|
||||
|
||||
// properties
|
||||
const char HADeviceIdentifiersProperty[] PROGMEM = {"ids"};
|
||||
const char HADeviceManufacturerProperty[] PROGMEM = {"mf"};
|
||||
const char HADeviceModelProperty[] PROGMEM = {"mdl"};
|
||||
const char HADeviceSoftwareVersionProperty[] PROGMEM = {"sw"};
|
||||
const char HADeviceConfigurationUrlProperty[] PROGMEM = {"cu"};
|
||||
const char HANameProperty[] PROGMEM = {"name"};
|
||||
const char HAUniqueIdProperty[] PROGMEM = {"uniq_id"};
|
||||
const char HAObjectIdProperty[] PROGMEM = {"obj_id"};
|
||||
const char HADeviceProperty[] PROGMEM = {"dev"};
|
||||
const char HADeviceClassProperty[] PROGMEM = {"dev_cla"};
|
||||
const char HAStateClassProperty[] PROGMEM = {"stat_cla"};
|
||||
const char HAIconProperty[] PROGMEM = {"ic"};
|
||||
const char HARetainProperty[] PROGMEM = {"ret"};
|
||||
const char HASourceTypeProperty[] PROGMEM = {"src_type"};
|
||||
const char HAEncodingProperty[] PROGMEM = {"e"};
|
||||
const char HAOptimisticProperty[] PROGMEM = {"opt"};
|
||||
const char HAAutomationTypeProperty[] PROGMEM = {"atype"};
|
||||
const char HATypeProperty[] PROGMEM = {"type"};
|
||||
const char HASubtypeProperty[] PROGMEM = {"stype"};
|
||||
const char HAForceUpdateProperty[] PROGMEM = {"frc_upd"};
|
||||
const char HAUnitOfMeasurementProperty[] PROGMEM = {"unit_of_meas"};
|
||||
const char HAValueTemplateProperty[] PROGMEM = {"val_tpl"};
|
||||
const char HAOptionsProperty[] PROGMEM = {"options"};
|
||||
const char HAMinProperty[] PROGMEM = {"min"};
|
||||
const char HAMaxProperty[] PROGMEM = {"max"};
|
||||
const char HAStepProperty[] PROGMEM = {"step"};
|
||||
const char HAModeProperty[] PROGMEM = {"mode"};
|
||||
const char HACommandTemplateProperty[] PROGMEM = {"cmd_tpl"};
|
||||
const char HASpeedRangeMaxProperty[] PROGMEM = {"spd_rng_max"};
|
||||
const char HASpeedRangeMinProperty[] PROGMEM = {"spd_rng_min"};
|
||||
const char HABrightnessScaleProperty[] PROGMEM = {"bri_scl"};
|
||||
const char HAMinMiredsProperty[] PROGMEM = {"min_mirs"};
|
||||
const char HAMaxMiredsProperty[] PROGMEM = {"max_mirs"};
|
||||
const char HATemperatureUnitProperty[] PROGMEM = {"temp_unit"};
|
||||
const char HAMinTempProperty[] PROGMEM = {"min_temp"};
|
||||
const char HAMaxTempProperty[] PROGMEM = {"max_temp"};
|
||||
const char HATempStepProperty[] PROGMEM = {"temp_step"};
|
||||
const char HAFanModesProperty[] PROGMEM = {"fan_modes"};
|
||||
const char HASwingModesProperty[] PROGMEM = {"swing_modes"};
|
||||
const char HAModesProperty[] PROGMEM = {"modes"};
|
||||
const char HATemperatureCommandTemplateProperty[] PROGMEM = {"temp_cmd_tpl"};
|
||||
const char HAPayloadOnProperty[] PROGMEM = {"pl_on"};
|
||||
const char HAExpireAfterProperty[] PROGMEM = {"exp_aft"};
|
||||
|
||||
// topics
|
||||
const char HAConfigTopic[] PROGMEM = {"config"};
|
||||
const char HAAvailabilityTopic[] PROGMEM = {"avty_t"};
|
||||
const char HATopic[] PROGMEM = {"t"};
|
||||
const char HAStateTopic[] PROGMEM = {"stat_t"};
|
||||
const char HACommandTopic[] PROGMEM = {"cmd_t"};
|
||||
const char HAPositionTopic[] PROGMEM = {"pos_t"};
|
||||
const char HAPercentageStateTopic[] PROGMEM = {"pct_stat_t"};
|
||||
const char HAPercentageCommandTopic[] PROGMEM = {"pct_cmd_t"};
|
||||
const char HABrightnessCommandTopic[] PROGMEM = {"bri_cmd_t"};
|
||||
const char HABrightnessStateTopic[] PROGMEM = {"bri_stat_t"};
|
||||
const char HAColorTemperatureCommandTopic[] PROGMEM = {"clr_temp_cmd_t"};
|
||||
const char HAColorTemperatureStateTopic[] PROGMEM = {"clr_temp_stat_t"};
|
||||
const char HACurrentTemperatureTopic[] PROGMEM = {"curr_temp_t"};
|
||||
const char HAActionTopic[] PROGMEM = {"act_t"};
|
||||
const char HAAuxCommandTopic[] PROGMEM = {"aux_cmd_t"};
|
||||
const char HAAuxStateTopic[] PROGMEM = {"aux_stat_t"};
|
||||
const char HAPowerCommandTopic[] PROGMEM = {"pow_cmd_t"};
|
||||
const char HAFanModeCommandTopic[] PROGMEM = {"fan_mode_cmd_t"};
|
||||
const char HAFanModeStateTopic[] PROGMEM = {"fan_mode_stat_t"};
|
||||
const char HASwingModeCommandTopic[] PROGMEM = {"swing_mode_cmd_t"};
|
||||
const char HASwingModeStateTopic[] PROGMEM = {"swing_mode_stat_t"};
|
||||
const char HAModeCommandTopic[] PROGMEM = {"mode_cmd_t"};
|
||||
const char HAModeStateTopic[] PROGMEM = {"mode_stat_t"};
|
||||
const char HATemperatureCommandTopic[] PROGMEM = {"temp_cmd_t"};
|
||||
const char HATemperatureStateTopic[] PROGMEM = {"temp_stat_t"};
|
||||
const char HARGBCommandTopic[] PROGMEM = {"rgb_cmd_t"};
|
||||
const char HARGBStateTopic[] PROGMEM = {"rgb_stat_t"};
|
||||
const char HAJsonAttributesTopic[] PROGMEM = {"json_attr_t"};
|
||||
|
||||
// misc
|
||||
const char HAOnline[] PROGMEM = {"online"};
|
||||
const char HAOffline[] PROGMEM = {"offline"};
|
||||
const char HAStateOn[] PROGMEM = {"ON"};
|
||||
const char HAStateOff[] PROGMEM = {"OFF"};
|
||||
const char HAStateLocked[] PROGMEM = {"LOCKED"};
|
||||
const char HAStateUnlocked[] PROGMEM = {"UNLOCKED"};
|
||||
const char HAStateNone[] PROGMEM = {"None"};
|
||||
const char HATrue[] PROGMEM = {"true"};
|
||||
const char HAFalse[] PROGMEM = {"false"};
|
||||
const char HAHome[] PROGMEM = {"home"};
|
||||
const char HANotHome[] PROGMEM = {"not_home"};
|
||||
const char HATrigger[] PROGMEM = {"trigger"};
|
||||
const char HAModeBox[] PROGMEM = {"box"};
|
||||
const char HAModeSlider[] PROGMEM = {"slider"};
|
||||
|
||||
// covers
|
||||
const char HAClosedState[] PROGMEM = {"closed"};
|
||||
const char HAClosingState[] PROGMEM = {"closing"};
|
||||
const char HAOpenState[] PROGMEM = {"open"};
|
||||
const char HAOpeningState[] PROGMEM = {"opening"};
|
||||
const char HAStoppedState[] PROGMEM = {"stopped"};
|
||||
|
||||
// commands
|
||||
const char HAOpenCommand[] PROGMEM = {"OPEN"};
|
||||
const char HACloseCommand[] PROGMEM = {"CLOSE"};
|
||||
const char HAStopCommand[] PROGMEM = {"STOP"};
|
||||
const char HALockCommand[] PROGMEM = {"LOCK"};
|
||||
const char HAUnlockCommand[] PROGMEM = {"UNLOCK"};
|
||||
|
||||
// device tracker
|
||||
const char HAGPSType[] PROGMEM = {"gps"};
|
||||
const char HARouterType[] PROGMEM = {"router"};
|
||||
const char HABluetoothType[] PROGMEM = {"bluetooth"};
|
||||
const char HABluetoothLEType[] PROGMEM = {"bluetooth_le"};
|
||||
|
||||
// camera
|
||||
const char HAEncodingBase64[] PROGMEM = {"b64"};
|
||||
|
||||
// trigger
|
||||
const char HAButtonShortPressType[] PROGMEM = {"button_short_press"};
|
||||
const char HAButtonShortReleaseType[] PROGMEM = {"button_short_release"};
|
||||
const char HAButtonLongPressType[] PROGMEM = {"button_long_press"};
|
||||
const char HAButtonLongReleaseType[] PROGMEM = {"button_long_release"};
|
||||
const char HAButtonDoublePressType[] PROGMEM = {"button_double_press"};
|
||||
const char HAButtonTriplePressType[] PROGMEM = {"button_triple_press"};
|
||||
const char HAButtonQuadruplePressType[] PROGMEM = {"button_quadruple_press"};
|
||||
const char HAButtonQuintuplePressType[] PROGMEM = {"button_quintuple_press"};
|
||||
const char HATurnOnSubtype[] PROGMEM = {"turn_on"};
|
||||
const char HATurnOffSubtype[] PROGMEM = {"turn_off"};
|
||||
const char HAButton1Subtype[] PROGMEM = {"button_1"};
|
||||
const char HAButton2Subtype[] PROGMEM = {"button_2"};
|
||||
const char HAButton3Subtype[] PROGMEM = {"button_3"};
|
||||
const char HAButton4Subtype[] PROGMEM = {"button_4"};
|
||||
const char HAButton5Subtype[] PROGMEM = {"button_5"};
|
||||
const char HAButton6Subtype[] PROGMEM = {"button_6"};
|
||||
|
||||
// actions
|
||||
const char HAActionOff[] PROGMEM = {"off"};
|
||||
const char HAActionHeating[] PROGMEM = {"heating"};
|
||||
const char HAActionCooling[] PROGMEM = {"cooling"};
|
||||
const char HAActionDrying[] PROGMEM = {"drying"};
|
||||
const char HAActionIdle[] PROGMEM = {"idle"};
|
||||
const char HAActionFan[] PROGMEM = {"fan"};
|
||||
|
||||
// fan modes
|
||||
const char HAFanModeAuto[] PROGMEM = {"auto"};
|
||||
const char HAFanModeLow[] PROGMEM = {"low"};
|
||||
const char HAFanModeMedium[] PROGMEM = {"medium"};
|
||||
const char HAFanModeHigh[] PROGMEM = {"high"};
|
||||
|
||||
// swing modes
|
||||
const char HASwingModeOn[] PROGMEM = {"on"};
|
||||
const char HASwingModeOff[] PROGMEM = {"off"};
|
||||
|
||||
// HVAC modes
|
||||
const char HAModeAuto[] PROGMEM = {"auto"};
|
||||
const char HAModeOff[] PROGMEM = {"off"};
|
||||
const char HAModeCool[] PROGMEM = {"cool"};
|
||||
const char HAModeHeat[] PROGMEM = {"heat"};
|
||||
const char HAModeDry[] PROGMEM = {"dry"};
|
||||
const char HAModeFanOnly[] PROGMEM = {"fan_only"};
|
||||
|
||||
// other
|
||||
const char HAHexMap[] PROGMEM = {"0123456789abcdef"};
|
||||
|
||||
// value templates
|
||||
const char HAValueTemplateFloatP1[] PROGMEM = {"{{int(float(value)*10**1)}}"};
|
||||
const char HAValueTemplateFloatP2[] PROGMEM = {"{{int(float(value)*10**2)}}"};
|
||||
const char HAValueTemplateFloatP3[] PROGMEM = {"{{int(float(value)*10**3)}}"};
|
||||
const char HATemperatureUnitC[] PROGMEM = {"C"};
|
||||
const char HATemperatureUnitF[] PROGMEM = {"F"};
|
||||
@@ -0,0 +1,202 @@
|
||||
#ifndef AHA_HADICTIONARY_H
|
||||
#define AHA_HADICTIONARY_H
|
||||
|
||||
// components
|
||||
extern const char HAComponentBinarySensor[];
|
||||
extern const char HAComponentButton[];
|
||||
extern const char HAComponentCamera[];
|
||||
extern const char HAComponentCover[];
|
||||
extern const char HAComponentDeviceTracker[];
|
||||
extern const char HAComponentDeviceAutomation[];
|
||||
extern const char HAComponentLock[];
|
||||
extern const char HAComponentNumber[];
|
||||
extern const char HAComponentSelect[];
|
||||
extern const char HAComponentSensor[];
|
||||
extern const char HAComponentSwitch[];
|
||||
extern const char HAComponentTag[];
|
||||
extern const char HAComponentScene[];
|
||||
extern const char HAComponentFan[];
|
||||
extern const char HAComponentLight[];
|
||||
extern const char HAComponentClimate[];
|
||||
|
||||
// decorators
|
||||
extern const char HASerializerSlash[];
|
||||
extern const char HASerializerJsonDataPrefix[];
|
||||
extern const char HASerializerJsonDataSuffix[];
|
||||
extern const char HASerializerJsonPropertyPrefix[];
|
||||
extern const char HASerializerJsonPropertySuffix[];
|
||||
extern const char HASerializerJsonEscapeChar[];
|
||||
extern const char HASerializerJsonPropertiesSeparator[];
|
||||
extern const char HASerializerJsonArrayPrefix[];
|
||||
extern const char HASerializerJsonArraySuffix[];
|
||||
extern const char HASerializerUnderscore[];
|
||||
|
||||
// properties
|
||||
extern const char HADeviceIdentifiersProperty[];
|
||||
extern const char HADeviceManufacturerProperty[];
|
||||
extern const char HADeviceModelProperty[];
|
||||
extern const char HADeviceSoftwareVersionProperty[];
|
||||
extern const char HADeviceConfigurationUrlProperty[];
|
||||
extern const char HANameProperty[];
|
||||
extern const char HAUniqueIdProperty[];
|
||||
extern const char HAObjectIdProperty[];
|
||||
extern const char HADeviceProperty[];
|
||||
extern const char HADeviceClassProperty[];
|
||||
extern const char HAStateClassProperty[];
|
||||
extern const char HAIconProperty[];
|
||||
extern const char HARetainProperty[];
|
||||
extern const char HASourceTypeProperty[];
|
||||
extern const char HAEncodingProperty[];
|
||||
extern const char HAOptimisticProperty[];
|
||||
extern const char HAAutomationTypeProperty[];
|
||||
extern const char HATypeProperty[];
|
||||
extern const char HASubtypeProperty[];
|
||||
extern const char HAForceUpdateProperty[];
|
||||
extern const char HAUnitOfMeasurementProperty[];
|
||||
extern const char HAValueTemplateProperty[];
|
||||
extern const char HAOptionsProperty[];
|
||||
extern const char HAMinProperty[];
|
||||
extern const char HAMaxProperty[];
|
||||
extern const char HAStepProperty[];
|
||||
extern const char HAModeProperty[];
|
||||
extern const char HACommandTemplateProperty[];
|
||||
extern const char HASpeedRangeMaxProperty[];
|
||||
extern const char HASpeedRangeMinProperty[];
|
||||
extern const char HABrightnessScaleProperty[];
|
||||
extern const char HAMinMiredsProperty[];
|
||||
extern const char HAMaxMiredsProperty[];
|
||||
extern const char HATemperatureUnitProperty[];
|
||||
extern const char HAMinTempProperty[];
|
||||
extern const char HAMaxTempProperty[];
|
||||
extern const char HATempStepProperty[];
|
||||
extern const char HAFanModesProperty[];
|
||||
extern const char HASwingModesProperty[];
|
||||
extern const char HAModesProperty[];
|
||||
extern const char HATemperatureCommandTemplateProperty[];
|
||||
extern const char HAPayloadOnProperty[];
|
||||
extern const char HAExpireAfterProperty[];
|
||||
|
||||
// topics
|
||||
extern const char HAConfigTopic[];
|
||||
extern const char HAAvailabilityTopic[];
|
||||
extern const char HATopic[];
|
||||
extern const char HAStateTopic[];
|
||||
extern const char HACommandTopic[];
|
||||
extern const char HAPositionTopic[];
|
||||
extern const char HAPercentageStateTopic[];
|
||||
extern const char HAPercentageCommandTopic[];
|
||||
extern const char HABrightnessCommandTopic[];
|
||||
extern const char HABrightnessStateTopic[];
|
||||
extern const char HAColorTemperatureCommandTopic[];
|
||||
extern const char HAColorTemperatureStateTopic[];
|
||||
extern const char HACurrentTemperatureTopic[];
|
||||
extern const char HAActionTopic[];
|
||||
extern const char HAAuxCommandTopic[];
|
||||
extern const char HAAuxStateTopic[];
|
||||
extern const char HAPowerCommandTopic[];
|
||||
extern const char HAFanModeCommandTopic[];
|
||||
extern const char HAFanModeStateTopic[];
|
||||
extern const char HASwingModeCommandTopic[];
|
||||
extern const char HASwingModeStateTopic[];
|
||||
extern const char HAModeCommandTopic[];
|
||||
extern const char HAModeStateTopic[];
|
||||
extern const char HATemperatureCommandTopic[];
|
||||
extern const char HATemperatureStateTopic[];
|
||||
extern const char HARGBCommandTopic[];
|
||||
extern const char HARGBStateTopic[];
|
||||
extern const char HAJsonAttributesTopic[];
|
||||
|
||||
// misc
|
||||
extern const char HAOnline[];
|
||||
extern const char HAOffline[];
|
||||
extern const char HAStateOn[];
|
||||
extern const char HAStateOff[];
|
||||
extern const char HAStateLocked[];
|
||||
extern const char HAStateUnlocked[];
|
||||
extern const char HAStateNone[];
|
||||
extern const char HATrue[];
|
||||
extern const char HAFalse[];
|
||||
extern const char HAHome[];
|
||||
extern const char HANotHome[];
|
||||
extern const char HATrigger[];
|
||||
extern const char HAModeBox[];
|
||||
extern const char HAModeSlider[];
|
||||
|
||||
// covers
|
||||
extern const char HAClosedState[];
|
||||
extern const char HAClosingState[];
|
||||
extern const char HAOpenState[];
|
||||
extern const char HAOpeningState[];
|
||||
extern const char HAStoppedState[];
|
||||
|
||||
// commands
|
||||
extern const char HAOpenCommand[];
|
||||
extern const char HACloseCommand[];
|
||||
extern const char HAStopCommand[];
|
||||
extern const char HALockCommand[];
|
||||
extern const char HAUnlockCommand[];
|
||||
|
||||
// device tracker
|
||||
extern const char HAGPSType[];
|
||||
extern const char HARouterType[];
|
||||
extern const char HABluetoothType[];
|
||||
extern const char HABluetoothLEType[];
|
||||
|
||||
// camera
|
||||
extern const char HAEncodingBase64[];
|
||||
|
||||
// trigger
|
||||
extern const char HAButtonShortPressType[];
|
||||
extern const char HAButtonShortReleaseType[];
|
||||
extern const char HAButtonLongPressType[];
|
||||
extern const char HAButtonLongReleaseType[];
|
||||
extern const char HAButtonDoublePressType[];
|
||||
extern const char HAButtonTriplePressType[];
|
||||
extern const char HAButtonQuadruplePressType[];
|
||||
extern const char HAButtonQuintuplePressType[];
|
||||
extern const char HATurnOnSubtype[];
|
||||
extern const char HATurnOffSubtype[];
|
||||
extern const char HAButton1Subtype[];
|
||||
extern const char HAButton2Subtype[];
|
||||
extern const char HAButton3Subtype[];
|
||||
extern const char HAButton4Subtype[];
|
||||
extern const char HAButton5Subtype[];
|
||||
extern const char HAButton6Subtype[];
|
||||
|
||||
// actions
|
||||
extern const char HAActionOff[];
|
||||
extern const char HAActionHeating[];
|
||||
extern const char HAActionCooling[];
|
||||
extern const char HAActionDrying[];
|
||||
extern const char HAActionIdle[];
|
||||
extern const char HAActionFan[];
|
||||
|
||||
// fan modes
|
||||
extern const char HAFanModeAuto[];
|
||||
extern const char HAFanModeLow[];
|
||||
extern const char HAFanModeMedium[];
|
||||
extern const char HAFanModeHigh[];
|
||||
|
||||
// swing modes
|
||||
extern const char HASwingModeOn[];
|
||||
extern const char HASwingModeOff[];
|
||||
|
||||
// HVAC modes
|
||||
extern const char HAModeAuto[];
|
||||
extern const char HAModeOff[];
|
||||
extern const char HAModeCool[];
|
||||
extern const char HAModeHeat[];
|
||||
extern const char HAModeDry[];
|
||||
extern const char HAModeFanOnly[];
|
||||
|
||||
// other
|
||||
extern const char HAHexMap[];
|
||||
|
||||
// value templates
|
||||
extern const char HAValueTemplateFloatP1[];
|
||||
extern const char HAValueTemplateFloatP2[];
|
||||
extern const char HAValueTemplateFloatP3[];
|
||||
extern const char HATemperatureUnitC[];
|
||||
extern const char HATemperatureUnitF[];
|
||||
|
||||
#endif
|
||||
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user