diff --git a/Arduino/McLighting/McLighting.ino b/Arduino/McLighting/McLighting.ino index 42f9831..708b4fd 100644 --- a/Arduino/McLighting/McLighting.ino +++ b/Arduino/McLighting/McLighting.ino @@ -8,7 +8,7 @@ // needed for library WiFiManager #include #include -#include //https://github.com/tzapu/WiFiManager +#include //https://github.com/tzapu/WiFiManager #include #include @@ -35,6 +35,18 @@ PubSubClient mqtt_client(espClient); #endif +#ifdef ENABLE_AMQTT + #include //https://github.com/marvinroger/async-mqtt-client + //https://github.com/me-no-dev/ESPAsyncTCP + #ifdef ENABLE_HOMEASSISTANT + #include + #endif + + AsyncMqttClient amqttClient; + WiFiEventHandler wifiConnectHandler; + WiFiEventHandler wifiDisconnectHandler; +#endif + // *************************************************************************** // Instanciate HTTP(80) / WebSockets(81) Server @@ -43,10 +55,29 @@ ESP8266WebServer server(80); WebSocketsServer webSocket = WebSocketsServer(81); #ifdef HTTP_OTA - #include - ESP8266HTTPUpdateServer httpUpdater; +#include +ESP8266HTTPUpdateServer httpUpdater; #endif +#ifdef USE_NEOANIMATIONFX +// *************************************************************************** +// Load libraries / Instanciate NeoAnimationFX library +// *************************************************************************** +// https://github.com/debsahu/NeoAnimationFX +#include +#define NEOMETHOD NeoPBBGRB800 + +NEOMETHOD neoStrip(NUMLEDS); +NeoAnimationFX strip(neoStrip); + +// Uses Pin RX / GPIO3 (Only pin that is supported, due to hardware limitations) +// NEOMETHOD NeoPBBGRB800 uses GRB config 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEOMETHOD NeoPBBGRB400 uses GRB config 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEOMETHOD NeoPBBRGB800 uses RGB config 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEOMETHOD NeoPBBRGB400 uses RGB config 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +#endif + +#ifdef USE_WS2812FX // *************************************************************************** // Load libraries / Instanciate WS2812FX library // *************************************************************************** @@ -66,14 +97,23 @@ WS2812FX strip = WS2812FX(NUMLEDS, PIN, NEO_GRB + NEO_KHZ800); // pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input // and minimize distance between Arduino and first pixel. Avoid connecting // on a live circuit...if you must, connect GND first. - +#endif // *************************************************************************** // Load library "ticker" for blinking status led // *************************************************************************** #include Ticker ticker; - +#ifdef ENABLE_HOMEASSISTANT + Ticker ha_send_data; +#endif +#ifdef ENABLE_AMQTT + Ticker mqttReconnectTimer; + Ticker wifiReconnectTimer; +#endif +#ifdef ENABLE_STATE_SAVE_SPIFFS + Ticker spiffs_save_state; +#endif void tick() { //toggle state @@ -81,33 +121,33 @@ void tick() digitalWrite(BUILTIN_LED, !state); // set pin to the opposite state } - -// *************************************************************************** -// EEPROM helper -// *************************************************************************** -String readEEPROM(int offset, int len) { - String res = ""; - for (int i = 0; i < len; ++i) - { - res += char(EEPROM.read(i + offset)); - //DBG_OUTPUT_PORT.println(char(EEPROM.read(i + offset))); +#ifdef ENABLE_STATE_SAVE_EEPROM + // *************************************************************************** + // EEPROM helper + // *************************************************************************** + String readEEPROM(int offset, int len) { + String res = ""; + for (int i = 0; i < len; ++i) + { + res += char(EEPROM.read(i + offset)); + //DBG_OUTPUT_PORT.println(char(EEPROM.read(i + offset))); + } + DBG_OUTPUT_PORT.printf("readEEPROM(): %s\n", res.c_str()); + return res; } - DBG_OUTPUT_PORT.printf("readEEPROM(): %s\n", res.c_str()); - return res; -} - -void writeEEPROM(int offset, int len, String value) { - DBG_OUTPUT_PORT.printf("writeEEPROM(): %s\n", value.c_str()); - for (int i = 0; i < len; ++i) - { - if (i < value.length()) { - EEPROM.write(i + offset, value[i]); - } else { - EEPROM.write(i + offset, NULL); + + void writeEEPROM(int offset, int len, String value) { + DBG_OUTPUT_PORT.printf("writeEEPROM(): %s\n", value.c_str()); + for (int i = 0; i < len; ++i) + { + if (i < value.length()) { + EEPROM.write(i + offset, value[i]); + } else { + EEPROM.write(i + offset, NULL); + } } } -} - +#endif // *************************************************************************** // Saved state handling @@ -130,7 +170,6 @@ String getValue(String data, char separator, int index) return found>index ? data.substring(strIndex[0], strIndex[1]) : ""; } - // *************************************************************************** // Callback for WiFiManager library when config mode is entered // *************************************************************************** @@ -171,12 +210,12 @@ void saveConfigCallback () { // *************************************************************************** #include "colormodes.h" - - // *************************************************************************** // MAIN // *************************************************************************** void setup() { +// system_update_cpu_freq(160); + DBG_OUTPUT_PORT.begin(115200); EEPROM.begin(512); @@ -189,185 +228,6 @@ void setup() { // start ticker with 0.5 because we start in AP mode and try to connect ticker.attach(0.5, tick); - wifi_station_set_hostname(const_cast(HOSTNAME)); - - // *************************************************************************** - // Setup: Neopixel - // *************************************************************************** - strip.init(); - strip.setBrightness(brightness); - strip.setSpeed(convertSpeed(ws2812fx_speed)); - //strip.setMode(FX_MODE_RAINBOW_CYCLE); - strip.setColor(main_color.red, main_color.green, main_color.blue); - strip.start(); - - // *************************************************************************** - // Setup: WiFiManager - // *************************************************************************** - // The extra parameters to be configured (can be either global or just in the setup) - // After connecting, parameter.getValue() will get you the configured value - // id/name placeholder/prompt default length - #ifdef ENABLE_MQTT - String settings_available = readEEPROM(134, 1); - if (settings_available == "1") { - readEEPROM(0, 64).toCharArray(mqtt_host, 64); // 0-63 - readEEPROM(64, 6).toCharArray(mqtt_port, 6); // 64-69 - readEEPROM(70, 32).toCharArray(mqtt_user, 32); // 70-101 - readEEPROM(102, 32).toCharArray(mqtt_pass, 32); // 102-133 - DBG_OUTPUT_PORT.printf("MQTT host: %s\n", mqtt_host); - DBG_OUTPUT_PORT.printf("MQTT port: %s\n", mqtt_port); - DBG_OUTPUT_PORT.printf("MQTT user: %s\n", mqtt_user); - DBG_OUTPUT_PORT.printf("MQTT pass: %s\n", mqtt_pass); - } - - WiFiManagerParameter custom_mqtt_host("host", "MQTT hostname", mqtt_host, 64); - WiFiManagerParameter custom_mqtt_port("port", "MQTT port", mqtt_port, 6); - WiFiManagerParameter custom_mqtt_user("user", "MQTT user", mqtt_user, 32); - WiFiManagerParameter custom_mqtt_pass("pass", "MQTT pass", mqtt_pass, 32); - #endif - - //Local intialization. Once its business is done, there is no need to keep it around - WiFiManager wifiManager; - //reset settings - for testing - //wifiManager.resetSettings(); - - //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode - wifiManager.setAPCallback(configModeCallback); - - #ifdef ENABLE_MQTT - //set config save notify callback - wifiManager.setSaveConfigCallback(saveConfigCallback); - - //add all your parameters here - wifiManager.addParameter(&custom_mqtt_host); - wifiManager.addParameter(&custom_mqtt_port); - wifiManager.addParameter(&custom_mqtt_user); - wifiManager.addParameter(&custom_mqtt_pass); - #endif - - //fetches ssid and pass and tries to connect - //if it does not connect it starts an access point with the specified name - //here "AutoConnectAP" - //and goes into a blocking loop awaiting configuration - if (!wifiManager.autoConnect(HOSTNAME)) { - DBG_OUTPUT_PORT.println("failed to connect and hit timeout"); - //reset and try again, or maybe put it to deep sleep - ESP.reset(); - delay(1000); - } - - #ifdef ENABLE_MQTT - //read updated parameters - strcpy(mqtt_host, custom_mqtt_host.getValue()); - strcpy(mqtt_port, custom_mqtt_port.getValue()); - strcpy(mqtt_user, custom_mqtt_user.getValue()); - strcpy(mqtt_pass, custom_mqtt_pass.getValue()); - - //save the custom parameters to FS - if (shouldSaveConfig) { - DBG_OUTPUT_PORT.println("Saving WiFiManager config"); - - writeEEPROM(0, 64, mqtt_host); // 0-63 - writeEEPROM(64, 6, mqtt_port); // 64-69 - writeEEPROM(70, 32, mqtt_user); // 70-101 - writeEEPROM(102, 32, mqtt_pass); // 102-133 - writeEEPROM(134, 1, "1"); // 134 --> always "1" - EEPROM.commit(); - } - #endif - - //if you get here you have connected to the WiFi - DBG_OUTPUT_PORT.println("connected...yeey :)"); - ticker.detach(); - //keep LED on - digitalWrite(BUILTIN_LED, LOW); - - - // *************************************************************************** - // Configure OTA - // *************************************************************************** - #ifdef ENABLE_OTA - DBG_OUTPUT_PORT.println("Arduino OTA activated."); - - // Port defaults to 8266 - ArduinoOTA.setPort(8266); - - // Hostname defaults to esp8266-[ChipID] - ArduinoOTA.setHostname(HOSTNAME); - - // No authentication by default - // ArduinoOTA.setPassword("admin"); - - // Password can be set with it's md5 value as well - // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 - // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); - - ArduinoOTA.onStart([]() { - DBG_OUTPUT_PORT.println("Arduino OTA: Start updating"); - }); - ArduinoOTA.onEnd([]() { - DBG_OUTPUT_PORT.println("Arduino OTA: End"); - }); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - DBG_OUTPUT_PORT.printf("Arduino OTA Progress: %u%%\r", (progress / (total / 100))); - }); - ArduinoOTA.onError([](ota_error_t error) { - DBG_OUTPUT_PORT.printf("Arduino OTA Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Auth Failed"); - else if (error == OTA_BEGIN_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Begin Failed"); - else if (error == OTA_CONNECT_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Receive Failed"); - else if (error == OTA_END_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: End Failed"); - }); - - ArduinoOTA.begin(); - DBG_OUTPUT_PORT.println(""); - #endif - - - // *************************************************************************** - // Configure MQTT - // *************************************************************************** - #ifdef ENABLE_MQTT - if (mqtt_host != "" && String(mqtt_port).toInt() > 0) { - snprintf(mqtt_intopic, sizeof mqtt_intopic, "%s/in", HOSTNAME); - snprintf(mqtt_outtopic, sizeof mqtt_outtopic, "%s/out", HOSTNAME); - - DBG_OUTPUT_PORT.printf("MQTT active: %s:%d\n", mqtt_host, String(mqtt_port).toInt()); - - mqtt_client.setServer(mqtt_host, String(mqtt_port).toInt()); - mqtt_client.setCallback(mqtt_callback); - } - #endif - - - // *************************************************************************** - // Setup: MDNS responder - // *************************************************************************** - bool mdns_result = MDNS.begin(HOSTNAME); - - DBG_OUTPUT_PORT.print("Open http://"); - DBG_OUTPUT_PORT.print(WiFi.localIP()); - DBG_OUTPUT_PORT.println("/ to open McLighting."); - - DBG_OUTPUT_PORT.print("Use http://"); - DBG_OUTPUT_PORT.print(HOSTNAME); - DBG_OUTPUT_PORT.println(".local/ when you have Bonjour installed."); - - DBG_OUTPUT_PORT.print("New users: Open http://"); - DBG_OUTPUT_PORT.print(WiFi.localIP()); - DBG_OUTPUT_PORT.println("/upload to upload the webpages first."); - - DBG_OUTPUT_PORT.println(""); - - - // *************************************************************************** - // Setup: WebSocket server - // *************************************************************************** - webSocket.begin(); - webSocket.onEvent(webSocketEvent); - - // *************************************************************************** // Setup: SPIFFS // *************************************************************************** @@ -385,6 +245,213 @@ void setup() { DBG_OUTPUT_PORT.printf("FS Usage: %d/%d bytes\n\n", fs_info.usedBytes, fs_info.totalBytes); } + wifi_station_set_hostname(const_cast(HOSTNAME)); + + // *************************************************************************** + // Setup: Neopixel + // *************************************************************************** + strip.init(); + strip.setBrightness(brightness); + strip.setSpeed(convertSpeed(ws2812fx_speed)); + //strip.setMode(FX_MODE_RAINBOW_CYCLE); + strip.setColor(main_color.red, main_color.green, main_color.blue); + strip.start(); + + // *************************************************************************** + // Setup: WiFiManager + // *************************************************************************** + // The extra parameters to be configured (can be either global or just in the setup) + // After connecting, parameter.getValue() will get you the configured value + // id/name placeholder/prompt default length + #if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) + #if defined(ENABLE_STATE_SAVE_SPIFFS) + (readConfigFS()) ? DBG_OUTPUT_PORT.println("WiFiManager config FS Read success!"): DBG_OUTPUT_PORT.println("WiFiManager config FS Read failure!"); + #else + String settings_available = readEEPROM(134, 1); + if (settings_available == "1") { + readEEPROM(0, 64).toCharArray(mqtt_host, 64); // 0-63 + readEEPROM(64, 6).toCharArray(mqtt_port, 6); // 64-69 + readEEPROM(70, 32).toCharArray(mqtt_user, 32); // 70-101 + readEEPROM(102, 32).toCharArray(mqtt_pass, 32); // 102-133 + DBG_OUTPUT_PORT.printf("MQTT host: %s\n", mqtt_host); + DBG_OUTPUT_PORT.printf("MQTT port: %s\n", mqtt_port); + DBG_OUTPUT_PORT.printf("MQTT user: %s\n", mqtt_user); + DBG_OUTPUT_PORT.printf("MQTT pass: %s\n", mqtt_pass); + } + #endif + WiFiManagerParameter custom_mqtt_host("host", "MQTT hostname", mqtt_host, 64); + WiFiManagerParameter custom_mqtt_port("port", "MQTT port", mqtt_port, 6); + WiFiManagerParameter custom_mqtt_user("user", "MQTT user", mqtt_user, 32); + WiFiManagerParameter custom_mqtt_pass("pass", "MQTT pass", mqtt_pass, 32); + #endif + + //Local intialization. Once its business is done, there is no need to keep it around + WiFiManager wifiManager; + //reset settings - for testing + //wifiManager.resetSettings(); + + //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode + wifiManager.setAPCallback(configModeCallback); + + #if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) + //set config save notify callback + wifiManager.setSaveConfigCallback(saveConfigCallback); + + //add all your parameters here + wifiManager.addParameter(&custom_mqtt_host); + wifiManager.addParameter(&custom_mqtt_port); + wifiManager.addParameter(&custom_mqtt_user); + wifiManager.addParameter(&custom_mqtt_pass); + #endif + + WiFi.setSleepMode(WIFI_NONE_SLEEP); + + //fetches ssid and pass and tries to connect + //if it does not connect it starts an access point with the specified name + //here "AutoConnectAP" + //and goes into a blocking loop awaiting configuration + if (!wifiManager.autoConnect(HOSTNAME)) { + DBG_OUTPUT_PORT.println("failed to connect and hit timeout"); + //reset and try again, or maybe put it to deep sleep + ESP.reset(); + delay(1000); + } + + #if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) + //read updated parameters + strcpy(mqtt_host, custom_mqtt_host.getValue()); + strcpy(mqtt_port, custom_mqtt_port.getValue()); + strcpy(mqtt_user, custom_mqtt_user.getValue()); + strcpy(mqtt_pass, custom_mqtt_pass.getValue()); + + //save the custom parameters to FS + #if defined(ENABLE_STATE_SAVE_SPIFFS) + (writeConfigFS(shouldSaveConfig)) ? DBG_OUTPUT_PORT.println("WiFiManager config FS Save success!"): DBG_OUTPUT_PORT.println("WiFiManager config FS Save failure!"); + #else if defined(ENABLE_STATE_SAVE_EEPROM) + if (shouldSaveConfig) { + DBG_OUTPUT_PORT.println("Saving WiFiManager config"); + + writeEEPROM(0, 64, mqtt_host); // 0-63 + writeEEPROM(64, 6, mqtt_port); // 64-69 + writeEEPROM(70, 32, mqtt_user); // 70-101 + writeEEPROM(102, 32, mqtt_pass); // 102-133 + writeEEPROM(134, 1, "1"); // 134 --> always "1" + EEPROM.commit(); + } + #endif + #endif + + #ifdef ENABLE_AMQTT + wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); + wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); + #endif + + //if you get here you have connected to the WiFi + DBG_OUTPUT_PORT.println("connected...yeey :)"); + ticker.detach(); + //keep LED on + digitalWrite(BUILTIN_LED, LOW); + + + // *************************************************************************** + // Configure OTA + // *************************************************************************** + #ifdef ENABLE_OTA + DBG_OUTPUT_PORT.println("Arduino OTA activated."); + + // Port defaults to 8266 + ArduinoOTA.setPort(8266); + + // Hostname defaults to esp8266-[ChipID] + ArduinoOTA.setHostname(HOSTNAME); + + // No authentication by default + // ArduinoOTA.setPassword("admin"); + + // Password can be set with it's md5 value as well + // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 + // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); + + ArduinoOTA.onStart([]() { + DBG_OUTPUT_PORT.println("Arduino OTA: Start updating"); + }); + ArduinoOTA.onEnd([]() { + DBG_OUTPUT_PORT.println("Arduino OTA: End"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + DBG_OUTPUT_PORT.printf("Arduino OTA Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + DBG_OUTPUT_PORT.printf("Arduino OTA Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Auth Failed"); + else if (error == OTA_BEGIN_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Begin Failed"); + else if (error == OTA_CONNECT_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Receive Failed"); + else if (error == OTA_END_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: End Failed"); + }); + + ArduinoOTA.begin(); + DBG_OUTPUT_PORT.println(""); + #endif + + + // *************************************************************************** + // Configure MQTT + // *************************************************************************** + #ifdef ENABLE_MQTT + if (mqtt_host != "" && String(mqtt_port).toInt() > 0) { + snprintf(mqtt_intopic, sizeof mqtt_intopic, "%s/in", HOSTNAME); + snprintf(mqtt_outtopic, sizeof mqtt_outtopic, "%s/out", HOSTNAME); + + DBG_OUTPUT_PORT.printf("MQTT active: %s:%d\n", mqtt_host, String(mqtt_port).toInt()); + + mqtt_client.setServer(mqtt_host, String(mqtt_port).toInt()); + mqtt_client.setCallback(mqtt_callback); + } + #endif + + #ifdef ENABLE_AMQTT + if (mqtt_host != "" && String(mqtt_port).toInt() > 0) { + amqttClient.onConnect(onMqttConnect); + amqttClient.onDisconnect(onMqttDisconnect); + amqttClient.onMessage(onMqttMessage); + amqttClient.setServer(mqtt_host, String(mqtt_port).toInt()); + amqttClient.setClientId(mqtt_clientid); + + connectToMqtt(); + } + #endif + + // #ifdef ENABLE_HOMEASSISTANT + // ha_send_data.attach(5, tickerSendState); // Send HA data back only every 5 sec + // #endif + + // *************************************************************************** + // Setup: MDNS responder + // *************************************************************************** + bool mdns_result = MDNS.begin(HOSTNAME); + + DBG_OUTPUT_PORT.print("Open http://"); + DBG_OUTPUT_PORT.print(WiFi.localIP()); + DBG_OUTPUT_PORT.println("/ to open McLighting."); + + DBG_OUTPUT_PORT.print("Use http://"); + DBG_OUTPUT_PORT.print(HOSTNAME); + DBG_OUTPUT_PORT.println(".local/ when you have Bonjour installed."); + + DBG_OUTPUT_PORT.print("New users: Open http://"); + DBG_OUTPUT_PORT.print(WiFi.localIP()); + DBG_OUTPUT_PORT.println("/upload to upload the webpages first."); + + DBG_OUTPUT_PORT.println(""); + + + // *************************************************************************** + // Setup: WebSocket server + // *************************************************************************** + webSocket.begin(); + webSocket.onEvent(webSocketEvent); + // *************************************************************************** // Setup: SPIFFS Webserver handler // *************************************************************************** @@ -462,7 +529,19 @@ void setup() { brightness = 0; } strip.setBrightness(brightness); - + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK %") + String(brightness)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK %") + String(brightness)).c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif getStatusJSON(); }); @@ -472,14 +551,23 @@ void setup() { DBG_OUTPUT_PORT.print("/get_brightness: "); DBG_OUTPUT_PORT.println(str_brightness); }); - + server.on("/set_speed", []() { if (server.arg("d").toInt() >= 0) { ws2812fx_speed = server.arg("d").toInt(); ws2812fx_speed = constrain(ws2812fx_speed, 0, 255); strip.setSpeed(convertSpeed(ws2812fx_speed)); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ?") + String(ws2812fx_speed)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ?") + String(ws2812fx_speed)).c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif } - + getStatusJSON(); }); @@ -511,6 +599,18 @@ void setup() { mode = OFF; getArgs(); getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =off").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =off").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = false; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); server.on("/all", []() { @@ -518,6 +618,18 @@ void setup() { mode = ALL; getArgs(); getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =all").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =all").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); server.on("/wipe", []() { @@ -525,6 +637,18 @@ void setup() { mode = WIPE; getArgs(); getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =wipe").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =wipe").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); server.on("/rainbow", []() { @@ -532,6 +656,18 @@ void setup() { mode = RAINBOW; getArgs(); getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =rainbow").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =rainbow").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); server.on("/rainbowCycle", []() { @@ -539,6 +675,18 @@ void setup() { mode = RAINBOWCYCLE; getArgs(); getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =rainbowCycle").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =rainbowCycle").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); server.on("/theaterchase", []() { @@ -546,13 +694,56 @@ void setup() { mode = THEATERCHASE; getArgs(); getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =theaterchase").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =theaterchase").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); + server.on("/twinkleRandom", []() { + exit_func = true; + mode = TWINKLERANDOM; + getArgs(); + getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =twinkleRandom").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =twinkleRandom").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif + }); + server.on("/theaterchaseRainbow", []() { exit_func = true; mode = THEATERCHASERAINBOW; getArgs(); getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =theaterchaseRainbow").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =theaterchaseRainbow").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); server.on("/tv", []() { @@ -560,6 +751,18 @@ void setup() { mode = TV; getArgs(); getStatusJSON(); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =tv").c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =tv").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); server.on("/get_modes", []() { @@ -570,20 +773,40 @@ void setup() { getArgs(); mode = SET_MODE; getStatusJSON(); + + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK /") + String(ws2812fx_mode).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK /") + String(ws2812fx_mode)).c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif }); #ifdef HTTP_OTA httpUpdater.setup(&server,"/update"); #endif + #ifdef HTTP_OTA + httpUpdater.setup(&server, "/update"); + #endif + server.begin(); // Start MDNS service if (mdns_result) { MDNS.addService("http", "tcp", 80); } - - #ifdef ENABLE_STATE_SAVE + #ifdef ENABLE_STATE_SAVE_SPIFFS + (readStateFS()) ? DBG_OUTPUT_PORT.println(" Success!") : DBG_OUTPUT_PORT.println(" Failure!"); + #endif + #ifdef ENABLE_STATE_SAVE_EEPROM // Load state string from EEPROM String saved_state_string = readEEPROM(256, 32); String chk = getValue(saved_state_string, '|', 0); @@ -599,24 +822,43 @@ void setup() { void loop() { #ifdef ENABLE_BUTTON button(); - #endif + #endif server.handleClient(); webSocket.loop(); - + #ifdef ENABLE_OTA ArduinoOTA.handle(); #endif #ifdef ENABLE_MQTT - if (mqtt_host != "" && String(mqtt_port).toInt() > 0 && mqtt_reconnect_retries < MQTT_MAX_RECONNECT_TRIES) { - if (!mqtt_client.connected()) { - mqtt_reconnect(); - } else { - mqtt_client.loop(); + if (WiFi.status() != WL_CONNECTED) { + #ifdef ENABLE_HOMEASSISTANT + ha_send_data.detach(); + #endif + DBG_OUTPUT_PORT.println("WiFi disconnected, reconnecting!"); + WiFi.disconnect(); + WiFi.setSleepMode(WIFI_NONE_SLEEP); + WiFi.mode(WIFI_STA); + WiFi.begin(); + } else { + if (mqtt_host != "" && String(mqtt_port).toInt() > 0 && mqtt_reconnect_retries < MQTT_MAX_RECONNECT_TRIES) { + if (!mqtt_client.connected()) { + #ifdef ENABLE_HOMEASSISTANT + ha_send_data.detach(); + #endif + DBG_OUTPUT_PORT.println("MQTT disconnected, reconnecting!"); + mqtt_reconnect(); + } else { + mqtt_client.loop(); + } } } #endif - + #ifdef ENABLE_HOMEASSISTANT +// if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + if (new_ha_mqtt_msg) sendState(); + #endif + // Simple statemachine that handles the different modes if (mode == SET_MODE) { DBG_OUTPUT_PORT.printf("SET_MODE: %d %d\n", ws2812fx_mode, mode); @@ -624,8 +866,9 @@ void loop() { mode = HOLD; } if (mode == OFF) { - strip.setColor(0,0,0); - strip.setMode(FX_MODE_STATIC); +// strip.setColor(0,0,0); +// strip.setMode(FX_MODE_STATIC); + if(strip.isRunning()) strip.stop(); //should clear memory // mode = HOLD; } if (mode == ALL) { @@ -633,6 +876,18 @@ void loop() { strip.setMode(FX_MODE_STATIC); mode = HOLD; } + if (mode == SETCOLOR) { + strip.setColor(main_color.red, main_color.green, main_color.blue); + mode = HOLD; + } + if (mode == SETSPEED) { + strip.setSpeed(convertSpeed(ws2812fx_speed)); + // mode = HOLD; + } + if (mode == BRIGHTNESS) { + strip.setBrightness(brightness); + mode = HOLD; + } if (mode == WIPE) { strip.setColor(main_color.red, main_color.green, main_color.blue); strip.setMode(FX_MODE_COLOR_WIPE); @@ -661,11 +916,13 @@ void loop() { mode = HOLD; } if (mode == HOLD || mode == CUSTOM) { + if(!strip.isRunning()) strip.start(); if (exit_func) { exit_func = false; } } if (mode == TV) { + if(!strip.isRunning()) strip.start(); tv(); } @@ -674,11 +931,16 @@ void loop() { strip.service(); } + #ifdef ENABLE_STATE_SAVE_SPIFFS + if (updateStateFS) { + (writeStateFS()) ? DBG_OUTPUT_PORT.println(" Success!") : DBG_OUTPUT_PORT.println(" Failure!"); + } + #endif - #ifdef ENABLE_STATE_SAVE + #ifdef ENABLE_STATE_SAVE_EEPROM // Check for state changes sprintf(current_state, "STA|%2d|%3d|%3d|%3d|%3d|%3d|%3d", mode, strip.getMode(), ws2812fx_speed, brightness, main_color.red, main_color.green, main_color.blue); - + if (strcmp(current_state, last_state) != 0) { // DBG_OUTPUT_PORT.printf("STATE CHANGED: %s / %s\n", last_state, current_state); strcpy(last_state, current_state); diff --git a/Arduino/McLighting/definitions.h b/Arduino/McLighting/definitions.h index e65dd62..cb42b6e 100644 --- a/Arduino/McLighting/definitions.h +++ b/Arduino/McLighting/definitions.h @@ -1,16 +1,31 @@ +//#define USE_NEOANIMATIONFX // Uses NeoAnimationFX, PIN is ignored & set to RX/GPIO3 +#define USE_WS2812FX // Uses WS2812FX + // Neopixel -#define PIN 5 // PIN (5 / D1) where neopixel / WS2811 strip is attached +#define PIN 5 // PIN (14 / D5) where neopixel / WS2811 strip is attached #define NUMLEDS 24 // Number of leds in the strip -// #define BUILTIN_LED 2 // ESP-12F has the built in LED on GPIO2, see https://github.com/esp8266/Arduino/issues/2192 +#define BUILTIN_LED 2 // ESP-12F has the built in LED on GPIO2, see https://github.com/esp8266/Arduino/issues/2192 #define BUTTON 4 // Input pin (4 / D2) for switching the LED strip on / off, connect this PIN to ground to trigger button. const char HOSTNAME[] = "McLighting01"; // Friedly hostname -//#define HTTP_OTA // If defined, enable Added ESP8266HTTPUpdateServer #define ENABLE_OTA // If defined, enable Arduino OTA code. #define ENABLE_MQTT // If defined, enable MQTT client code, see: https://github.com/toblum/McLighting/wiki/MQTT-API #define ENABLE_HOMEASSISTANT // If defined, enable Homeassistant integration, ENABLE_MQTT must be active -// #define ENABLE_BUTTON // If defined, enable button handling code, see: https://github.com/toblum/McLighting/wiki/Button-control +#define ENABLE_BUTTON // If defined, enable button handling code, see: https://github.com/toblum/McLighting/wiki/Button-control + +#if defined(USE_NEOANIMATIONFX) and defined(USE_WS2812FX) +#error "Cant have both NeoAnimationFX and WS2812FX enabled. Choose either one." +#endif +#if !defined(USE_NEOANIMATIONFX) and !defined(USE_WS2812FX) +#error "Need to either use NeoAnimationFX and WS2812FX mode." +#endif +#if defined(ENABLE_MQTT) and defined(ENABLE_AMQTT) +#error "Cant have both PubSubClient and AsyncMQTT enabled. Choose either one." +#endif +#if ( (defined(ENABLE_HOMEASSISTANT) and !defined(ENABLE_MQTT)) and (defined(ENABLE_HOMEASSISTANT) and !defined(ENABLE_AMQTT)) ) +#error "To use HA, you have to either enable PubCubClient or AsyncMQTT" +#endif // parameters for automatically cycling favorite patterns uint32_t autoParams[][4] = { // color, speed, mode, duration (seconds) @@ -20,33 +35,44 @@ uint32_t autoParams[][4] = { // color, speed, mode, duration (seconds) {0x0000ff, 200, 42, 15.0} // fireworks for 15 seconds }; -#ifdef ENABLE_MQTT - #define MQTT_MAX_PACKET_SIZE 512 - #define MQTT_MAX_RECONNECT_TRIES 4 +#if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) + #ifdef ENABLE_MQTT + #define MQTT_MAX_PACKET_SIZE 512 + #define MQTT_MAX_RECONNECT_TRIES 4 - int mqtt_reconnect_retries = 0; - char mqtt_intopic[strlen(HOSTNAME) + 4 + 5]; // Topic in will be: /in - char mqtt_outtopic[strlen(HOSTNAME) + 5 + 5]; // Topic out will be: /out + int mqtt_reconnect_retries = 0; + char mqtt_intopic[strlen(HOSTNAME) + 4 + 5]; // Topic in will be: /in + char mqtt_outtopic[strlen(HOSTNAME) + 5 + 5]; // Topic out will be: /out + uint8_t qossub = 0; // PubSubClient can sub qos 0 or 1 + #endif + + #ifdef ENABLE_AMQTT + String mqtt_intopic = String(HOSTNAME) + "/in"; + String mqtt_outtopic = String(HOSTNAME) + "/out"; + uint8_t qossub = 0; // AMQTT can sub qos 0 or 1 or 2 + uint8_t qospub = 0; // AMQTT can pub qos 0 or 1 or 2 + #endif #ifdef ENABLE_HOMEASSISTANT String mqtt_ha = "home/" + String(HOSTNAME) + "_ha/"; String mqtt_ha_state_in = mqtt_ha + "state/in"; String mqtt_ha_state_out = mqtt_ha + "state/out"; - String mqtt_ha_speed = mqtt_ha + "speed"; - + const char* on_cmd = "ON"; const char* off_cmd = "OFF"; bool stateOn = false; bool animation_on = false; - String effectString = "Static"; + bool new_ha_mqtt_msg = false; + uint16_t color_temp = 327; // min is 154 and max is 500 #endif - const char * mqtt_clientid = HOSTNAME; // Set ClientID to HOSTNAME to be unique + const char mqtt_clientid[] = "NeoPixelsStrip"; // MQTT ClientID + + char mqtt_host[64] = ""; + char mqtt_port[6] = ""; + char mqtt_user[32] = ""; + char mqtt_pass[32] = ""; - char mqtt_host[64] = ""; - char mqtt_port[6] = ""; - char mqtt_user[32] = ""; - char mqtt_pass[32] = ""; #endif @@ -56,7 +82,7 @@ uint32_t autoParams[][4] = { // color, speed, mode, duration (seconds) #define DBG_OUTPUT_PORT Serial // Set debug output port // List of all color modes -enum MODE { SET_MODE, HOLD, OFF, ALL, WIPE, RAINBOW, RAINBOWCYCLE, THEATERCHASE, TWINKLERANDOM, THEATERCHASERAINBOW, TV, CUSTOM }; +enum MODE { SET_MODE, HOLD, OFF, ALL, SETCOLOR, SETSPEED, BRIGHTNESS, WIPE, RAINBOW, RAINBOWCYCLE, THEATERCHASE, TWINKLERANDOM, THEATERCHASERAINBOW, TV, CUSTOM }; MODE mode = RAINBOW; // Standard mode that is active when software starts @@ -80,14 +106,18 @@ typedef struct ledstate LEDState; // Define the datatype LEDState LEDState ledstates[NUMLEDS]; // Get an array of led states to store the state of the whole strip LEDState main_color = { 255, 0, 0 }; // Store the "main color" of the strip used in single color modes -#define ENABLE_STATE_SAVE // If defined, save state on reboot -#ifdef ENABLE_STATE_SAVE +#define ENABLE_STATE_SAVE_SPIFFS // If defined, saves state on SPIFFS +//#define ENABLE_STATE_SAVE_EEPROM // If defined, save state on reboot +#ifdef ENABLE_STATE_SAVE_EEPROM char current_state[32]; // Keeps the current state representation char last_state[32]; // Save the last state as string representation unsigned long time_statechange = 0; // Time when the state last changed int timeout_statechange_save = 5000; // Timeout in ms to wait before state is saved bool state_save_requested = false; // State has to be saved after timeout #endif +#ifdef ENABLE_STATE_SAVE_SPIFFS + bool updateStateFS = false; +#endif // Button handling #ifdef ENABLE_BUTTON @@ -102,4 +132,14 @@ LEDState main_color = { 255, 0, 0 }; // Store the "main color" of the strip use byte KeyPressCount = 0; byte prevKeyState = HIGH; // button is active low boolean buttonState = false; -#endif +#endif + +#define PIN 14 // PIN (14 / D5) where neopixel / WS2811 strip is attached +#define NUMLEDS 300 // Number of leds in the strip +#define BUILTIN_LED 2 // ESP-12F has the built in LED on GPIO2, see https://github.com/esp8266/Arduino/issues/2192 +#define BUTTON 0 // Input pin (4 / D2) for switching the LED strip on / off, connect this PIN to ground to trigger button. +#define HTTP_OTA // If defined, enable ESP8266HTTPUpdateServer OTA code. +//#define ENABLE_OTA // If defined, enable Arduino OTA code. +#define ENABLE_AMQTT // If defined, enable Async MQTT code, see: https://github.com/marvinroger/async-mqtt-client +//#define ENABLE_MQTT // If defined, enable MQTT client code, see: https://github.com/toblum/McLighting/wiki/MQTT-API + const char * mqtt_clientid = HOSTNAME; // Set ClientID to HOSTNAME to be unique \ No newline at end of file diff --git a/Arduino/McLighting/request_handlers.h b/Arduino/McLighting/request_handlers.h index 370c96e..543639e 100644 --- a/Arduino/McLighting/request_handlers.h +++ b/Arduino/McLighting/request_handlers.h @@ -1,6 +1,17 @@ // *************************************************************************** // Request handlers // *************************************************************************** +#ifdef ENABLE_HOMEASSISTANT +void tickerSendState(){ + new_ha_mqtt_msg = true; +} +#endif +#ifdef ENABLE_STATE_SAVE_SPIFFS +void tickerSpiffsSaveState(){ + updateStateFS = true; +} +#endif + void getArgs() { if (server.arg("rgb") != "") { uint32_t rgb = (uint32_t) strtol(server.arg("rgb").c_str(), NULL, 16); @@ -40,8 +51,9 @@ void getArgs() { } -long convertSpeed(int mcl_speed) { - long ws2812_speed = mcl_speed * 256; +uint16_t convertSpeed(uint8_t mcl_speed) { + //long ws2812_speed = mcl_speed * 256; + uint16_t ws2812_speed = 61760 * (exp(0.0002336 * mcl_speed) - exp(-0.03181 * mcl_speed)); ws2812_speed = SPEED_MAX - ws2812_speed; if (ws2812_speed < SPEED_MIN) { ws2812_speed = SPEED_MIN; @@ -190,7 +202,7 @@ void handleSetNamedMode(String str_mode) { exit_func = true; if (str_mode.startsWith("=off")) { - mode = OFF; + mode = OFF; #ifdef ENABLE_HOMEASSISTANT stateOn = false; #endif @@ -286,33 +298,6 @@ void getModesJSON() { server.send ( 200, "application/json", listModesJSON() ); } -#ifdef ENABLE_HOMEASSISTANT -/********************************** START SEND STATE*****************************************/ -void sendState() { - StaticJsonBuffer jsonBuffer; - - JsonObject& root = jsonBuffer.createObject(); - - root["state"] = (stateOn) ? on_cmd : off_cmd; - JsonObject& color = root.createNestedObject("color"); - color["r"] = main_color.red; - color["g"] = main_color.green; - color["b"] = main_color.blue; - - root["brightness"] = brightness; - - char modeName[30]; - strncpy_P(modeName, (PGM_P)strip.getModeName(strip.getMode()), sizeof(modeName)); // copy from progmem - root["effect"] = modeName; - - - char buffer[root.measureLength() + 1]; - root.printTo(buffer, sizeof(buffer)); - - mqtt_client.publish(mqtt_ha_state_out.c_str(), buffer, true); -} -#endif - // *************************************************************************** // HTTP request handlers // *************************************************************************** @@ -408,9 +393,18 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght handleSetMainColor(payload); DBG_OUTPUT_PORT.printf("Set main color to: [%u] [%u] [%u]\n", main_color.red, main_color.green, main_color.blue); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT stateOn = true; - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } @@ -421,6 +415,15 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght strip.setSpeed(convertSpeed(ws2812fx_speed)); DBG_OUTPUT_PORT.printf("WS: Set speed to: [%u]\n", ws2812fx_speed); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_HOMEASSISTANT + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } // % ==> Set brightness @@ -430,9 +433,18 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght DBG_OUTPUT_PORT.printf("WS: Set brightness to: [%u]\n", brightness); strip.setBrightness(brightness); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT stateOn = true; - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } @@ -440,9 +452,18 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght if (payload[0] == '*') { handleSetAllMode(payload); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT stateOn = true; - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } @@ -450,18 +471,36 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght if (payload[0] == '!') { handleSetSingleLED(payload, 1); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } // + ==> Set multiple LED in the given colors if (payload[0] == '+') { handleSetDifferentColors(payload); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } // + ==> Set range of LEDs in the given color if (payload[0] == 'R') { handleRangeDifferentColors(payload); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } // = ==> Activate named mode @@ -473,8 +512,17 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght DBG_OUTPUT_PORT.printf("Activated mode [%u]!\n", mode); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } @@ -485,6 +533,13 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght String json = listStatusJSON(); DBG_OUTPUT_PORT.println(json); webSocket.sendTXT(num, json); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, listStatusJSON()); + #endif + #ifdef ENABLE_AMQTT + String liststat = (String) listStatusJSON(); + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, liststat.c_str()); + #endif } // ~ ==> Get WS2812 modes. @@ -494,15 +549,36 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght String json = listModesJSON(); DBG_OUTPUT_PORT.println(json); webSocket.sendTXT(num, json); + #ifdef ENABLE_MQTT + DBG_OUTPUT_PORT.printf("Error: Not implemented. Message too large for pubsubclient."); + mqtt_client.publish(mqtt_outtopic, "ERROR: Not implemented. Message too large for pubsubclient."); + //String json_modes = listModesJSON(); + //DBG_OUTPUT_PORT.printf(json_modes.c_str()); + + //int res = mqtt_client.publish(mqtt_outtopic, json_modes.c_str(), json_modes.length()); + //DBG_OUTPUT_PORT.printf("Result: %d / %d", res, json_modes.length()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("ERROR: Not implemented. Message too large for AsyncMQTT.").c_str()); + #endif } // / ==> Set WS2812 mode. if (payload[0] == '/') { handleSetWS2812FXMode(payload); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT stateOn = true; - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } @@ -533,263 +609,391 @@ void checkForRequests() { // *************************************************************************** // MQTT callback / connection handler // *************************************************************************** -#ifdef ENABLE_MQTT +#if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) #ifdef ENABLE_HOMEASSISTANT - void temp2rgb(unsigned int kelvin) { + LEDState temp2rgb(unsigned int kelvin) { int tmp_internal = kelvin / 100.0; - + LEDState tmp_color; + // red if (tmp_internal <= 66) { - main_color.red = 255; + tmp_color.red = 255; } else { float tmp_red = 329.698727446 * pow(tmp_internal - 60, -0.1332047592); if (tmp_red < 0) { - main_color.red = 0; + tmp_color.red = 0; } else if (tmp_red > 255) { - main_color.red = 255; + tmp_color.red = 255; } else { - main_color.red = tmp_red; + tmp_color.red = tmp_red; } } - + // green if (tmp_internal <= 66) { float tmp_green = 99.4708025861 * log(tmp_internal) - 161.1195681661; if (tmp_green < 0) { - main_color.green = 0; + tmp_color.green = 0; } else if (tmp_green > 255) { - main_color.green = 255; + tmp_color.green = 255; } else { - main_color.green = tmp_green; + tmp_color.green = tmp_green; } } else { float tmp_green = 288.1221695283 * pow(tmp_internal - 60, -0.0755148492); if (tmp_green < 0) { - main_color.green = 0; + tmp_color.green = 0; } else if (tmp_green > 255) { - main_color.green = 255; + tmp_color.green = 255; } else { - main_color.green = tmp_green; + tmp_color.green = tmp_green; } } - + // blue if (tmp_internal >= 66) { - main_color.blue = 255; + tmp_color.blue = 255; } else if (tmp_internal <= 19) { - main_color.blue = 0; + tmp_color.blue = 0; } else { float tmp_blue = 138.5177312231 * log(tmp_internal - 10) - 305.0447927307; if (tmp_blue < 0) { - main_color.blue = 0; + tmp_color.blue = 0; } else if (tmp_blue > 255) { - main_color.blue = 255; + tmp_color.blue = 255; } else { - main_color.blue = tmp_blue; + tmp_color.blue = tmp_blue; } } - - //handleSetWS2812FXMode((uint8_t *) "/0"); - strip.setColor(main_color.red, main_color.green, main_color.blue); - strip.start(); - + return tmp_color; } - + + void sendState() { + const size_t bufferSize = JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(6); + //StaticJsonBuffer jsonBuffer; + DynamicJsonBuffer jsonBuffer(bufferSize); + JsonObject& root = jsonBuffer.createObject(); + + root["state"] = (stateOn) ? on_cmd : off_cmd; + JsonObject& color = root.createNestedObject("color"); + color["r"] = main_color.red; + color["g"] = main_color.green; + color["b"] = main_color.blue; + + root["brightness"] = brightness; + + root["color_temp"] = color_temp; + + root["speed"] = ws2812fx_speed; + + char modeName[30]; + strncpy_P(modeName, (PGM_P)strip.getModeName(strip.getMode()), sizeof(modeName)); // copy from progmem + root["effect"] = modeName; + + char buffer[root.measureLength() + 1]; + root.printTo(buffer, sizeof(buffer)); + + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_ha_state_out.c_str(), buffer, true); + DBG_OUTPUT_PORT.printf("MQTT: Send [%s]: %s\n", mqtt_ha_state_out.c_str(), buffer); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_ha_state_out.c_str(), 1, true, buffer); + DBG_OUTPUT_PORT.printf("MQTT: Send [%s]: %s\n", mqtt_ha_state_out.c_str(), buffer); + #endif + new_ha_mqtt_msg = false; + ha_send_data.detach(); + DBG_OUTPUT_PORT.printf("Heap size: %u\n", ESP.getFreeHeap()); + } + bool processJson(char* message) { - StaticJsonBuffer jsonBuffer; - + const size_t bufferSize = JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(5) + 150; + //StaticJsonBuffer jsonBuffer; + DynamicJsonBuffer jsonBuffer(bufferSize); JsonObject& root = jsonBuffer.parseObject(message); - + if (!root.success()) { - Serial.println("parseObject() failed"); + DBG_OUTPUT_PORT.println("parseObject() failed"); return false; } - + //DBG_OUTPUT_PORT.println("JSON ParseObject() done!"); + if (root.containsKey("state")) { - if (strcmp(root["state"], on_cmd) == 0 and !(animation_on)) { + const char* state_in = root["state"]; + if (strcmp(state_in, on_cmd) == 0 and !(animation_on)) { stateOn = true; mode = ALL; - strip.setColor(main_color.red, main_color.green, main_color.blue); - strip.start(); } - else if (strcmp(root["state"], off_cmd) == 0) { + else if (strcmp(state_in, off_cmd) == 0) { stateOn = false; - mode = OFF; animation_on = false; - strip.start(); + mode = OFF; + return true; } } - + if (root.containsKey("color")) { - main_color.red = root["color"]["r"]; - main_color.green = root["color"]["g"]; - main_color.blue = root["color"]["b"]; - //handleSetWS2812FXMode((uint8_t *) "/0"); - strip.setColor(main_color.red, main_color.green, main_color.blue); - strip.start(); + JsonObject& color = root["color"]; + main_color.red = (uint8_t) color["r"]; + main_color.green = (uint8_t) color["g"]; + main_color.blue = (uint8_t) color["b"]; + mode = SETCOLOR; } - + + if (root.containsKey("speed")) { + uint8_t json_speed = constrain((uint8_t) root["speed"], 0, 255); + if (json_speed != ws2812fx_speed) { + ws2812fx_speed = json_speed; + mode = SETSPEED; + } + } + if (root.containsKey("color_temp")) { //temp comes in as mireds, need to convert to kelvin then to RGB - int color_temp = root["color_temp"]; + color_temp = (uint16_t) root["color_temp"]; unsigned int kelvin = 1000000 / color_temp; - - temp2rgb(kelvin); + main_color = temp2rgb(kelvin); + mode = SETCOLOR; } - + if (root.containsKey("brightness")) { const char * brightness_json = root["brightness"]; uint8_t b = (uint8_t) strtol((const char *) &brightness_json[0], NULL, 10); brightness = constrain(b, 0, 255); - strip.setBrightness(brightness); + mode = BRIGHTNESS; } - + if (root.containsKey("effect")) { animation_on = true; - effectString = String((const char *)root["effect"]); + String effectString = root["effect"].asString(); for (uint8_t i = 0; i < strip.getModeCount(); i++) { if(String(strip.getModeName(i)) == effectString) { - mode = HOLD; - strip.setColor(main_color.red, main_color.green, main_color.blue); - strip.setMode(i); - strip.start(); + mode = SET_MODE; + ws2812fx_mode = i; break; } } } - + jsonBuffer.clear(); return true; } #endif - + + #ifdef ENABLE_AMQTT + void onMqttMessage(char* topic, char* payload_in, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total) { + DBG_OUTPUT_PORT.print("MQTT: Recieved ["); DBG_OUTPUT_PORT.print(topic); +// DBG_OUTPUT_PORT.print("]: "); DBG_OUTPUT_PORT.println(payload_in); + uint8_t * payload = (uint8_t *) malloc(length + 1); + memcpy(payload, payload_in, length); + payload[length] = NULL; + DBG_OUTPUT_PORT.printf("]: %s\n", payload); + #endif + + #ifdef ENABLE_MQTT void mqtt_callback(char* topic, byte* payload_in, unsigned int length) { uint8_t * payload = (uint8_t *)malloc(length + 1); memcpy(payload, payload_in, length); payload[length] = NULL; DBG_OUTPUT_PORT.printf("MQTT: Message arrived [%s]\n", payload); - + #endif #ifdef ENABLE_HOMEASSISTANT if (strcmp(topic, mqtt_ha_state_in.c_str()) == 0) { if (!processJson((char*)payload)) { return; } - sendState(); - - } else if (strcmp(topic, mqtt_ha_speed.c_str()) == 0) { - uint8_t d = (uint8_t) strtol((const char *) &payload[0], NULL, 10); - ws2812fx_speed = constrain(d, 0, 255); - strip.setSpeed(convertSpeed(ws2812fx_speed)); - - } else if (strcmp(topic, (char *)mqtt_intopic) == 0) { + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); + #endif + #ifdef ENABLE_MQTT + } else if (strcmp(topic, (char *)mqtt_intopic) == 0) { + #endif + #ifdef ENABLE_AMQTT + } else if (strcmp(topic, mqtt_intopic.c_str()) == 0) { + #endif #endif - + // # ==> Set main color if (payload[0] == '#') { handleSetMainColor(payload); DBG_OUTPUT_PORT.printf("MQTT: Set main color to [%u] [%u] [%u]\n", main_color.red, main_color.green, main_color.blue); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT stateOn = true; - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } - + // ? ==> Set speed if (payload[0] == '?') { uint8_t d = (uint8_t) strtol((const char *) &payload[1], NULL, 10); ws2812fx_speed = constrain(d, 0, 255); strip.setSpeed(convertSpeed(ws2812fx_speed)); DBG_OUTPUT_PORT.printf("MQTT: Set speed to [%u]\n", ws2812fx_speed); + #ifdef ENABLE_HOMEASSISTANT + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } - + // % ==> Set brightness if (payload[0] == '%') { uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); brightness = constrain(b, 0, 255); strip.setBrightness(brightness); DBG_OUTPUT_PORT.printf("MQTT: Set brightness to [%u]\n", brightness); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT stateOn = true; - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } - + // * ==> Set main color and light all LEDs (Shortcut) if (payload[0] == '*') { handleSetAllMode(payload); DBG_OUTPUT_PORT.printf("MQTT: Set main color and light all LEDs [%s]\n", payload); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT stateOn = true; - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } - + // ! ==> Set single LED in given color if (payload[0] == '!') { handleSetSingleLED(payload, 1); DBG_OUTPUT_PORT.printf("MQTT: Set single LED in given color [%s]\n", payload); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } - + // + ==> Set multiple LED in the given colors if (payload[0] == '+') { handleSetDifferentColors(payload); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } - + // R ==> Set range of LEDs in the given colors if (payload[0] == 'R') { handleRangeDifferentColors(payload); DBG_OUTPUT_PORT.printf("MQTT: Set range of LEDS to single color: [%s]\n", payload); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } - + // = ==> Activate named mode if (payload[0] == '=') { String str_mode = String((char *) &payload[0]); handleSetNamedMode(str_mode); DBG_OUTPUT_PORT.printf("MQTT: Activate named mode [%s]\n", payload); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } - + // $ ==> Get status Info. if (payload[0] == '$') { DBG_OUTPUT_PORT.printf("MQTT: Get status info.\n"); DBG_OUTPUT_PORT.println("MQTT: Out: " + String(listStatusJSON())); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, listStatusJSON()); + #endif + #ifdef ENABLE_AMQTT + String liststat = (String) listStatusJSON(); + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, liststat.c_str()); + #endif } - + // ~ ==> Get WS2812 modes. // TODO: Fix this, doesn't return anything. Too long? // Hint: https://github.com/knolleary/pubsubclient/issues/110 if (payload[0] == '~') { DBG_OUTPUT_PORT.printf("MQTT: Get WS2812 modes.\n"); + #ifdef ENABLE_MQTT DBG_OUTPUT_PORT.printf("Error: Not implemented. Message too large for pubsubclient."); mqtt_client.publish(mqtt_outtopic, "ERROR: Not implemented. Message too large for pubsubclient."); //String json_modes = listModesJSON(); //DBG_OUTPUT_PORT.printf(json_modes.c_str()); - + //int res = mqtt_client.publish(mqtt_outtopic, json_modes.c_str(), json_modes.length()); //DBG_OUTPUT_PORT.printf("Result: %d / %d", res, json_modes.length()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("ERROR: Not implemented. Message too large for AsyncMQTT.").c_str()); + #endif } - + // / ==> Set WS2812 mode. if (payload[0] == '/') { handleSetWS2812FXMode(payload); DBG_OUTPUT_PORT.printf("MQTT: Set WS2812 mode [%s]\n", payload); + #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif #ifdef ENABLE_HOMEASSISTANT stateOn = true; - sendState(); + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } @@ -798,12 +1002,13 @@ void checkForRequests() { #endif free(payload); } - + + #ifdef ENABLE_MQTT void mqtt_reconnect() { // Loop until we're reconnected while (!mqtt_client.connected() && mqtt_reconnect_retries < MQTT_MAX_RECONNECT_TRIES) { mqtt_reconnect_retries++; - DBG_OUTPUT_PORT.printf("Attempting MQTT connection %d / %d (%s) ...\n", mqtt_reconnect_retries, MQTT_MAX_RECONNECT_TRIES, mqtt_clientid); + DBG_OUTPUT_PORT.printf("Attempting MQTT connection %d / %d ...\n", mqtt_reconnect_retries, MQTT_MAX_RECONNECT_TRIES); // Attempt to connect if (mqtt_client.connect(mqtt_clientid, mqtt_user, mqtt_pass)) { DBG_OUTPUT_PORT.println("MQTT connected!"); @@ -813,13 +1018,12 @@ void checkForRequests() { strcat(message, HOSTNAME); mqtt_client.publish(mqtt_outtopic, message); // ... and resubscribe - mqtt_client.subscribe(mqtt_intopic); + mqtt_client.subscribe(mqtt_intopic, qossub, qossub); #ifdef ENABLE_HOMEASSISTANT - DBG_OUTPUT_PORT.printf("Homeassistant MQTT topic in: %s\n", mqtt_ha_state_in.c_str()); - mqtt_client.subscribe(mqtt_ha_state_in.c_str()); - mqtt_client.subscribe(mqtt_ha_speed.c_str()); + ha_send_data.detach(); + mqtt_client.subscribe(mqtt_ha_state_in.c_str(), qossub); #endif - + DBG_OUTPUT_PORT.printf("MQTT topic in: %s\n", mqtt_intopic); DBG_OUTPUT_PORT.printf("MQTT topic out: %s\n", mqtt_outtopic); } else { @@ -834,6 +1038,76 @@ void checkForRequests() { DBG_OUTPUT_PORT.printf("MQTT connection failed, giving up after %d tries ...\n", mqtt_reconnect_retries); } } + #endif + #ifdef ENABLE_AMQTT + void connectToWifi() { + DBG_OUTPUT_PORT.println("Re-connecting to Wi-Fi..."); + WiFi.setSleepMode(WIFI_NONE_SLEEP); + WiFi.mode(WIFI_STA); + WiFi.begin(); + } + + void connectToMqtt() { + DBG_OUTPUT_PORT.println("Connecting to MQTT..."); + amqttClient.connect(); + } + + void onWifiConnect(const WiFiEventStationModeGotIP& event) { + DBG_OUTPUT_PORT.println("Connected to Wi-Fi."); + connectToMqtt(); + } + + void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { + DBG_OUTPUT_PORT.println("Disconnected from Wi-Fi."); + #ifdef ENABLE_HOMEASSISTANT + ha_send_data.detach(); + #endif + mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi + wifiReconnectTimer.once(2, connectToWifi); + } + + void onMqttConnect(bool sessionPresent) { + DBG_OUTPUT_PORT.println("Connected to MQTT."); + DBG_OUTPUT_PORT.print("Session present: "); + DBG_OUTPUT_PORT.println(sessionPresent); + char * message = new char[18 + strlen(HOSTNAME) + 1]; + strcpy(message, "McLighting ready: "); + strcat(message, HOSTNAME); + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, message); + //Subscribe + uint16_t packetIdSub1 = amqttClient.subscribe((char *)mqtt_intopic.c_str(), qossub); + DBG_OUTPUT_PORT.printf("Subscribing at QoS %d, packetId: ", qossub); DBG_OUTPUT_PORT.println(packetIdSub1); + #ifdef ENABLE_HOMEASSISTANT + ha_send_data.detach(); + uint16_t packetIdSub2 = amqttClient.subscribe((char *)mqtt_ha_state_in.c_str(), qossub); + DBG_OUTPUT_PORT.printf("Subscribing at QoS %d, packetId: ", qossub); DBG_OUTPUT_PORT.println(packetIdSub2); + #endif + } + + void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { + DBG_OUTPUT_PORT.print("Disconnected from MQTT, reason: "); + if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT) { + DBG_OUTPUT_PORT.println("Bad server fingerprint."); + } else if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { + DBG_OUTPUT_PORT.println("TCP Disconnected."); + } else if (reason == AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION) { + DBG_OUTPUT_PORT.println("Bad server fingerprint."); + } else if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) { + DBG_OUTPUT_PORT.println("MQTT Identifier rejected."); + } else if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) { + DBG_OUTPUT_PORT.println("MQTT server unavailable."); + } else if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) { + DBG_OUTPUT_PORT.println("MQTT malformed credentials."); + } else if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { + DBG_OUTPUT_PORT.println("MQTT not authorized."); + } else if (reason == AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE) { + DBG_OUTPUT_PORT.println("Not enough space on esp8266."); + } + if (WiFi.isConnected()) { + mqttReconnectTimer.once(5, connectToMqtt); + } + } + #endif #endif @@ -854,46 +1128,64 @@ void checkForRequests() { buttonState = false; #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String("OK =off").c_str()); - #ifdef ENABLE_HOMEASSISTANT - stateOn = false; - sendState(); - #endif + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =off").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = false; + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } } - + // called when button is kept pressed for less than 2 seconds void mediumKeyPress() { DBG_OUTPUT_PORT.printf("Medium button press\n"); setModeByStateString(BTN_MODE_MEDIUM); #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String("OK =fire flicker").c_str()); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #endif + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =fire flicker").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } - + // called when button is kept pressed for 2 seconds or more void longKeyPress() { DBG_OUTPUT_PORT.printf("Long button press\n"); setModeByStateString(BTN_MODE_LONG); #ifdef ENABLE_MQTT mqtt_client.publish(mqtt_outtopic, String("OK =fireworks random").c_str()); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #endif + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("OK =fireworks random").c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_STATE_SAVE_SPIFFS + if(!spiffs_save_state.active()) spiffs_save_state.once(3, tickerSpiffsSaveState); #endif } - + void button() { if (millis() - keyPrevMillis >= keySampleIntervalMs) { keyPrevMillis = millis(); - + byte currKeyState = digitalRead(BUTTON); - + if ((prevKeyState == HIGH) && (currKeyState == LOW)) { // key goes from not pressed to pressed KeyPressCount = 0; @@ -918,3 +1210,157 @@ void checkForRequests() { } } #endif + +#ifdef ENABLE_STATE_SAVE_SPIFFS +bool updateFS = false; +// Write configuration to FS JSON +bool writeConfigFS(bool saveConfig){ + if (saveConfig) { + //FS save + updateFS = true; + DBG_OUTPUT_PORT.print("Saving config: "); + DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(4)); +// StaticJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json["mqtt_host"] = mqtt_host; + json["mqtt_port"] = mqtt_port; + json["mqtt_user"] = mqtt_user; + json["mqtt_pass"] = mqtt_pass; + +// SPIFFS.remove("/config.json") ? DBG_OUTPUT_PORT.println("removed file") : DBG_OUTPUT_PORT.println("failed removing file"); + File configFile = SPIFFS.open("/config.json", "w"); + if (!configFile) DBG_OUTPUT_PORT.println("failed to open config file for writing"); + + json.printTo(DBG_OUTPUT_PORT); + json.printTo(configFile); + configFile.close(); + updateFS = false; + return true; + //end save + } else { + DBG_OUTPUT_PORT.println("SaveConfig is False!"); + return false; + } +} + +// Read search_str to FS +bool readConfigFS() { + //read configuration from FS JSON + updateFS = true; + if (SPIFFS.exists("/config.json")) { + //file exists, reading and loading + DBG_OUTPUT_PORT.print("Reading config file... "); + File configFile = SPIFFS.open("/config.json", "r"); + if (configFile) { + DBG_OUTPUT_PORT.println("Opened!"); + size_t size = configFile.size(); + std::unique_ptr buf(new char[size]); + configFile.readBytes(buf.get(), size); + DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(4)+300); +// StaticJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.parseObject(buf.get()); + DBG_OUTPUT_PORT.print("Config: "); + json.printTo(DBG_OUTPUT_PORT); + if (json.success()) { + DBG_OUTPUT_PORT.println(" Parsed!"); + strcpy(mqtt_host, json["mqtt_host"]); + strcpy(mqtt_port, json["mqtt_port"]); + strcpy(mqtt_user, json["mqtt_user"]); + strcpy(mqtt_pass, json["mqtt_pass"]); + updateFS = false; + return true; + } else { + DBG_OUTPUT_PORT.println("Failed to load json config"); + } + } else { + DBG_OUTPUT_PORT.println("Failed to open /config.json"); + } + } else { + DBG_OUTPUT_PORT.println("Coudnt find config.json"); + } + //end read + updateFS = false; + return false; +} + +bool writeStateFS(){ + updateFS = true; + //save the strip state to FS JSON + DBG_OUTPUT_PORT.print("Saving cfg: "); + DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(7)); +// StaticJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json["mode"] = static_cast(mode); + json["strip_mode"] = (int) strip.getMode(); + json["brightness"] = brightness; + json["speed"] = ws2812fx_speed; + json["red"] = main_color.red; + json["green"] = main_color.green; + json["blue"] = main_color.blue; + +// SPIFFS.remove("/state.json") ? DBG_OUTPUT_PORT.println("removed file") : DBG_OUTPUT_PORT.println("failed removing file"); + File configFile = SPIFFS.open("/stripstate.json", "w"); + if (!configFile) { + DBG_OUTPUT_PORT.println("Failed!"); + updateFS = false; + spiffs_save_state.detach(); + updateStateFS = false; + return false; + } + json.printTo(DBG_OUTPUT_PORT); + json.printTo(configFile); + configFile.close(); + updateFS = false; + spiffs_save_state.detach(); + updateStateFS = false; + return true; + //end save +} + +bool readStateFS() { + //read strip state from FS JSON + updateFS = true; + //if (resetsettings) { SPIFFS.begin(); SPIFFS.remove("/config.json"); SPIFFS.format(); delay(1000);} + if (SPIFFS.exists("/stripstate.json")) { + //file exists, reading and loading + DBG_OUTPUT_PORT.print("Read cfg: "); + File configFile = SPIFFS.open("/stripstate.json", "r"); + if (configFile) { + size_t size = configFile.size(); + // Allocate a buffer to store contents of the file. + std::unique_ptr buf(new char[size]); + configFile.readBytes(buf.get(), size); + DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(7)+200); +// StaticJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.parseObject(buf.get()); + json.printTo(DBG_OUTPUT_PORT); + if (json.success()) { + mode = static_cast((int) json["mode"]); + ws2812fx_mode = json["strip_mode"]; + brightness = json["brightness"]; + ws2812fx_speed = json["speed"]; + main_color.red = json["red"]; + main_color.green = json["green"]; + main_color.blue = json["blue"]; + + strip.setMode(ws2812fx_mode); + strip.setSpeed(convertSpeed(ws2812fx_speed)); + strip.setBrightness(brightness); + strip.setColor(main_color.red, main_color.green, main_color.blue); + + updateFS = false; + return true; + } else { + DBG_OUTPUT_PORT.println("Failed to parse JSON!"); + } + } else { + DBG_OUTPUT_PORT.println("Failed to open \"/stripstate.json\""); + } + } else { + DBG_OUTPUT_PORT.println("Coudnt find \"/stripstate.json\""); + } + //end read + updateFS = false; + return false; +} +#endif diff --git a/clients/HomeAssistant/light.yaml b/clients/HomeAssistant/light.yaml index 918a307..dc43d4f 100644 --- a/clients/HomeAssistant/light.yaml +++ b/clients/HomeAssistant/light.yaml @@ -3,6 +3,7 @@ light: name: "NeoPixel LEDs" state_topic: "home/McLighting01_ha/state/out" command_topic: "home/McLighting01_ha/state/in" + on_command_type: 'first' effect: true effect_list: ###### @@ -62,6 +63,7 @@ light: - "Tricolor Chase" - "ICU" brightness: true + color_temp: true rgb: true optimistic: false qos: 0 @@ -77,7 +79,7 @@ input_number: automation: - id: 71938579813759813757 - alias: NeoPixel Animation Speed + alias: NeoPixel Animation Speed Send initial_state: true hide_entity: false trigger: @@ -85,7 +87,18 @@ automation: platform: state action: - data_template: - payload_template: '{{ trigger.to_state.state | int }}' + payload_template: '{"speed": {{ trigger.to_state.state | int }}}' retain: true - topic: home/McLighting01_ha/speed + topic: home/McLighting01_ha/state/in service: mqtt.publish + + - id: 93786598732698756967 + alias: NeoPixel Animation Speed Receive + trigger: + - platform: mqtt + topic: home/McLighting01_ha/state/out + action: + - data_template: + entity_id: input_number.neopixel_animation_speed + value: '{{ trigger.payload_json.speed | int }}' + service: input_number.set_value \ No newline at end of file