From 38e2fc6812b58a5f2d0894cc45779e92eff13096 Mon Sep 17 00:00:00 2001 From: Steven Dashevsky Date: Thu, 20 Oct 2022 02:07:32 +0300 Subject: [PATCH] Implemented usermod for integration with smartnest.cz (#2800) Co-authored-by: Christian Schwinne --- usermods/smartnest/readme.md | 61 +++++++++ usermods/smartnest/usermod_smartnest.h | 167 +++++++++++++++++++++++++ wled00/const.h | 3 +- wled00/mqtt.cpp | 4 + wled00/usermods_list.cpp | 8 ++ 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 usermods/smartnest/readme.md create mode 100644 usermods/smartnest/usermod_smartnest.h diff --git a/usermods/smartnest/readme.md b/usermods/smartnest/readme.md new file mode 100644 index 00000000..c56f3621 --- /dev/null +++ b/usermods/smartnest/readme.md @@ -0,0 +1,61 @@ +# Smartnest + +This usermod-v2 modification allows integration with `smartnest.cz` service which provides MQTT integration with voice assistants. +In order to setup Smartnest follow the [documentation](https://www.docu.smartnest.cz/). + +## MQTT API + +The API is described in the Smartnest [Github repo](https://github.com/aososam/Smartnest/blob/master/Devices/lightRgb/lightRgb.ino). + + +## Usermod installation + +1. Register the usermod by adding `#include "../usermods/smartnest/usermod_smartnest.h"` at the top and `usermods.add(new Smartnest());` at the bottom of `usermods_list.cpp`. +or +2. Use `#define USERMOD_SMARTNEST` in wled.h or `-D USERMOD_SMARTNEST` in your platformio.ini + + +Example **usermods_list.cpp**: + +```cpp +#include "wled.h" +/* + * Register your v2 usermods here! + * (for v1 usermods using just usermod.cpp, you can ignore this file) + */ + +/* + * Add/uncomment your usermod filename here (and once more below) + * || || || + * \/ \/ \/ + */ +//#include "usermod_v2_example.h" +//#include "usermod_temperature.h" +#include "../usermods/usermod_smartnest.h" + +void registerUsermods() +{ + /* + * Add your usermod class name here + * || || || + * \/ \/ \/ + */ + //usermods.add(new MyExampleUsermod()); + //usermods.add(new UsermodTemperature()); + usermods.add(new Smartnest()); + +} +``` + +## Configuration + +Usermod has no configuration but relies on the MQTT configuration.\ +Under Config > Sync Interfaces > MQTT: +* Enable MQTT check box +* Set the `Broker` field to: `smartnest.cz` +* The `Username` and `Password` fields are the login information from the `smartnest.cz` website. +* `Client ID` field is obtained from the device configuration panel in `smartnest.cz`. + +## Change log +2022-09 +* First implementation. diff --git a/usermods/smartnest/usermod_smartnest.h b/usermods/smartnest/usermod_smartnest.h new file mode 100644 index 00000000..82673578 --- /dev/null +++ b/usermods/smartnest/usermod_smartnest.h @@ -0,0 +1,167 @@ +#pragma once + +#include "wled.h" + +class Smartnest : public Usermod +{ +private: + void sendToBroker(const char *const topic, const char *const message) + { + if (!WLED_MQTT_CONNECTED) + { + return; + } + + String topic_ = String(mqttClientID) + "/" + String(topic); + mqtt->publish(topic_.c_str(), 0, true, message); + } + + void turnOff() + { + setBrightness(0); + turnOnAtBoot = false; + offMode = true; + sendToBroker("report/powerState", "OFF"); + } + + void turnOn() + { + setBrightness(briLast); + turnOnAtBoot = true; + offMode = false; + sendToBroker("report/powerState", "ON"); + } + + void setBrightness(int value) + { + if (value == 0 && bri > 0) briLast = bri; + bri = value; + stateUpdated(CALL_MODE_DIRECT_CHANGE); + } + + void setColor(int r, int g, int b) + { + strip.setColor(0, r, g, b); + stateUpdated(CALL_MODE_DIRECT_CHANGE); + char msg[18] {}; + sprintf(msg, "rgb(%d,%d,%d)", r, g, b); + sendToBroker("report/color", msg); + } + + int splitColor(const char *const color, int * const rgb) + { + char *color_ = NULL; + const char delim[] = ","; + char *cxt = NULL; + char *token = NULL; + int position = 0; + + // We need to copy the string in order to keep it read only as strtok_r function requires mutable string + color_ = (char *)malloc(strlen(color)); + if (NULL == color_) { + return -1; + } + + strcpy(color_, color); + token = strtok_r(color_, delim, &cxt); + + while (token != NULL) + { + rgb[position++] = (int)strtoul(token, NULL, 10); + token = strtok_r(NULL, delim, &cxt); + } + free(color_); + + return position; + } + +public: + // Functions called by WLED + + /** + * handling of MQTT message + * topic should look like: /// + */ + bool onMqttMessage(char *topic, char *message) + { + String topic_{topic}; + String topic_prefix{mqttClientID + String("/directive/")}; + + if (!topic_.startsWith(topic_prefix)) + { + return false; + } + + String subtopic = topic_.substring(topic_prefix.length()); + String message_(message); + + if (subtopic == "powerState") + { + if (strcmp(message, "ON") == 0) + { + turnOn(); + } + else if (strcmp(message, "OFF") == 0) + { + turnOff(); + } + return true; + } + + if (subtopic == "percentage") + { + int val = (int)strtoul(message, NULL, 10); + if (val >= 0 && val <= 100) + { + setBrightness(map(val, 0, 100, 0, 255)); + } + return true; + } + + if (subtopic == "color") + { + // Parse the message which is in the format "rgb(<0-255>,<0-255>,<0-255>)" + int rgb[3] = {}; + String colors = message_.substring(String("rgb(").length(), message_.lastIndexOf(')')); + if (3 != splitColor(colors.c_str(), rgb)) + { + return false; + } + setColor(rgb[0], rgb[1], rgb[2]); + return true; + } + + return false; + } + + /** + * subscribe to MQTT topic and send publish current status. + */ + void onMqttConnect(bool sessionPresent) + { + String topic = String(mqttClientID) + "/#"; + + mqtt->subscribe(topic.c_str(), 0); + sendToBroker("report/online", (bri ? "true" : "false")); // Reports that the device is online + delay(100); + sendToBroker("report/firmware", versionString); // Reports the firmware version + delay(100); + sendToBroker("report/ip", (char *)WiFi.localIP().toString().c_str()); // Reports the ip + delay(100); + sendToBroker("report/network", (char *)WiFi.SSID().c_str()); // Reports the network name + delay(100); + + String signal(WiFi.RSSI(), 10); + sendToBroker("report/signal", signal.c_str()); // Reports the signal strength + delay(100); + } + + /** + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() + { + return USERMOD_ID_SMARTNEST; + } +}; diff --git a/wled00/const.h b/wled00/const.h index 0c3905cf..99c929c7 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -91,7 +91,8 @@ #define USERMOD_ID_MY9291 28 //Usermod "usermod_MY9291.h" #define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h" #define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h -#define USERMOD_ID_AUDIOREACTIVE 31 //Usermod "audioreactive.h" +#define USERMOD_ID_SMARTNEST 31 //Usermod "usermod_smartnest.h" +#define USERMOD_ID_AUDIOREACTIVE 32 //Usermod "audioreactive.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index e53a14e7..1690341b 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -117,6 +117,7 @@ void publishMqtt() if (!WLED_MQTT_CONNECTED) return; DEBUG_PRINTLN(F("Publish MQTT")); + #ifndef USERMOD_SMARTNEST char s[10]; char subuf[38]; @@ -139,6 +140,7 @@ void publishMqtt() strlcpy(subuf, mqttDeviceTopic, 33); strcat_P(subuf, PSTR("/v")); mqtt->publish(subuf, 0, false, apires); // do not retain message + #endif } @@ -166,9 +168,11 @@ bool initMqtt() mqtt->setClientId(mqttClientID); if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); + #ifndef USERMOD_SMARTNEST strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); strcat_P(mqttStatusTopic, PSTR("/status")); mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message + #endif mqtt->setKeepAlive(MQTT_KEEP_ALIVE_TIME); mqtt->connect(); return true; diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index d63730d4..b3a6a007 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -132,6 +132,10 @@ #include "../usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h" #endif +#ifdef USERMOD_SMARTNEST +#include "../usermods/smartnest/usermod_smartnest.h" +#endif + #ifdef USERMOD_AUDIOREACTIVE #include "../usermods/audioreactive/audio_reactive.h" #endif @@ -256,6 +260,10 @@ void registerUsermods() usermods.add(new Si7021_MQTT_HA()); #endif + #ifdef USERMOD_SMARTNEST + usermods.add(new Smartnest()); + #endif + #ifdef USERMOD_AUDIOREACTIVE usermods.add(new AudioReactive()); #endif