diff --git a/Arduino/McLighting/McLighting.ino b/Arduino/McLighting/McLighting.ino index e64a9a61..88f24174 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 @@ -42,7 +54,30 @@ ESP8266WebServer server(80); WebSocketsServer webSocket = WebSocketsServer(81); +#ifdef HTTP_OTA +#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 // *************************************************************************** @@ -62,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 @@ -77,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 @@ -126,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 // *************************************************************************** @@ -167,12 +210,12 @@ void saveConfigCallback () { // *************************************************************************** #include "colormodes.h" - - // *************************************************************************** // MAIN // *************************************************************************** void setup() { +// system_update_cpu_freq(160); + DBG_OUTPUT_PORT.begin(115200); EEPROM.begin(512); @@ -185,13 +228,30 @@ void setup() { // start ticker with 0.5 because we start in AP mode and try to connect ticker.attach(0.5, tick); + // *************************************************************************** + // Setup: SPIFFS + // *************************************************************************** + SPIFFS.begin(); + { + Dir dir = SPIFFS.openDir("/"); + while (dir.next()) { + String fileName = dir.fileName(); + size_t fileSize = dir.fileSize(); + DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); + } + + FSInfo fs_info; + SPIFFS.info(fs_info); + 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.setBrightness(brightness); strip.setSpeed(convertSpeed(ws2812fx_speed)); //strip.setMode(FX_MODE_RAINBOW_CYCLE); strip.setColor(main_color.red, main_color.green, main_color.blue); @@ -203,25 +263,28 @@ void setup() { // 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); - } - + #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 @@ -230,10 +293,10 @@ void setup() { //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode wifiManager.setAPCallback(configModeCallback); - #ifdef ENABLE_MQTT + #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); @@ -241,6 +304,8 @@ void setup() { 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" @@ -252,7 +317,7 @@ void setup() { delay(1000); } - #ifdef ENABLE_MQTT + #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()); @@ -260,16 +325,25 @@ void setup() { 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(); - } + #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 @@ -284,20 +358,20 @@ void setup() { // *************************************************************************** #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"); }); @@ -315,7 +389,7 @@ void setup() { 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 @@ -328,14 +402,29 @@ void setup() { 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 @@ -352,10 +441,10 @@ void setup() { 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("/upload to upload the webpages first."); DBG_OUTPUT_PORT.println(""); - + // *************************************************************************** // Setup: WebSocket server @@ -363,24 +452,6 @@ void setup() { webSocket.begin(); webSocket.onEvent(webSocketEvent); - - // *************************************************************************** - // Setup: SPIFFS - // *************************************************************************** - SPIFFS.begin(); - { - Dir dir = SPIFFS.openDir("/"); - while (dir.next()) { - String fileName = dir.fileName(); - size_t fileSize = dir.fileSize(); - DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); - } - - FSInfo fs_info; - SPIFFS.info(fs_info); - DBG_OUTPUT_PORT.printf("FS Usage: %d/%d bytes\n\n", fs_info.usedBytes, fs_info.totalBytes); - } - // *************************************************************************** // Setup: SPIFFS Webserver handler // *************************************************************************** @@ -458,11 +529,19 @@ void setup() { brightness = 0; } strip.setBrightness(brightness); - - if (mode == HOLD) { - mode = ALL; - } - + #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,20 @@ 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 } - + getStatusJSON(); }); @@ -511,6 +596,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 +615,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 +634,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 +653,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 +672,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 +691,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 +748,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,16 +770,36 @@ 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 + 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); @@ -595,24 +815,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); @@ -620,8 +859,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) { @@ -629,6 +869,14 @@ 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 == 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); @@ -657,11 +905,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(); } @@ -670,11 +920,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 80445f1c..aab1fbfb 100644 --- a/Arduino/McLighting/definitions.h +++ b/Arduino/McLighting/definitions.h @@ -1,15 +1,33 @@ +//#define USE_NEOANIMATIONFX // Uses NeoAnimationFX, PIN is ignored & set to RX/GPIO3 +#define USE_WS2812FX // Uses WS2812FX + // Neopixel -#define PIN 5 // PIN (14 / D5) where neopixel / WS2811 strip is attached -#define NUMLEDS 24 // Number of leds in the strip +#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 4 // Input pin (4 / D2) for switching the LED strip on / off, connect this PIN to ground to trigger button. +#define BUTTON 0 // 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 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 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 #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) @@ -19,33 +37,45 @@ 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[] = "NeoPixelsStrip"; // MQTT ClientID + const char mqtt_clientid[] = "NeoPixelStrip01"; // 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 @@ -55,7 +85,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, BRIGHTNESS, WIPE, RAINBOW, RAINBOWCYCLE, THEATERCHASE, TWINKLERANDOM, THEATERCHASERAINBOW, TV, CUSTOM }; MODE mode = RAINBOW; // Standard mode that is active when software starts @@ -79,14 +109,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 diff --git a/Arduino/McLighting/request_handlers.h b/Arduino/McLighting/request_handlers.h index 26e84fc4..9dd3a64b 100644 --- a/Arduino/McLighting/request_handlers.h +++ b/Arduino/McLighting/request_handlers.h @@ -1,919 +1,1358 @@ -// *************************************************************************** -// Request handlers -// *************************************************************************** -void getArgs() { - if (server.arg("rgb") != "") { - uint32_t rgb = (uint32_t) strtol(server.arg("rgb").c_str(), NULL, 16); - main_color.red = ((rgb >> 16) & 0xFF); - main_color.green = ((rgb >> 8) & 0xFF); - main_color.blue = ((rgb >> 0) & 0xFF); - } else { - main_color.red = server.arg("r").toInt(); - main_color.green = server.arg("g").toInt(); - main_color.blue = server.arg("b").toInt(); - } - ws2812fx_speed = constrain(server.arg("s").toInt(), 0, 255); - if (server.arg("s") == "") { - ws2812fx_speed = 196; - } - - if (server.arg("m") != "") { - ws2812fx_mode = constrain(server.arg("m").toInt(), 0, strip.getModeCount() - 1); - } - - main_color.red = constrain(main_color.red, 0, 255); - main_color.green = constrain(main_color.green, 0, 255); - main_color.blue = constrain(main_color.blue, 0, 255); - - DBG_OUTPUT_PORT.print("Mode: "); - DBG_OUTPUT_PORT.print(mode); - DBG_OUTPUT_PORT.print(", Color: "); - DBG_OUTPUT_PORT.print(main_color.red); - DBG_OUTPUT_PORT.print(", "); - DBG_OUTPUT_PORT.print(main_color.green); - DBG_OUTPUT_PORT.print(", "); - DBG_OUTPUT_PORT.print(main_color.blue); - DBG_OUTPUT_PORT.print(", Speed:"); - DBG_OUTPUT_PORT.print(ws2812fx_speed); - DBG_OUTPUT_PORT.print(", Brightness:"); - DBG_OUTPUT_PORT.println(brightness); -} - - -long convertSpeed(int mcl_speed) { - long ws2812_speed = mcl_speed * 256; - ws2812_speed = SPEED_MAX - ws2812_speed; - if (ws2812_speed < SPEED_MIN) { - ws2812_speed = SPEED_MIN; - } - if (ws2812_speed > SPEED_MAX) { - ws2812_speed = SPEED_MAX; - } - return ws2812_speed; -} - - -// *************************************************************************** -// Handler functions for WS and MQTT -// *************************************************************************** -void handleSetMainColor(uint8_t * mypayload) { - // decode rgb data - uint32_t rgb = (uint32_t) strtol((const char *) &mypayload[1], NULL, 16); - main_color.red = ((rgb >> 16) & 0xFF); - main_color.green = ((rgb >> 8) & 0xFF); - main_color.blue = ((rgb >> 0) & 0xFF); - strip.setColor(main_color.red, main_color.green, main_color.blue); -} - -void handleSetAllMode(uint8_t * mypayload) { - // decode rgb data - uint32_t rgb = (uint32_t) strtol((const char *) &mypayload[1], NULL, 16); - - main_color.red = ((rgb >> 16) & 0xFF); - main_color.green = ((rgb >> 8) & 0xFF); - main_color.blue = ((rgb >> 0) & 0xFF); - - for (int i = 0; i < strip.numPixels(); i++) { - strip.setPixelColor(i, main_color.red, main_color.green, main_color.blue); - } - strip.show(); - DBG_OUTPUT_PORT.printf("WS: Set all leds to main color: [%u] [%u] [%u]\n", main_color.red, main_color.green, main_color.blue); - exit_func = true; - mode = ALL; -} - -void handleSetSingleLED(uint8_t * mypayload, uint8_t firstChar = 0) { - // decode led index - char templed[3]; - strncpy (templed, (const char *) &mypayload[firstChar], 2 ); - uint8_t led = atoi(templed); - - DBG_OUTPUT_PORT.printf("led value: [%i]. Entry threshold: <= [%i] (=> %s)\n", led, strip.numPixels(), mypayload ); - if (led <= strip.numPixels()) { - char redhex[3]; - char greenhex[3]; - char bluehex[3]; - strncpy (redhex, (const char *) &mypayload[2 + firstChar], 2 ); - strncpy (greenhex, (const char *) &mypayload[4 + firstChar], 2 ); - strncpy (bluehex, (const char *) &mypayload[6 + firstChar], 2 ); - ledstates[led].red = strtol(redhex, NULL, 16); - ledstates[led].green = strtol(greenhex, NULL, 16); - ledstates[led].blue = strtol(bluehex, NULL, 16); - DBG_OUTPUT_PORT.printf("rgb.red: [%s] rgb.green: [%s] rgb.blue: [%s]\n", redhex, greenhex, bluehex); - DBG_OUTPUT_PORT.printf("rgb.red: [%i] rgb.green: [%i] rgb.blue: [%i]\n", strtol(redhex, NULL, 16), strtol(greenhex, NULL, 16), strtol(bluehex, NULL, 16)); - DBG_OUTPUT_PORT.printf("WS: Set single led [%i] to [%i] [%i] [%i] (%s)!\n", led, ledstates[led].red, ledstates[led].green, ledstates[led].blue, mypayload); - - - strip.setPixelColor(led, ledstates[led].red, ledstates[led].green, ledstates[led].blue); - strip.show(); - } - exit_func = true; - mode = CUSTOM; -} - -void handleSetDifferentColors(uint8_t * mypayload) { - uint8_t* nextCommand = 0; - nextCommand = (uint8_t*) strtok((char*) mypayload, "+"); - while (nextCommand) { - handleSetSingleLED(nextCommand, 0); - nextCommand = (uint8_t*) strtok(NULL, "+"); - } -} - -void handleRangeDifferentColors(uint8_t * mypayload) { - uint8_t* nextCommand = 0; - nextCommand = (uint8_t*) strtok((char*) mypayload, "R"); - // While there is a range to process R0110<00ff00> - - while (nextCommand) { - // Loop for each LED. - char startled[3] = { 0, 0, 0 }; - char endled[3] = { 0, 0, 0 }; - char colorval[7] = { 0, 0, 0, 0, 0, 0, 0 }; - strncpy ( startled, (const char *) &nextCommand[0], 2 ); - strncpy ( endled, (const char *) &nextCommand[2], 2 ); - strncpy ( colorval, (const char *) &nextCommand[4], 6 ); - int rangebegin = atoi(startled); - int rangeend = atoi(endled); - DBG_OUTPUT_PORT.printf("Setting RANGE from [%i] to [%i] as color [%s] \n", rangebegin, rangeend, colorval); - - while ( rangebegin <= rangeend ) { - char rangeData[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - if ( rangebegin < 10 ) { - // Create the valid 'nextCommand' structure - sprintf(rangeData, "0%d%s", rangebegin, colorval); - } - if ( rangebegin >= 10 ) { - // Create the valid 'nextCommand' structure - sprintf(rangeData, "%d%s", rangebegin, colorval); - } - // Set one LED - handleSetSingleLED((uint8_t*) rangeData, 0); - rangebegin++; - } - - // Next Range at R - nextCommand = (uint8_t*) strtok(NULL, "R"); - } -} - -void setModeByStateString(String saved_state_string) { - String str_mode = getValue(saved_state_string, '|', 1); - mode = static_cast(str_mode.toInt()); - String str_ws2812fx_mode = getValue(saved_state_string, '|', 2); - ws2812fx_mode = str_ws2812fx_mode.toInt(); - String str_ws2812fx_speed = getValue(saved_state_string, '|', 3); - ws2812fx_speed = str_ws2812fx_speed.toInt(); - String str_brightness = getValue(saved_state_string, '|', 4); - brightness = str_brightness.toInt(); - String str_red = getValue(saved_state_string, '|', 5); - main_color.red = str_red.toInt(); - String str_green = getValue(saved_state_string, '|', 6); - main_color.green = str_green.toInt(); - String str_blue = getValue(saved_state_string, '|', 7); - main_color.blue = str_blue.toInt(); - - DBG_OUTPUT_PORT.printf("ws2812fx_mode: %d\n", ws2812fx_mode); - DBG_OUTPUT_PORT.printf("ws2812fx_speed: %d\n", ws2812fx_speed); - DBG_OUTPUT_PORT.printf("brightness: %d\n", brightness); - DBG_OUTPUT_PORT.printf("main_color.red: %d\n", main_color.red); - DBG_OUTPUT_PORT.printf("main_color.green: %d\n", main_color.green); - DBG_OUTPUT_PORT.printf("main_color.blue: %d\n", main_color.blue); - - strip.setMode(ws2812fx_mode); - strip.setSpeed(convertSpeed(ws2812fx_speed)); - strip.setBrightness(brightness); - strip.setColor(main_color.red, main_color.green, main_color.blue); -} - -void handleSetNamedMode(String str_mode) { - exit_func = true; - - if (str_mode.startsWith("=off")) { - mode = OFF; - #ifdef ENABLE_HOMEASSISTANT - stateOn = false; - #endif - } - if (str_mode.startsWith("=all")) { - mode = ALL; - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - #endif - } - if (str_mode.startsWith("=wipe")) { - mode = WIPE; - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - #endif - } - if (str_mode.startsWith("=rainbow")) { - mode = RAINBOW; - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - #endif - } - if (str_mode.startsWith("=rainbowCycle")) { - mode = RAINBOWCYCLE; - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - #endif - } - if (str_mode.startsWith("=theaterchase")) { - mode = THEATERCHASE; - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - #endif - } - if (str_mode.startsWith("=twinkleRandom")) { - mode = TWINKLERANDOM; - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - #endif - } - if (str_mode.startsWith("=theaterchaseRainbow")) { - mode = THEATERCHASERAINBOW; - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - #endif - } - if (str_mode.startsWith("=tv")) { - mode = TV; - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - #endif - } -} - -void handleSetWS2812FXMode(uint8_t * mypayload) { - mode = HOLD; - uint8_t ws2812fx_mode = (uint8_t) strtol((const char *) &mypayload[1], NULL, 10); - ws2812fx_mode = constrain(ws2812fx_mode, 0, 255); - strip.setColor(main_color.red, main_color.green, main_color.blue); - strip.setMode(ws2812fx_mode); - strip.start(); -} - -char* listStatusJSON() { - char json[255]; - - char modeName[30]; - strncpy_P(modeName, (PGM_P)strip.getModeName(strip.getMode()), sizeof(modeName)); // copy from progmem - - snprintf(json, sizeof(json), "{\"mode\":%d, \"ws2812fx_mode\":%d, \"ws2812fx_mode_name\":\"%s\", \"speed\":%d, \"brightness\":%d, \"color\":[%d, %d, %d]}", - mode, strip.getMode(), modeName, ws2812fx_speed, brightness, main_color.red, main_color.green, main_color.blue); - return json; -} - -void getStatusJSON() { - server.send ( 200, "application/json", listStatusJSON() ); -} - -String listModesJSON() { - String modes = "["; - for (uint8_t i = 0; i < strip.getModeCount(); i++) { - modes += "{\"mode\":"; - modes += i; - modes += ", \"name\":\""; - modes += strip.getModeName(i); - modes += "\"},"; - } - modes += "{}]"; - return modes; -} - -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 -// *************************************************************************** -void handleMinimalUpload() { - char temp[1500]; - - snprintf ( temp, 1500, - "\ - \ - \ - ESP8266 Upload\ - \ - \ - \ - \ - \ -
\ - \ - \ - \ -
\ - \ - " - ); - server.send ( 200, "text/html", temp ); -} - -void handleNotFound() { - String message = "File Not Found\n\n"; - message += "URI: "; - message += server.uri(); - message += "\nMethod: "; - message += ( server.method() == HTTP_GET ) ? "GET" : "POST"; - message += "\nArguments: "; - message += server.args(); - message += "\n"; - for ( uint8_t i = 0; i < server.args(); i++ ) { - message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n"; - } - server.send ( 404, "text/plain", message ); -} - -// automatic cycling -Ticker autoTicker; -int autoCount = 0; - -void autoTick() { - strip.setColor(autoParams[autoCount][0]); - strip.setSpeed(convertSpeed((uint8_t)autoParams[autoCount][1])); - strip.setMode((uint8_t)autoParams[autoCount][2]); - autoTicker.once((float)autoParams[autoCount][3], autoTick); - DBG_OUTPUT_PORT.print("autoTick "); - DBG_OUTPUT_PORT.println(autoCount); - - autoCount++; - if (autoCount >= (sizeof(autoParams) / sizeof(autoParams[0]))) autoCount = 0; -} - -void handleAutoStart() { - autoCount = 0; - autoTick(); - strip.start(); -} - -void handleAutoStop() { - autoTicker.detach(); - strip.stop(); -} - -// *************************************************************************** -// WS request handlers -// *************************************************************************** -void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) { - switch (type) { - case WStype_DISCONNECTED: - DBG_OUTPUT_PORT.printf("WS: [%u] Disconnected!\n", num); - break; - - case WStype_CONNECTED: { - IPAddress ip = webSocket.remoteIP(num); - DBG_OUTPUT_PORT.printf("WS: [%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); - - // send message to client - webSocket.sendTXT(num, "Connected"); - } - break; - - case WStype_TEXT: - DBG_OUTPUT_PORT.printf("WS: [%u] get Text: %s\n", num, payload); - - // # ==> Set main color - if (payload[0] == '#') { - 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_HOMEASSISTANT - stateOn = true; - sendState(); - #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("WS: Set speed to: [%u]\n", ws2812fx_speed); - webSocket.sendTXT(num, "OK"); - } - - // % ==> Set brightness - if (payload[0] == '%') { - uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); - brightness = ((b >> 0) & 0xFF); - DBG_OUTPUT_PORT.printf("WS: Set brightness to: [%u]\n", brightness); - strip.setBrightness(brightness); - webSocket.sendTXT(num, "OK"); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #endif - } - - // * ==> Set main color and light all LEDs (Shortcut) - if (payload[0] == '*') { - handleSetAllMode(payload); - webSocket.sendTXT(num, "OK"); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #endif - } - - // ! ==> Set single LED in given color - if (payload[0] == '!') { - handleSetSingleLED(payload, 1); - webSocket.sendTXT(num, "OK"); - } - - // + ==> Set multiple LED in the given colors - if (payload[0] == '+') { - handleSetDifferentColors(payload); - webSocket.sendTXT(num, "OK"); - } - - // + ==> Set range of LEDs in the given color - if (payload[0] == 'R') { - handleRangeDifferentColors(payload); - webSocket.sendTXT(num, "OK"); - } - - // = ==> Activate named mode - if (payload[0] == '=') { - // we get mode data - String str_mode = String((char *) &payload[0]); - - handleSetNamedMode(str_mode); - - DBG_OUTPUT_PORT.printf("Activated mode [%u]!\n", mode); - webSocket.sendTXT(num, "OK"); - #ifdef ENABLE_HOMEASSISTANT - sendState(); - #endif - } - - // $ ==> Get status Info. - if (payload[0] == '$') { - DBG_OUTPUT_PORT.printf("Get status info."); - - String json = listStatusJSON(); - DBG_OUTPUT_PORT.println(json); - webSocket.sendTXT(num, json); - } - - // ~ ==> Get WS2812 modes. - if (payload[0] == '~') { - DBG_OUTPUT_PORT.printf("Get WS2812 modes."); - - String json = listModesJSON(); - DBG_OUTPUT_PORT.println(json); - webSocket.sendTXT(num, json); - } - - // / ==> Set WS2812 mode. - if (payload[0] == '/') { - handleSetWS2812FXMode(payload); - webSocket.sendTXT(num, "OK"); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #endif - } - - // start auto cycling - if (strcmp((char *)payload, "start") == 0 ) { - handleAutoStart(); - webSocket.sendTXT(num, "OK"); - } - - // stop auto cycling - if (strcmp((char *)payload, "stop") == 0 ) { - handleAutoStop(); - webSocket.sendTXT(num, "OK"); - } - break; - } -} - -void checkForRequests() { - webSocket.loop(); - server.handleClient(); - #ifdef ENABLE_MQTT - mqtt_client.loop(); - #endif -} - - -// *************************************************************************** -// MQTT callback / connection handler -// *************************************************************************** -#ifdef ENABLE_MQTT - - #ifdef ENABLE_HOMEASSISTANT - - void temp2rgb(unsigned int kelvin) { - int tmp_internal = kelvin / 100.0; - - // red - if (tmp_internal <= 66) { - main_color.red = 255; - } else { - float tmp_red = 329.698727446 * pow(tmp_internal - 60, -0.1332047592); - if (tmp_red < 0) { - main_color.red = 0; - } else if (tmp_red > 255) { - main_color.red = 255; - } else { - main_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; - } else if (tmp_green > 255) { - main_color.green = 255; - } else { - main_color.green = tmp_green; - } - } else { - float tmp_green = 288.1221695283 * pow(tmp_internal - 60, -0.0755148492); - if (tmp_green < 0) { - main_color.green = 0; - } else if (tmp_green > 255) { - main_color.green = 255; - } else { - main_color.green = tmp_green; - } - } - - // blue - if (tmp_internal >= 66) { - main_color.blue = 255; - } else if (tmp_internal <= 19) { - main_color.blue = 0; - } else { - float tmp_blue = 138.5177312231 * log(tmp_internal - 10) - 305.0447927307; - if (tmp_blue < 0) { - main_color.blue = 0; - } else if (tmp_blue > 255) { - main_color.blue = 255; - } else { - main_color.blue = tmp_blue; - } - } - - //handleSetWS2812FXMode((uint8_t *) "/0"); - strip.setColor(main_color.red, main_color.green, main_color.blue); - strip.start(); - - } - - bool processJson(char* message) { - StaticJsonBuffer jsonBuffer; - - JsonObject& root = jsonBuffer.parseObject(message); - - if (!root.success()) { - Serial.println("parseObject() failed"); - return false; - } - - if (root.containsKey("state")) { - if (strcmp(root["state"], 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) { - stateOn = false; - mode = OFF; - animation_on = false; - strip.start(); - } - } - - 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(); - } - - if (root.containsKey("color_temp")) { - //temp comes in as mireds, need to convert to kelvin then to RGB - int color_temp = root["color_temp"]; - unsigned int kelvin = 1000000 / color_temp; - - temp2rgb(kelvin); - } - - 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); - } - - if (root.containsKey("effect")) { - animation_on = true; - effectString = String((const char *)root["effect"]); - - 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(); - break; - } - } - } - - return true; - } - #endif - - 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); - - #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) { - #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); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #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); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - } - - // % ==> 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); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #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); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #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); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - } - - // + ==> Set multiple LED in the given colors - if (payload[0] == '+') { - handleSetDifferentColors(payload); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - } - - // 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); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - } - - // = ==> 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); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - #ifdef ENABLE_HOMEASSISTANT - sendState(); - #endif - } - - // $ ==> Get status Info. - if (payload[0] == '$') { - DBG_OUTPUT_PORT.printf("MQTT: Get status info.\n"); - DBG_OUTPUT_PORT.println("MQTT: Out: " + String(listStatusJSON())); - mqtt_client.publish(mqtt_outtopic, listStatusJSON()); - } - - // ~ ==> 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"); - 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()); - } - - // / ==> Set WS2812 mode. - if (payload[0] == '/') { - handleSetWS2812FXMode(payload); - DBG_OUTPUT_PORT.printf("MQTT: Set WS2812 mode [%s]\n", payload); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); - #ifdef ENABLE_HOMEASSISTANT - stateOn = true; - sendState(); - #endif - } - - #ifdef ENABLE_HOMEASSISTANT - } - #endif - free(payload); - } - - 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 ...\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!"); - // Once connected, publish an announcement... - char * message = new char[18 + strlen(HOSTNAME) + 1]; - strcpy(message, "McLighting ready: "); - strcat(message, HOSTNAME); - mqtt_client.publish(mqtt_outtopic, message); - // ... and resubscribe - mqtt_client.subscribe(mqtt_intopic); - #ifdef ENABLE_HOMEASSISTANT - mqtt_client.subscribe(mqtt_ha_state_in.c_str()); - mqtt_client.subscribe(mqtt_ha_speed.c_str()); - #endif - - DBG_OUTPUT_PORT.printf("MQTT topic in: %s\n", mqtt_intopic); - DBG_OUTPUT_PORT.printf("MQTT topic out: %s\n", mqtt_outtopic); - } else { - DBG_OUTPUT_PORT.print("failed, rc="); - DBG_OUTPUT_PORT.print(mqtt_client.state()); - DBG_OUTPUT_PORT.println(" try again in 5 seconds"); - // Wait 5 seconds before retrying - delay(5000); - } - } - if (mqtt_reconnect_retries >= MQTT_MAX_RECONNECT_TRIES) { - DBG_OUTPUT_PORT.printf("MQTT connection failed, giving up after %d tries ...\n", mqtt_reconnect_retries); - } - } -#endif - - -// *************************************************************************** -// Button management -// *************************************************************************** -#ifdef ENABLE_BUTTON - void shortKeyPress() { - DBG_OUTPUT_PORT.printf("Short button press\n"); - if (buttonState == false) { - setModeByStateString(BTN_MODE_SHORT); - buttonState = true; - #ifdef ENABLE_MQTT - mqtt_client.publish(mqtt_outtopic, String("OK =static white").c_str()); - #endif - } else { - mode = OFF; - buttonState = false; - #ifdef ENABLE_MQTT - mqtt_client.publish(mqtt_outtopic, String("OK =off").c_str()); - #ifdef ENABLE_HOMEASSISTANT - stateOn = false; - sendState(); - #endif - #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 - } - - // 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 - } - - 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; - } - else if ((prevKeyState == LOW) && (currKeyState == HIGH)) { - if (KeyPressCount < longKeyPressCountMax && KeyPressCount >= mediumKeyPressCountMin) { - mediumKeyPress(); - } - else { - if (KeyPressCount < mediumKeyPressCountMin) { - shortKeyPress(); - } - } - } - else if (currKeyState == LOW) { - KeyPressCount++; - if (KeyPressCount >= longKeyPressCountMax) { - longKeyPress(); - } - } - prevKeyState = currKeyState; - } - } +// *************************************************************************** +// 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); + main_color.red = ((rgb >> 16) & 0xFF); + main_color.green = ((rgb >> 8) & 0xFF); + main_color.blue = ((rgb >> 0) & 0xFF); + } else { + main_color.red = server.arg("r").toInt(); + main_color.green = server.arg("g").toInt(); + main_color.blue = server.arg("b").toInt(); + } + ws2812fx_speed = constrain(server.arg("s").toInt(), 0, 255); + if (server.arg("s") == "") { + ws2812fx_speed = 196; + } + + if (server.arg("m") != "") { + ws2812fx_mode = constrain(server.arg("m").toInt(), 0, strip.getModeCount() - 1); + } + + main_color.red = constrain(main_color.red, 0, 255); + main_color.green = constrain(main_color.green, 0, 255); + main_color.blue = constrain(main_color.blue, 0, 255); + + DBG_OUTPUT_PORT.print("Mode: "); + DBG_OUTPUT_PORT.print(mode); + DBG_OUTPUT_PORT.print(", Color: "); + DBG_OUTPUT_PORT.print(main_color.red); + DBG_OUTPUT_PORT.print(", "); + DBG_OUTPUT_PORT.print(main_color.green); + DBG_OUTPUT_PORT.print(", "); + DBG_OUTPUT_PORT.print(main_color.blue); + DBG_OUTPUT_PORT.print(", Speed:"); + DBG_OUTPUT_PORT.print(ws2812fx_speed); + DBG_OUTPUT_PORT.print(", Brightness:"); + DBG_OUTPUT_PORT.println(brightness); +} + + +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; + } + if (ws2812_speed > SPEED_MAX) { + ws2812_speed = SPEED_MAX; + } + return ws2812_speed; +} + + +// *************************************************************************** +// Handler functions for WS and MQTT +// *************************************************************************** +void handleSetMainColor(uint8_t * mypayload) { + // decode rgb data + uint32_t rgb = (uint32_t) strtol((const char *) &mypayload[1], NULL, 16); + main_color.red = ((rgb >> 16) & 0xFF); + main_color.green = ((rgb >> 8) & 0xFF); + main_color.blue = ((rgb >> 0) & 0xFF); + strip.setColor(main_color.red, main_color.green, main_color.blue); +} + +void handleSetAllMode(uint8_t * mypayload) { + // decode rgb data + uint32_t rgb = (uint32_t) strtol((const char *) &mypayload[1], NULL, 16); + + main_color.red = ((rgb >> 16) & 0xFF); + main_color.green = ((rgb >> 8) & 0xFF); + main_color.blue = ((rgb >> 0) & 0xFF); + + for (int i = 0; i < strip.numPixels(); i++) { + strip.setPixelColor(i, main_color.red, main_color.green, main_color.blue); + } + strip.show(); + DBG_OUTPUT_PORT.printf("WS: Set all leds to main color: [%u] [%u] [%u]\n", main_color.red, main_color.green, main_color.blue); + exit_func = true; + mode = ALL; +} + +void handleSetSingleLED(uint8_t * mypayload, uint8_t firstChar = 0) { + // decode led index + char templed[3]; + strncpy (templed, (const char *) &mypayload[firstChar], 2 ); + uint8_t led = atoi(templed); + + DBG_OUTPUT_PORT.printf("led value: [%i]. Entry threshold: <= [%i] (=> %s)\n", led, strip.numPixels(), mypayload ); + if (led <= strip.numPixels()) { + char redhex[3]; + char greenhex[3]; + char bluehex[3]; + strncpy (redhex, (const char *) &mypayload[2 + firstChar], 2 ); + strncpy (greenhex, (const char *) &mypayload[4 + firstChar], 2 ); + strncpy (bluehex, (const char *) &mypayload[6 + firstChar], 2 ); + ledstates[led].red = strtol(redhex, NULL, 16); + ledstates[led].green = strtol(greenhex, NULL, 16); + ledstates[led].blue = strtol(bluehex, NULL, 16); + DBG_OUTPUT_PORT.printf("rgb.red: [%s] rgb.green: [%s] rgb.blue: [%s]\n", redhex, greenhex, bluehex); + DBG_OUTPUT_PORT.printf("rgb.red: [%i] rgb.green: [%i] rgb.blue: [%i]\n", strtol(redhex, NULL, 16), strtol(greenhex, NULL, 16), strtol(bluehex, NULL, 16)); + DBG_OUTPUT_PORT.printf("WS: Set single led [%i] to [%i] [%i] [%i] (%s)!\n", led, ledstates[led].red, ledstates[led].green, ledstates[led].blue, mypayload); + + + strip.setPixelColor(led, ledstates[led].red, ledstates[led].green, ledstates[led].blue); + strip.show(); + } + exit_func = true; + mode = CUSTOM; +} + +void handleSetDifferentColors(uint8_t * mypayload) { + uint8_t* nextCommand = 0; + nextCommand = (uint8_t*) strtok((char*) mypayload, "+"); + while (nextCommand) { + handleSetSingleLED(nextCommand, 0); + nextCommand = (uint8_t*) strtok(NULL, "+"); + } +} + +void handleRangeDifferentColors(uint8_t * mypayload) { + uint8_t* nextCommand = 0; + nextCommand = (uint8_t*) strtok((char*) mypayload, "R"); + // While there is a range to process R0110<00ff00> + + while (nextCommand) { + // Loop for each LED. + char startled[3] = { 0, 0, 0 }; + char endled[3] = { 0, 0, 0 }; + char colorval[7] = { 0, 0, 0, 0, 0, 0, 0 }; + strncpy ( startled, (const char *) &nextCommand[0], 2 ); + strncpy ( endled, (const char *) &nextCommand[2], 2 ); + strncpy ( colorval, (const char *) &nextCommand[4], 6 ); + int rangebegin = atoi(startled); + int rangeend = atoi(endled); + DBG_OUTPUT_PORT.printf("Setting RANGE from [%i] to [%i] as color [%s] \n", rangebegin, rangeend, colorval); + + while ( rangebegin <= rangeend ) { + char rangeData[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + if ( rangebegin < 10 ) { + // Create the valid 'nextCommand' structure + sprintf(rangeData, "0%d%s", rangebegin, colorval); + } + if ( rangebegin >= 10 ) { + // Create the valid 'nextCommand' structure + sprintf(rangeData, "%d%s", rangebegin, colorval); + } + // Set one LED + handleSetSingleLED((uint8_t*) rangeData, 0); + rangebegin++; + } + + // Next Range at R + nextCommand = (uint8_t*) strtok(NULL, "R"); + } +} + +void setModeByStateString(String saved_state_string) { + String str_mode = getValue(saved_state_string, '|', 1); + mode = static_cast(str_mode.toInt()); + String str_ws2812fx_mode = getValue(saved_state_string, '|', 2); + ws2812fx_mode = str_ws2812fx_mode.toInt(); + String str_ws2812fx_speed = getValue(saved_state_string, '|', 3); + ws2812fx_speed = str_ws2812fx_speed.toInt(); + String str_brightness = getValue(saved_state_string, '|', 4); + brightness = str_brightness.toInt(); + String str_red = getValue(saved_state_string, '|', 5); + main_color.red = str_red.toInt(); + String str_green = getValue(saved_state_string, '|', 6); + main_color.green = str_green.toInt(); + String str_blue = getValue(saved_state_string, '|', 7); + main_color.blue = str_blue.toInt(); + + DBG_OUTPUT_PORT.printf("ws2812fx_mode: %d\n", ws2812fx_mode); + DBG_OUTPUT_PORT.printf("ws2812fx_speed: %d\n", ws2812fx_speed); + DBG_OUTPUT_PORT.printf("brightness: %d\n", brightness); + DBG_OUTPUT_PORT.printf("main_color.red: %d\n", main_color.red); + DBG_OUTPUT_PORT.printf("main_color.green: %d\n", main_color.green); + DBG_OUTPUT_PORT.printf("main_color.blue: %d\n", main_color.blue); + + strip.setMode(ws2812fx_mode); + strip.setSpeed(convertSpeed(ws2812fx_speed)); + strip.setBrightness(brightness); + strip.setColor(main_color.red, main_color.green, main_color.blue); +} + +void handleSetNamedMode(String str_mode) { + exit_func = true; + + if (str_mode.startsWith("=off")) { + mode = OFF; + #ifdef ENABLE_HOMEASSISTANT + stateOn = false; + #endif + } + if (str_mode.startsWith("=all")) { + mode = ALL; + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + } + if (str_mode.startsWith("=wipe")) { + mode = WIPE; + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + } + if (str_mode.startsWith("=rainbow")) { + mode = RAINBOW; + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + } + if (str_mode.startsWith("=rainbowCycle")) { + mode = RAINBOWCYCLE; + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + } + if (str_mode.startsWith("=theaterchase")) { + mode = THEATERCHASE; + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + } + if (str_mode.startsWith("=twinkleRandom")) { + mode = TWINKLERANDOM; + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + } + if (str_mode.startsWith("=theaterchaseRainbow")) { + mode = THEATERCHASERAINBOW; + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + } + if (str_mode.startsWith("=tv")) { + mode = TV; + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + #endif + } +} + +void handleSetWS2812FXMode(uint8_t * mypayload) { + mode = HOLD; + uint8_t ws2812fx_mode = (uint8_t) strtol((const char *) &mypayload[1], NULL, 10); + ws2812fx_mode = constrain(ws2812fx_mode, 0, 255); + strip.setColor(main_color.red, main_color.green, main_color.blue); + strip.setMode(ws2812fx_mode); + strip.start(); +} + +char* listStatusJSON() { + char json[255]; + + char modeName[30]; + strncpy_P(modeName, (PGM_P)strip.getModeName(strip.getMode()), sizeof(modeName)); // copy from progmem + + snprintf(json, sizeof(json), "{\"mode\":%d, \"ws2812fx_mode\":%d, \"ws2812fx_mode_name\":\"%s\", \"speed\":%d, \"brightness\":%d, \"color\":[%d, %d, %d]}", + mode, strip.getMode(), modeName, ws2812fx_speed, brightness, main_color.red, main_color.green, main_color.blue); + return json; +} + +void getStatusJSON() { + server.send ( 200, "application/json", listStatusJSON() ); +} + +String listModesJSON() { + String modes = "["; + for (uint8_t i = 0; i < strip.getModeCount(); i++) { + modes += "{\"mode\":"; + modes += i; + modes += ", \"name\":\""; + modes += strip.getModeName(i); + modes += "\"},"; + } + modes += "{}]"; + return modes; +} + +void getModesJSON() { + server.send ( 200, "application/json", listModesJSON() ); +} + +// *************************************************************************** +// HTTP request handlers +// *************************************************************************** +void handleMinimalUpload() { + char temp[1500]; + + snprintf ( temp, 1500, + "\ + \ + \ + ESP8266 Upload\ + \ + \ + \ + \ + \ +
\ + \ + \ + \ +
\ + \ + " + ); + server.send ( 200, "text/html", temp ); +} + +void handleNotFound() { + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += ( server.method() == HTTP_GET ) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + for ( uint8_t i = 0; i < server.args(); i++ ) { + message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n"; + } + server.send ( 404, "text/plain", message ); +} + +// automatic cycling +Ticker autoTicker; +int autoCount = 0; + +void autoTick() { + strip.setColor(autoParams[autoCount][0]); + strip.setSpeed(convertSpeed((uint8_t)autoParams[autoCount][1])); + strip.setMode((uint8_t)autoParams[autoCount][2]); + autoTicker.once((float)autoParams[autoCount][3], autoTick); + DBG_OUTPUT_PORT.print("autoTick "); + DBG_OUTPUT_PORT.println(autoCount); + + autoCount++; + if (autoCount >= (sizeof(autoParams) / sizeof(autoParams[0]))) autoCount = 0; +} + +void handleAutoStart() { + autoCount = 0; + autoTick(); + strip.start(); +} + +void handleAutoStop() { + autoTicker.detach(); + strip.stop(); +} + +// *************************************************************************** +// WS request handlers +// *************************************************************************** +void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) { + switch (type) { + case WStype_DISCONNECTED: + DBG_OUTPUT_PORT.printf("WS: [%u] Disconnected!\n", num); + break; + + case WStype_CONNECTED: { + IPAddress ip = webSocket.remoteIP(num); + DBG_OUTPUT_PORT.printf("WS: [%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); + + // send message to client + webSocket.sendTXT(num, "Connected"); + } + break; + + case WStype_TEXT: + DBG_OUTPUT_PORT.printf("WS: [%u] get Text: %s\n", num, payload); + + // # ==> Set main color + if (payload[0] == '#') { + 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; + 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("WS: Set speed to: [%u]\n", ws2812fx_speed); + 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 brightness + if (payload[0] == '%') { + uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); + brightness = ((b >> 0) & 0xFF); + 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; + 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); + 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; + 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); + 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 + if (payload[0] == '=') { + // we get mode data + String str_mode = String((char *) &payload[0]); + + handleSetNamedMode(str_mode); + + 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 + 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("Get status info."); + + 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. + if (payload[0] == '~') { + DBG_OUTPUT_PORT.printf("Get WS2812 modes."); + + 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; + 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 + } + + // start auto cycling + if (strcmp((char *)payload, "start") == 0 ) { + handleAutoStart(); + webSocket.sendTXT(num, "OK"); + } + + // stop auto cycling + if (strcmp((char *)payload, "stop") == 0 ) { + handleAutoStop(); + webSocket.sendTXT(num, "OK"); + } + break; + } +} + +void checkForRequests() { + webSocket.loop(); + server.handleClient(); + #ifdef ENABLE_MQTT + mqtt_client.loop(); + #endif +} + + +// *************************************************************************** +// MQTT callback / connection handler +// *************************************************************************** +#if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) + + #ifdef ENABLE_HOMEASSISTANT + + LEDState temp2rgb(unsigned int kelvin) { + int tmp_internal = kelvin / 100.0; + LEDState tmp_color; + + // red + if (tmp_internal <= 66) { + tmp_color.red = 255; + } else { + float tmp_red = 329.698727446 * pow(tmp_internal - 60, -0.1332047592); + if (tmp_red < 0) { + tmp_color.red = 0; + } else if (tmp_red > 255) { + tmp_color.red = 255; + } else { + tmp_color.red = tmp_red; + } + } + + // green + if (tmp_internal <= 66) { + float tmp_green = 99.4708025861 * log(tmp_internal) - 161.1195681661; + if (tmp_green < 0) { + tmp_color.green = 0; + } else if (tmp_green > 255) { + tmp_color.green = 255; + } else { + tmp_color.green = tmp_green; + } + } else { + float tmp_green = 288.1221695283 * pow(tmp_internal - 60, -0.0755148492); + if (tmp_green < 0) { + tmp_color.green = 0; + } else if (tmp_green > 255) { + tmp_color.green = 255; + } else { + tmp_color.green = tmp_green; + } + } + + // blue + if (tmp_internal >= 66) { + tmp_color.blue = 255; + } else if (tmp_internal <= 19) { + tmp_color.blue = 0; + } else { + float tmp_blue = 138.5177312231 * log(tmp_internal - 10) - 305.0447927307; + if (tmp_blue < 0) { + tmp_color.blue = 0; + } else if (tmp_blue > 255) { + tmp_color.blue = 255; + } else { + tmp_color.blue = tmp_blue; + } + } + return tmp_color; + } + + void sendState() { + const size_t bufferSize = JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(5); + //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; + + 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) { + 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()) { + DBG_OUTPUT_PORT.println("parseObject() failed"); + return false; + } + //DBG_OUTPUT_PORT.println("JSON ParseObject() done!"); + + if (root.containsKey("state")) { + const char* state_in = root["state"]; + if (strcmp(state_in, on_cmd) == 0 and !(animation_on)) { + stateOn = true; + mode = ALL; + } + else if (strcmp(state_in, off_cmd) == 0) { + stateOn = false; + animation_on = false; + mode = OFF; + return true; + } + } + + if (root.containsKey("color")) { + 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("color_temp")) { + //temp comes in as mireds, need to convert to kelvin then to RGB + color_temp = (uint16_t) root["color_temp"]; + unsigned int kelvin = 1000000 / color_temp; + 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); + mode = BRIGHTNESS; + } + + if (root.containsKey("effect")) { + animation_on = true; + String effectString = root["effect"].asString(); + + for (uint8_t i = 0; i < strip.getModeCount(); i++) { + if(String(strip.getModeName(i)) == effectString) { + 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; + } + 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 + + } 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)); + #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; + 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_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; + 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; + 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 + 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; + 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 ENABLE_HOMEASSISTANT + } + #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 ...\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!"); + // Once connected, publish an announcement... + char * message = new char[18 + strlen(HOSTNAME) + 1]; + strcpy(message, "McLighting ready: "); + strcat(message, HOSTNAME); + mqtt_client.publish(mqtt_outtopic, message); + // ... and resubscribe + mqtt_client.subscribe(mqtt_intopic, qossub, qossub); + #ifdef ENABLE_HOMEASSISTANT + ha_send_data.detach(); + mqtt_client.subscribe(mqtt_ha_state_in.c_str(), qossub); + mqtt_client.subscribe(mqtt_ha_speed.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 { + DBG_OUTPUT_PORT.print("failed, rc="); + DBG_OUTPUT_PORT.print(mqtt_client.state()); + DBG_OUTPUT_PORT.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } + if (mqtt_reconnect_retries >= MQTT_MAX_RECONNECT_TRIES) { + 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); + uint16_t packetIdSub3 = amqttClient.subscribe((char *)mqtt_ha_speed.c_str(), qossub); + DBG_OUTPUT_PORT.printf("Subscribing at QoS %d, packetId: ", qossub); DBG_OUTPUT_PORT.println(packetIdSub3); + #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 + + +// *************************************************************************** +// Button management +// *************************************************************************** +#ifdef ENABLE_BUTTON + void shortKeyPress() { + DBG_OUTPUT_PORT.printf("Short button press\n"); + if (buttonState == false) { + setModeByStateString(BTN_MODE_SHORT); + buttonState = true; + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String("OK =static white").c_str()); + #endif + } else { + mode = OFF; + buttonState = false; + #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; + 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()); + #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()); + #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; + } + else if ((prevKeyState == LOW) && (currKeyState == HIGH)) { + if (KeyPressCount < longKeyPressCountMax && KeyPressCount >= mediumKeyPressCountMin) { + mediumKeyPress(); + } + else { + if (KeyPressCount < mediumKeyPressCountMin) { + shortKeyPress(); + } + } + } + else if (currKeyState == LOW) { + KeyPressCount++; + if (KeyPressCount >= longKeyPressCountMax) { + longKeyPress(); + } + } + prevKeyState = currKeyState; + } + } +#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 918a307e..7d93e3af 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