diff --git a/Arduino/McLighting/GY33_MCU.cpp b/Arduino/McLighting/GY33_MCU.cpp new file mode 100644 index 0000000..ab0dafa --- /dev/null +++ b/Arduino/McLighting/GY33_MCU.cpp @@ -0,0 +1,240 @@ +/**************************************************************************/ +/*! + @file GY33_MCU.h + @author BPoHVoodoo (FabLab Luenen) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2018, + All rights reserved. + + Driver for the GY-33 MCU digital color sensors. + + @section HISTORY + + v1.0 - First release +*/ +/**************************************************************************/ +#ifdef __AVR + #include +#elif defined(ESP8266) + #include +#endif +#include +#include + +#include "GY33_MCU.h" + +/*========================================================================*/ +/* PRIVATE FUNCTIONS */ +/*========================================================================*/ + +/**************************************************************************/ +/*! + @brief Implements missing powf function +*/ +/**************************************************************************/ +float powf(const float x, const float y) +{ + return (float)(pow((double)x, (double)y)); +} + +/**************************************************************************/ +/*! + @brief Writes a register and an 8 bit value over I2C +*/ +/**************************************************************************/ +void GY33_MCU::write8 (uint8_t reg, uint32_t val) +{ + uint8_t buf[2]; + brzo_i2c_start_transaction(MCU_ADDRESS, SCL_SPEED); + buf[0]=reg; + buf[1]=val; + brzo_i2c_write(buf, 2, true); + brzo_i2c_end_transaction(); +} + +/**************************************************************************/ +/*! + @brief Reads an 8 bit value over I2C +*/ +/**************************************************************************/ +uint8_t GY33_MCU::read8(uint8_t reg) +{ + uint8_t buf[2]; + brzo_i2c_start_transaction(MCU_ADDRESS, SCL_SPEED); + buf[0]=reg; + brzo_i2c_write(buf,1, true); + brzo_i2c_read(buf, 1, false); + brzo_i2c_end_transaction(); + return buf[0]; +} + +/**************************************************************************/ +/*! + @brief Reads a 16 bit values over I2C +*/ +/**************************************************************************/ +uint16_t GY33_MCU::read16(uint8_t reg) +{ + uint16_t x; uint16_t t; + uint8_t buf[2]; + brzo_i2c_start_transaction(MCU_ADDRESS, SCL_SPEED); + buf[0]=reg; + brzo_i2c_write(buf, 1, true); + brzo_i2c_read(buf, 2, false); + brzo_i2c_end_transaction(); + x = buf[0]; + t = buf[1]; + x <<= 8; + x |= t; + return buf[0] << 8 | buf[1]; +} + + +/*========================================================================*/ +/* CONSTRUCTORS */ +/*========================================================================*/ + +/**************************************************************************/ +/*! + Constructor +*/ +/**************************************************************************/ +GY33_MCU::GY33_MCU() +{ + _MCUInitialised = false; +} + +/*========================================================================*/ +/* PUBLIC FUNCTIONS */ +/*========================================================================*/ + +/**************************************************************************/ +/*! + Initializes I2C and configures the sensor (call this function before + doing anything else) +*/ +/**************************************************************************/ +boolean GY33_MCU::begin(void) +{ + brzo_i2c_setup(SDA, SCL, SCL_STRETCH_TIMEOUT); + + /* Make sure we're actually connected */ + uint8_t x = read8(MCU_CONFIG); + Serial.println(x, HEX); + if ((x != 0x00) && (x != 0xFF)) + { + return false; + } + _MCUInitialised = true; + + return true; +} + + +/**************************************************************************/ +/*! + @brief Reads the raw red, green, blue and clear channel values +*/ +/**************************************************************************/ +void GY33_MCU::getRawData (uint16_t *r, uint16_t *g, uint16_t *b, uint16_t *c, uint16_t *ct) +{ + if (!_MCUInitialised) begin(); + + *r = read16(MCU_RDATAH); + *g = read16(MCU_GDATAH); + *b = read16(MCU_BDATAH); + *c = read16(MCU_CDATAH); + *ct = read16(MCU_CTDATAH); +} + + +void GY33_MCU::getData (uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *c) +{ + if (!_MCUInitialised) begin(); + + *r = read8(MCU_RDATA); + *g = read8(MCU_GDATA); + *b = read8(MCU_BDATA); + *c = read8(MCU_COLDATA); +} +/**************************************************************************/ +/*! + @brief Converts the raw R/G/B values to color temperature in degrees + Kelvin +*/ +/**************************************************************************/ +uint16_t GY33_MCU::calculateColorTemperature(uint16_t r, uint16_t g, uint16_t b) +{ + float X, Y, Z; /* RGB to XYZ correlation */ + float xc, yc; /* Chromaticity co-ordinates */ + float n; /* McCamy's formula */ + float cct; + + /* 1. Map RGB values to their XYZ counterparts. */ + /* Based on 6500K fluorescent, 3000K fluorescent */ + /* and 60W incandescent values for a wide range. */ + /* Note: Y = Illuminance or lux */ + X = (-0.14282F * r) + (1.54924F * g) + (-0.95641F * b); + Y = (-0.32466F * r) + (1.57837F * g) + (-0.73191F * b); + Z = (-0.68202F * r) + (0.77073F * g) + ( 0.56332F * b); + + /* 2. Calculate the chromaticity co-ordinates */ + xc = (X) / (X + Y + Z); + yc = (Y) / (X + Y + Z); + + /* 3. Use McCamy's formula to determine the CCT */ + n = (xc - 0.3320F) / (0.1858F - yc); + + /* Calculate the final CCT */ + cct = (449.0F * powf(n, 3)) + (3525.0F * powf(n, 2)) + (6823.3F * n) + 5520.33F; + + /* Return the results in degrees Kelvin */ + return (uint16_t)cct; +} + +/**************************************************************************/ +/*! + @brief Converts the raw R/G/B values to lux +*/ +/**************************************************************************/ +uint16_t GY33_MCU::calculateLux(uint16_t r, uint16_t g, uint16_t b) +{ + float illuminance; + + /* This only uses RGB ... how can we integrate clear or calculate lux */ + /* based exclusively on clear since this might be more reliable? */ + illuminance = (-0.32466F * r) + (1.57837F * g) + (-0.73191F * b); + + return (uint16_t)illuminance; +} + + +/*void GY33_MCU::setInterrupt(boolean i) { + uint8_t r = read8(MCU_ENABLE); + if (i) { + r |= MCU_ENABLE_AIEN; + } else { + r &= ~MCU_ENABLE_AIEN; + } + write8(MCU_ENABLE, r); +} + +void GY33_MCU::clearInterrupt(void) { + Wire.beginTransmission(MCU_ADDRESS); + #if ARDUINO >= 100 + Wire.write(MCU_COMMAND_BIT | 0x66); + #else + Wire.send(MCU_COMMAND_BIT | 0x66); + #endif + Wire.endTransmission(); +} +*/ + +void GY33_MCU::setConfig(uint8_t high, uint8_t low) { +// write8(MCU_CONFIG, high | low); + write8(MCU_CONFIG, 0x11); +} diff --git a/Arduino/McLighting/GY33_MCU.h b/Arduino/McLighting/GY33_MCU.h new file mode 100644 index 0000000..23f8f4b --- /dev/null +++ b/Arduino/McLighting/GY33_MCU.h @@ -0,0 +1,109 @@ +#include + +/**************************************************************************/ +/*! + @file GY33_MCU.h + @author BPoHVoodoo (FabLab Luenen) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2018, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ +#ifndef _MCU_H_ +#define _MCU_H_ + +#if ARDUINO >= 100 + #include +#else + #include +#endif + +#include + +#define MCU_ADDRESS (0x5A) + +#define SCL_SPEED 100 +#define SCL_STRETCH_TIMEOUT 50000 + +#define MCU_LED_OFF (0x00) +#define MCU_LED_1 (0x10) +#define MCU_LED_2 (0x20) +#define MCU_LED_3 (0x30) +#define MCU_LED_4 (0x40) +#define MCU_LED_5 (0x50) +#define MCU_LED_6 (0x60) +#define MCU_LED_7 (0x70) +#define MCU_LED_8 (0x80) +#define MCU_LED_9 (0x90) +#define MCU_LED_10 (0xA0) +#define MCU_WHITE_OFF (0x00) /* No Whitebalance */ +#define MCU_WHITE_ON (0x01) /* Whitebalance */ + +#define MCU_RDATAH (0x00) /* Raw Red channel data */ +#define MCU_RDATAL (0x01) +#define MCU_GDATAH (0x02) /* Raw Green channel data */ +#define MCU_GDATAL (0x03) +#define MCU_BDATAH (0x04) /* Raw Blue channel data */ +#define MCU_BDATAL (0x05) +#define MCU_CDATAH (0x06) /* Clear channel data */ +#define MCU_CDATAL (0x07) +#define MCU_LDATAH (0x08) /* Lux channel data */ +#define MCU_LDATAL (0x09) +#define MCU_CTDATAH (0x0A) /* Colortemperature channel data */ +#define MCU_CTDATAL (0x0B) +#define MCU_RDATA (0x0C) /* Red channel data */ +#define MCU_GDATA (0x0D) /* Green channel data */ +#define MCU_BDATA (0x0E) /* Blue channel data */ +#define MCU_COLDATA (0x0F) /* Blue channel data */ +#define MCU_CONFIG (0x10) + +class GY33_MCU { + public: + GY33_MCU(); + + boolean begin(void); + void getRawData(uint16_t *r, uint16_t *g, uint16_t *b, uint16_t *c, uint16_t *ct); + void getData(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *c); + uint16_t calculateColorTemperature(uint16_t r, uint16_t g, uint16_t b); + uint16_t calculateLux(uint16_t r, uint16_t g, uint16_t b); + void write8 (uint8_t reg, uint32_t val); + uint8_t read8 (uint8_t reg); + uint16_t read16 (uint8_t reg); +/* void setInterrupt(boolean flag); + void clearInterrupt(void);*/ + void setConfig(uint8_t h, uint8_t l); + + private: + boolean _MCUInitialised; + + void disable(void); +}; + +#endif + diff --git a/Arduino/McLighting/McLighting.ino b/Arduino/McLighting/McLighting.ino index f954834..792d655 100644 --- a/Arduino/McLighting/McLighting.ino +++ b/Arduino/McLighting/McLighting.ino @@ -18,20 +18,50 @@ #include //https://github.com/Links2004/arduinoWebSockets #include +#ifdef ENABLE_BUTTON2 + // needed for MCU + #include "GY33_MCU.h" + // *************************************************************************** + // Initialize Color Sensor + // *************************************************************************** + byte gammatable[256]; + GY33_MCU tcs; +#endif + // OTA #ifdef ENABLE_OTA #include #include #endif +//SPIFFS Save +#if !defined(ENABLE_HOMEASSISTANT) and defined(ENABLE_STATE_SAVE_SPIFFS) + #include // +#endif + // MQTT #ifdef ENABLE_MQTT #include + #ifdef ENABLE_HOMEASSISTANT + #include + #endif WiFiClient espClient; 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 @@ -39,34 +69,73 @@ ESP8266WebServer server(80); WebSocketsServer webSocket = WebSocketsServer(81); +#ifdef HTTP_OTA + #include + ESP8266HTTPUpdateServer httpUpdater; +#endif +#ifdef USE_NEOANIMATIONFX // *************************************************************************** -// Load libraries / Instanciate WS2812FX library +// Load libraries / Instanciate NeoAnimationFX library // *************************************************************************** -// https://github.com/kitesurfer1404/WS2812FX -#include "WS2812FX.h" -WS2812FX strip = WS2812FX(NUMLEDS, PIN, NEO_GRBW + NEO_KHZ800); +// https://github.com/debsahu/NeoAnimationFX +#include +#define NEOMETHOD NeoPBBGRB800 -// Parameter 1 = number of pixels in strip -// Parameter 2 = Arduino pin number (most are valid) -// Parameter 3 = pixel type flags, add together as needed: -// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) -// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) -// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) -// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +NEOMETHOD neoStrip(NUMLEDS); +NeoAnimationFX strip(neoStrip); -// IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across -// 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. +// 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) +// Uses Pin D4 / GPIO2 (Only pin that is supported, due to hardware limitations) +// NEOMETHOD NeoPBBGRBU800 uses GRB config 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEOMETHOD NeoPBBGRBU400 uses GRB config 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEOMETHOD NeoPBBRGBU800 uses RGB config 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEOMETHOD NeoPBBRGBU400 uses RGB config 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) + +#endif + +#ifdef USE_WS2812FX + // *************************************************************************** + // Load libraries / Instanciate WS2812FX library + // *************************************************************************** + // https://github.com/kitesurfer1404/WS2812FX + #include "WS2812FX.h" + WS2812FX strip = WS2812FX(NUMLEDS, PIN, NEO_GRBW + NEO_KHZ800); + + // Parameter 1 = number of pixels in strip + // Parameter 2 = Arduino pin number (most are valid) + // Parameter 3 = pixel type flags, add together as needed: + // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) + // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) + // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) + // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) + + // IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across + // 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 @@ -74,33 +143,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 @@ -123,7 +192,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 // *************************************************************************** @@ -164,12 +232,12 @@ void saveConfigCallback () { // *************************************************************************** #include "colormodes.h" - - // *************************************************************************** // MAIN // *************************************************************************** void setup() { +// system_update_cpu_freq(160); + DBG_OUTPUT_PORT.begin(115200); EEPROM.begin(512); @@ -177,190 +245,29 @@ void setup() { pinMode(BUILTIN_LED, OUTPUT); // button pin setup #ifdef ENABLE_BUTTON - pinMode(BUTTON,INPUT_PULLUP); + pinMode(BUTTON, INPUT_PULLUP); #endif + +#ifdef ENABLE_BUTTON2 + pinMode(BUTTON2, INPUT_PULLUP); + for (int i=0; i<256; i++) { + float x = i; + x /= 255; + x = pow(x, 2.5); + x *= 255; + gammatable[i] = x; + } + if (tcs.begin()) { + DBG_OUTPUT_PORT.println("Found GY33 sensor"); + tcs.setConfig(MCU_LED_OFF,MCU_LED_OFF); + } else { + DBG_OUTPUT_PORT.println("No GY33 sensor found ... check your connections"); + } +#endif + // start ticker with 0.5 because we start in AP mode and try to connect ticker.attach(0.5, tick); - wifi_station_set_hostname(const_cast(HOSTNAME)); - - // *************************************************************************** - // Setup: Neopixel - // *************************************************************************** - strip.init(); - strip.setBrightness(brightness); - strip.setSpeed(convertSpeed(ws2812fx_speed)); - //strip.setMode(FX_MODE_RAINBOW_CYCLE); - strip.setColor(main_color.white, main_color.red, main_color.green, main_color.blue); - strip.start(); - - // *************************************************************************** - // Setup: WiFiManager - // *************************************************************************** - // The extra parameters to be configured (can be either global or just in the setup) - // After connecting, parameter.getValue() will get you the configured value - // id/name placeholder/prompt default length - #ifdef ENABLE_MQTT - String settings_available = readEEPROM(134, 1); - if (settings_available == "1") { - readEEPROM(0, 64).toCharArray(mqtt_host, 64); // 0-63 - readEEPROM(64, 6).toCharArray(mqtt_port, 6); // 64-69 - readEEPROM(70, 32).toCharArray(mqtt_user, 32); // 70-101 - readEEPROM(102, 32).toCharArray(mqtt_pass, 32); // 102-133 - DBG_OUTPUT_PORT.printf("MQTT host: %s\n", mqtt_host); - DBG_OUTPUT_PORT.printf("MQTT port: %s\n", mqtt_port); - DBG_OUTPUT_PORT.printf("MQTT user: %s\n", mqtt_user); - DBG_OUTPUT_PORT.printf("MQTT pass: %s\n", mqtt_pass); - } - - WiFiManagerParameter custom_mqtt_host("host", "MQTT hostname", mqtt_host, 64); - WiFiManagerParameter custom_mqtt_port("port", "MQTT port", mqtt_port, 6); - WiFiManagerParameter custom_mqtt_user("user", "MQTT user", mqtt_user, 32); - WiFiManagerParameter custom_mqtt_pass("pass", "MQTT pass", mqtt_pass, 32); - #endif - - //Local intialization. Once its business is done, there is no need to keep it around - WiFiManager wifiManager; - //reset settings - for testing - //wifiManager.resetSettings(); - - //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode - wifiManager.setAPCallback(configModeCallback); - - #ifdef ENABLE_MQTT - //set config save notify callback - wifiManager.setSaveConfigCallback(saveConfigCallback); - - //add all your parameters here - wifiManager.addParameter(&custom_mqtt_host); - wifiManager.addParameter(&custom_mqtt_port); - wifiManager.addParameter(&custom_mqtt_user); - wifiManager.addParameter(&custom_mqtt_pass); - #endif - - //fetches ssid and pass and tries to connect - //if it does not connect it starts an access point with the specified name - //here "AutoConnectAP" - //and goes into a blocking loop awaiting configuration - if (!wifiManager.autoConnect(HOSTNAME)) { - DBG_OUTPUT_PORT.println("failed to connect and hit timeout"); - //reset and try again, or maybe put it to deep sleep - ESP.reset(); - delay(1000); - } - - #ifdef ENABLE_MQTT - //read updated parameters - strcpy(mqtt_host, custom_mqtt_host.getValue()); - strcpy(mqtt_port, custom_mqtt_port.getValue()); - strcpy(mqtt_user, custom_mqtt_user.getValue()); - strcpy(mqtt_pass, custom_mqtt_pass.getValue()); - - //save the custom parameters to FS - if (shouldSaveConfig) { - DBG_OUTPUT_PORT.println("Saving WiFiManager config"); - - writeEEPROM(0, 64, mqtt_host); // 0-63 - writeEEPROM(64, 6, mqtt_port); // 64-69 - writeEEPROM(70, 32, mqtt_user); // 70-101 - writeEEPROM(102, 32, mqtt_pass); // 102-133 - writeEEPROM(134, 1, "1"); // 134 --> always "1" - EEPROM.commit(); - } - #endif - - //if you get here you have connected to the WiFi - DBG_OUTPUT_PORT.println("connected...yeey :)"); - ticker.detach(); - //keep LED on - digitalWrite(BUILTIN_LED, LOW); - - - // *************************************************************************** - // Configure OTA - // *************************************************************************** - #ifdef ENABLE_OTA - DBG_OUTPUT_PORT.println("Arduino OTA activated."); - - // Port defaults to 8266 - ArduinoOTA.setPort(8266); - - // Hostname defaults to esp8266-[ChipID] - ArduinoOTA.setHostname(HOSTNAME); - - // No authentication by default - // ArduinoOTA.setPassword("admin"); - - // Password can be set with it's md5 value as well - // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 - // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); - - ArduinoOTA.onStart([]() { - DBG_OUTPUT_PORT.println("Arduino OTA: Start updating"); - }); - ArduinoOTA.onEnd([]() { - DBG_OUTPUT_PORT.println("Arduino OTA: End"); - }); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - DBG_OUTPUT_PORT.printf("Arduino OTA Progress: %u%%\r", (progress / (total / 100))); - }); - ArduinoOTA.onError([](ota_error_t error) { - DBG_OUTPUT_PORT.printf("Arduino OTA Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Auth Failed"); - else if (error == OTA_BEGIN_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Begin Failed"); - else if (error == OTA_CONNECT_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Connect Failed"); - else if (error == OTA_RECEIVE_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Receive Failed"); - else if (error == OTA_END_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: End Failed"); - }); - - ArduinoOTA.begin(); - DBG_OUTPUT_PORT.println(""); - #endif - - - // *************************************************************************** - // Configure MQTT - // *************************************************************************** - #ifdef ENABLE_MQTT - if (mqtt_host != "" && String(mqtt_port).toInt() > 0) { - snprintf(mqtt_intopic, sizeof mqtt_intopic, "%s/in", HOSTNAME); - snprintf(mqtt_outtopic, sizeof mqtt_outtopic, "%s/out", HOSTNAME); - - DBG_OUTPUT_PORT.printf("MQTT active: %s:%d\n", mqtt_host, String(mqtt_port).toInt()); - - mqtt_client.setServer(mqtt_host, String(mqtt_port).toInt()); - mqtt_client.setCallback(mqtt_callback); - } - #endif - - - // *************************************************************************** - // Setup: MDNS responder - // *************************************************************************** - bool mdns_result = MDNS.begin(HOSTNAME); - - DBG_OUTPUT_PORT.print("Open http://"); - DBG_OUTPUT_PORT.print(WiFi.localIP()); - DBG_OUTPUT_PORT.println("/ to open McLighting."); - - DBG_OUTPUT_PORT.print("Use http://"); - DBG_OUTPUT_PORT.print(HOSTNAME); - DBG_OUTPUT_PORT.println(".local/ when you have Bonjour installed."); - - DBG_OUTPUT_PORT.print("New users: Open http://"); - DBG_OUTPUT_PORT.print(WiFi.localIP()); - DBG_OUTPUT_PORT.println("/upload to upload the webpages first."); - - DBG_OUTPUT_PORT.println(""); - - - // *************************************************************************** - // Setup: WebSocket server - // *************************************************************************** - webSocket.begin(); - webSocket.onEvent(webSocketEvent); - - // *************************************************************************** // Setup: SPIFFS // *************************************************************************** @@ -378,6 +285,214 @@ void setup() { DBG_OUTPUT_PORT.printf("FS Usage: %d/%d bytes\n\n", fs_info.usedBytes, fs_info.totalBytes); } + wifi_station_set_hostname(const_cast(HOSTNAME)); + + // *************************************************************************** + // Setup: Neopixel + // *************************************************************************** + strip.init(); + strip.setBrightness(brightness); + strip.setSpeed(convertSpeed(ws2812fx_speed)); + //strip.setMode(FX_MODE_RAINBOW_CYCLE); + strip.setColor(main_color.white, main_color.red, main_color.green, main_color.blue); + strip.start(); + + // *************************************************************************** + // Setup: WiFiManager + // *************************************************************************** + // The extra parameters to be configured (can be either global or just in the setup) + // After connecting, parameter.getValue() will get you the configured value + // id/name placeholder/prompt default length + #if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) + #if defined(ENABLE_STATE_SAVE_SPIFFS) and (defined(ENABLE_MQTT) or defined(ENABLE_AMQTT)) + (readConfigFS()) ? DBG_OUTPUT_PORT.println("WiFiManager config FS Read success!"): DBG_OUTPUT_PORT.println("WiFiManager config FS Read failure!"); + #else + String settings_available = readEEPROM(134, 1); + if (settings_available == "1") { + readEEPROM(0, 64).toCharArray(mqtt_host, 64); // 0-63 + readEEPROM(64, 6).toCharArray(mqtt_port, 6); // 64-69 + readEEPROM(70, 32).toCharArray(mqtt_user, 32); // 70-101 + readEEPROM(102, 32).toCharArray(mqtt_pass, 32); // 102-133 + DBG_OUTPUT_PORT.printf("MQTT host: %s\n", mqtt_host); + DBG_OUTPUT_PORT.printf("MQTT port: %s\n", mqtt_port); + DBG_OUTPUT_PORT.printf("MQTT user: %s\n", mqtt_user); + DBG_OUTPUT_PORT.printf("MQTT pass: %s\n", mqtt_pass); + } + #endif + WiFiManagerParameter custom_mqtt_host("host", "MQTT hostname", mqtt_host, 64); + WiFiManagerParameter custom_mqtt_port("port", "MQTT port", mqtt_port, 6); + WiFiManagerParameter custom_mqtt_user("user", "MQTT user", mqtt_user, 32); + WiFiManagerParameter custom_mqtt_pass("pass", "MQTT pass", mqtt_pass, 32); + #endif + + //Local intialization. Once its business is done, there is no need to keep it around + WiFiManager wifiManager; + //reset settings - for testing + //wifiManager.resetSettings(); + + //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode + wifiManager.setAPCallback(configModeCallback); + + #if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) + //set config save notify callback + wifiManager.setSaveConfigCallback(saveConfigCallback); + + //add all your parameters here + wifiManager.addParameter(&custom_mqtt_host); + wifiManager.addParameter(&custom_mqtt_port); + wifiManager.addParameter(&custom_mqtt_user); + wifiManager.addParameter(&custom_mqtt_pass); + #endif + + WiFi.setSleepMode(WIFI_NONE_SLEEP); + + //fetches ssid and pass and tries to connect + //if it does not connect it starts an access point with the specified name + //here "AutoConnectAP" + //and goes into a blocking loop awaiting configuration + if (!wifiManager.autoConnect(HOSTNAME)) { + DBG_OUTPUT_PORT.println("failed to connect and hit timeout"); + //reset and try again, or maybe put it to deep sleep + ESP.reset(); + delay(1000); + } + + #if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) + //read updated parameters + strcpy(mqtt_host, custom_mqtt_host.getValue()); + strcpy(mqtt_port, custom_mqtt_port.getValue()); + strcpy(mqtt_user, custom_mqtt_user.getValue()); + strcpy(mqtt_pass, custom_mqtt_pass.getValue()); + + //save the custom parameters to FS + #if defined(ENABLE_STATE_SAVE_SPIFFS) and (defined(ENABLE_MQTT) or defined(ENABLE_AMQTT)) + (writeConfigFS(shouldSaveConfig)) ? DBG_OUTPUT_PORT.println("WiFiManager config FS Save success!"): DBG_OUTPUT_PORT.println("WiFiManager config FS Save failure!"); + #else if defined(ENABLE_STATE_SAVE_EEPROM) + if (shouldSaveConfig) { + DBG_OUTPUT_PORT.println("Saving WiFiManager config"); + + writeEEPROM(0, 64, mqtt_host); // 0-63 + writeEEPROM(64, 6, mqtt_port); // 64-69 + writeEEPROM(70, 32, mqtt_user); // 70-101 + writeEEPROM(102, 32, mqtt_pass); // 102-133 + writeEEPROM(134, 1, "1"); // 134 --> always "1" + EEPROM.commit(); + } + #endif + #endif + + #ifdef ENABLE_AMQTT + wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); + wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); + #endif + + //if you get here you have connected to the WiFi + DBG_OUTPUT_PORT.println("connected...yeey :)"); + ticker.detach(); + //keep LED on + digitalWrite(BUILTIN_LED, LOW); + + + // *************************************************************************** + // Configure OTA + // *************************************************************************** + #ifdef ENABLE_OTA + DBG_OUTPUT_PORT.println("Arduino OTA activated."); + + // Port defaults to 8266 + ArduinoOTA.setPort(8266); + + // Hostname defaults to esp8266-[ChipID] + ArduinoOTA.setHostname(HOSTNAME); + + // No authentication by default + // ArduinoOTA.setPassword("admin"); + + // Password can be set with it's md5 value as well + // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 + // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); + + ArduinoOTA.onStart([]() { + DBG_OUTPUT_PORT.println("Arduino OTA: Start updating"); + }); + ArduinoOTA.onEnd([]() { + DBG_OUTPUT_PORT.println("Arduino OTA: End"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + DBG_OUTPUT_PORT.printf("Arduino OTA Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + DBG_OUTPUT_PORT.printf("Arduino OTA Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Auth Failed"); + else if (error == OTA_BEGIN_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Begin Failed"); + else if (error == OTA_CONNECT_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Receive Failed"); + else if (error == OTA_END_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: End Failed"); + }); + + ArduinoOTA.begin(); + DBG_OUTPUT_PORT.println(""); + #endif + + + // *************************************************************************** + // Configure MQTT + // *************************************************************************** + #ifdef ENABLE_MQTT + if (mqtt_host != "" && String(mqtt_port).toInt() > 0) { + snprintf(mqtt_intopic, sizeof mqtt_intopic, "%s/in", HOSTNAME); + snprintf(mqtt_outtopic, sizeof mqtt_outtopic, "%s/out", HOSTNAME); + + DBG_OUTPUT_PORT.printf("MQTT active: %s:%d\n", mqtt_host, String(mqtt_port).toInt()); + + mqtt_client.setServer(mqtt_host, String(mqtt_port).toInt()); + mqtt_client.setCallback(mqtt_callback); + } + #endif + + #ifdef ENABLE_AMQTT + if (mqtt_host != "" && String(mqtt_port).toInt() > 0) { + amqttClient.onConnect(onMqttConnect); + amqttClient.onDisconnect(onMqttDisconnect); + amqttClient.onMessage(onMqttMessage); + amqttClient.setServer(mqtt_host, String(mqtt_port).toInt()); + amqttClient.setCredentials(mqtt_user, mqtt_pass); + amqttClient.setClientId(mqtt_clientid); + + connectToMqtt(); + } + #endif + + // #ifdef ENABLE_HOMEASSISTANT + // ha_send_data.attach(5, tickerSendState); // Send HA data back only every 5 sec + // #endif + + // *************************************************************************** + // Setup: MDNS responder + // *************************************************************************** + bool mdns_result = MDNS.begin(HOSTNAME); + + DBG_OUTPUT_PORT.print("Open http://"); + DBG_OUTPUT_PORT.print(WiFi.localIP()); + DBG_OUTPUT_PORT.println("/ to open McLighting."); + + DBG_OUTPUT_PORT.print("Use http://"); + DBG_OUTPUT_PORT.print(HOSTNAME); + DBG_OUTPUT_PORT.println(".local/ when you have Bonjour installed."); + + DBG_OUTPUT_PORT.print("New users: Open http://"); + DBG_OUTPUT_PORT.print(WiFi.localIP()); + DBG_OUTPUT_PORT.println("/upload to upload the webpages first."); + + DBG_OUTPUT_PORT.println(""); + + + // *************************************************************************** + // Setup: WebSocket server + // *************************************************************************** + webSocket.begin(); + webSocket.onEvent(webSocketEvent); + // *************************************************************************** // Setup: SPIFFS Webserver handler // *************************************************************************** @@ -455,11 +570,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(); }); @@ -469,14 +592,23 @@ void setup() { DBG_OUTPUT_PORT.print("/get_brightness: "); DBG_OUTPUT_PORT.println(str_brightness); }); - + server.on("/set_speed", []() { if (server.arg("d").toInt() >= 0) { ws2812fx_speed = server.arg("d").toInt(); ws2812fx_speed = constrain(ws2812fx_speed, 0, 255); strip.setSpeed(convertSpeed(ws2812fx_speed)); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ?") + String(ws2812fx_speed)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ?") + String(ws2812fx_speed)).c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif } - + getStatusJSON(); }); @@ -508,6 +640,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", []() { @@ -515,6 +659,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", []() { @@ -522,6 +678,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", []() { @@ -529,6 +697,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", []() { @@ -536,6 +716,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", []() { @@ -543,13 +735,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", []() { @@ -557,6 +792,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", []() { @@ -567,16 +814,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, 36); String chk = getValue(saved_state_string, '|', 0); @@ -588,37 +855,59 @@ void setup() { #endif } - void loop() { #ifdef ENABLE_BUTTON button(); - #endif + #endif + #ifdef ENABLE_BUTTON2 + button2(); + #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); strip.setMode(ws2812fx_mode); - mode = HOLD; + mode = SETSPEED; } if (mode == OFF) { - strip.setColor(0,0,0,0); - strip.setMode(FX_MODE_STATIC); +// strip.setColor(0,0,0,0); +// strip.setMode(FX_MODE_STATIC); + if(strip.isRunning()) strip.stop(); //should clear memory // mode = HOLD; } if (mode == ALL) { @@ -626,6 +915,18 @@ void loop() { strip.setMode(FX_MODE_STATIC); mode = HOLD; } + if (mode == SETCOLOR) { + strip.setColor(main_color.white, main_color.red, main_color.green, main_color.blue); + mode = HOLD; + } + if (mode == SETSPEED) { + strip.setSpeed(convertSpeed(ws2812fx_speed)); + mode = HOLD; + } + if (mode == BRIGHTNESS) { + strip.setBrightness(brightness); + mode = HOLD; + } if (mode == WIPE) { strip.setColor(main_color.white, main_color.red, main_color.green, main_color.blue); strip.setMode(FX_MODE_COLOR_WIPE); @@ -654,11 +955,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(); } @@ -667,11 +970,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|%3d", mode, strip.getMode(), ws2812fx_speed, brightness, main_color.white, 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); @@ -685,4 +993,5 @@ void loop() { EEPROM.commit(); } #endif -} +} + diff --git a/Arduino/McLighting/WS2812FX.cpp b/Arduino/McLighting/WS2812FX.cpp index 5dfe03c..48b9a39 100644 --- a/Arduino/McLighting/WS2812FX.cpp +++ b/Arduino/McLighting/WS2812FX.cpp @@ -7,10 +7,10 @@ FEATURES * A lot of blinken modes and counting - * WS2812FX can be used as drop-in replacement for Adafruit Neopixel Library + * WS2812FX can be used as drop-in replacement for Adafruit NeoPixel Library NOTES - * Uses the Adafruit Neopixel library. Get it here: + * Uses the Adafruit NeoPixel library. Get it here: https://github.com/adafruit/Adafruit_NeoPixel @@ -168,7 +168,7 @@ void WS2812FX::decreaseLength(uint16_t s) { s = _segments[0].stop - _segments[0].start + 1 - s; for(uint16_t i=_segments[0].start + s; i <= (_segments[0].stop - _segments[0].start + 1); i++) { - Adafruit_NeoPixel::setPixelColor(i, 0); + this->setPixelColor(i, 0); } Adafruit_NeoPixel::show(); @@ -211,7 +211,15 @@ uint32_t WS2812FX::getColor(void) { return _segments[0].colors[0]; } -WS2812FX::segment* WS2812FX::getSegments(void) { +WS2812FX::Segment WS2812FX::getSegment(void) { + return SEGMENT; +} + +WS2812FX::Segment_runtime WS2812FX::getSegmentRuntime(void) { + return SEGMENT_RUNTIME; +} + +WS2812FX::Segment* WS2812FX::getSegments(void) { return _segments; } @@ -253,7 +261,6 @@ void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode void WS2812FX::resetSegments() { memset(_segments, 0, sizeof(_segments)); memset(_segment_runtimes, 0, sizeof(_segment_runtimes)); - _segment_index = 0; _num_segments = 1; setSegment(0, 0, 7, FX_MODE_STATIC, DEFAULT_COLOR, DEFAULT_SPEED, false); @@ -318,7 +325,7 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { */ uint16_t WS2812FX::mode_static(void) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, SEGMENT.colors[0]); + this->setPixelColor(i, SEGMENT.colors[0]); } return 500; } @@ -334,7 +341,7 @@ uint16_t WS2812FX::blink(uint32_t color1, uint32_t color2, bool strobe) { if(SEGMENT.reverse) color = (color == color1) ? color2 : color1; for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, color); + this->setPixelColor(i, color); } if((SEGMENT_RUNTIME.counter_mode_call & 1) == 0) { @@ -386,16 +393,16 @@ uint16_t WS2812FX::color_wipe(uint32_t color1, uint32_t color2, bool rev) { if(SEGMENT_RUNTIME.counter_mode_step < SEGMENT_LENGTH) { uint32_t led_offset = SEGMENT_RUNTIME.counter_mode_step; if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - led_offset, color1); + this->setPixelColor(SEGMENT.stop - led_offset, color1); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + led_offset, color1); + this->setPixelColor(SEGMENT.start + led_offset, color1); } } else { uint32_t led_offset = SEGMENT_RUNTIME.counter_mode_step - SEGMENT_LENGTH; if((SEGMENT.reverse && !rev) || (!SEGMENT.reverse && rev)) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - led_offset, color2); + this->setPixelColor(SEGMENT.stop - led_offset, color2); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + led_offset, color2); + this->setPixelColor(SEGMENT.start + led_offset, color2); } } @@ -437,7 +444,7 @@ uint16_t WS2812FX::mode_color_wipe_random(void) { /* - * Random color intruduced alternating from start and end of strip. + * Random color introduced alternating from start and end of strip. */ uint16_t WS2812FX::mode_color_sweep_random(void) { if(SEGMENT_RUNTIME.counter_mode_step % SEGMENT_LENGTH == 0) { // aux_param will store our random color wheel index @@ -457,7 +464,7 @@ uint16_t WS2812FX::mode_random_color(void) { uint32_t color = color_wheel(SEGMENT_RUNTIME.aux_param); for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, color); + this->setPixelColor(i, color); } return (SEGMENT.speed); } @@ -470,11 +477,11 @@ uint16_t WS2812FX::mode_random_color(void) { uint16_t WS2812FX::mode_single_dynamic(void) { if(SEGMENT_RUNTIME.counter_mode_call == 0) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, color_wheel(random(256))); + this->setPixelColor(i, color_wheel(random(256))); } } - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), color_wheel(random(256))); + this->setPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), color_wheel(random(256))); return (SEGMENT.speed); } @@ -485,7 +492,7 @@ uint16_t WS2812FX::mode_single_dynamic(void) { */ uint16_t WS2812FX::mode_multi_dynamic(void) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, color_wheel(random(256))); + this->setPixelColor(i, color_wheel(random(256))); } return (SEGMENT.speed); } @@ -523,7 +530,7 @@ uint16_t WS2812FX::mode_breath(void) { uint8_t g = (SEGMENT.colors[0] >> 8 & 0xFF) * lum / _brightness; uint8_t b = (SEGMENT.colors[0] & 0xFF) * lum / _brightness; for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, r, g, b, w); + this->setPixelColor(i, r, g, b, w); } SEGMENT_RUNTIME.aux_param = breath_brightness; @@ -544,7 +551,7 @@ uint16_t WS2812FX::mode_fade(void) { uint8_t g = (SEGMENT.colors[0] >> 8 & 0xFF) * lum / _brightness; uint8_t b = (SEGMENT.colors[0] & 0xFF) * lum / _brightness; for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, r, g, b, w); + this->setPixelColor(i, r, g, b, w); } SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % 64; @@ -561,16 +568,16 @@ uint16_t WS2812FX::mode_scan(void) { } for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, BLACK); + this->setPixelColor(i, BLACK); } int led_offset = SEGMENT_RUNTIME.counter_mode_step - (SEGMENT_LENGTH - 1); led_offset = abs(led_offset); if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - led_offset, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.stop - led_offset, SEGMENT.colors[0]); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + led_offset, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + led_offset, SEGMENT.colors[0]); } SEGMENT_RUNTIME.counter_mode_step++; @@ -587,14 +594,14 @@ uint16_t WS2812FX::mode_dual_scan(void) { } for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, BLACK); + this->setPixelColor(i, BLACK); } int led_offset = SEGMENT_RUNTIME.counter_mode_step - (SEGMENT_LENGTH - 1); led_offset = abs(led_offset); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + led_offset, SEGMENT.colors[0]); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + SEGMENT_LENGTH - led_offset - 1, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + led_offset, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + SEGMENT_LENGTH - led_offset - 1, SEGMENT.colors[0]); SEGMENT_RUNTIME.counter_mode_step++; return (SEGMENT.speed / (SEGMENT_LENGTH * 2)); @@ -607,7 +614,7 @@ uint16_t WS2812FX::mode_dual_scan(void) { uint16_t WS2812FX::mode_rainbow(void) { uint32_t color = color_wheel(SEGMENT_RUNTIME.counter_mode_step); for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, color); + this->setPixelColor(i, color); } SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; @@ -621,7 +628,7 @@ uint16_t WS2812FX::mode_rainbow(void) { uint16_t WS2812FX::mode_rainbow_cycle(void) { for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { uint32_t color = color_wheel(((i * 256 / SEGMENT_LENGTH) + SEGMENT_RUNTIME.counter_mode_step) & 0xFF); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color); + this->setPixelColor(SEGMENT.start + i, color); } SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; @@ -637,15 +644,15 @@ uint16_t WS2812FX::theater_chase(uint32_t color1, uint32_t color2) { for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { if((i % 3) == SEGMENT_RUNTIME.counter_mode_call) { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, color1); + this->setPixelColor(SEGMENT.stop - i, color1); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color1); + this->setPixelColor(SEGMENT.start + i, color1); } } else { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, color2); + this->setPixelColor(SEGMENT.stop - i, color2); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color2); + this->setPixelColor(SEGMENT.start + i, color2); } } } @@ -686,9 +693,9 @@ uint16_t WS2812FX::mode_running_lights(void) { for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { int lum = map((int)(sin((i + SEGMENT_RUNTIME.counter_mode_step) * radPerLed) * 128), -128, 128, 0, 255); if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, (r * lum) / 256, (g * lum) / 256, (b * lum) / 256, (w * lum) / 256); + this->setPixelColor(SEGMENT.start + i, (r * lum) / 256, (g * lum) / 256, (b * lum) / 256, (w * lum) / 256); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, (r * lum) / 256, (g * lum) / 256, (b * lum) / 256, (w * lum) / 256); + this->setPixelColor(SEGMENT.stop - i, (r * lum) / 256, (g * lum) / 256, (b * lum) / 256, (w * lum) / 256); } } SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % SEGMENT_LENGTH; @@ -702,14 +709,14 @@ uint16_t WS2812FX::mode_running_lights(void) { uint16_t WS2812FX::twinkle(uint32_t color) { if(SEGMENT_RUNTIME.counter_mode_step == 0) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, BLACK); + this->setPixelColor(i, BLACK); } uint16_t min_leds = max(1, SEGMENT_LENGTH / 5); // make sure, at least one LED is on uint16_t max_leds = max(1, SEGMENT_LENGTH / 2); // make sure, at least one LED is on SEGMENT_RUNTIME.counter_mode_step = random(min_leds, max_leds); } - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), color); + this->setPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), color); SEGMENT_RUNTIME.counter_mode_step--; return (SEGMENT.speed / SEGMENT_LENGTH); @@ -740,7 +747,7 @@ void WS2812FX::fade_out() { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { uint32_t color = Adafruit_NeoPixel::getPixelColor(i); color = (color >> 1) & 0x7F7F7F7F; - Adafruit_NeoPixel::setPixelColor(i, color); + this->setPixelColor(i, color); } } @@ -751,7 +758,7 @@ uint16_t WS2812FX::twinkle_fade(uint32_t color) { fade_out(); if(random(3) == 0) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), color); + this->setPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), color); } return (SEGMENT.speed / 8); } @@ -778,9 +785,9 @@ uint16_t WS2812FX::mode_twinkle_fade_random(void) { * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t WS2812FX::mode_sparkle(void) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.aux_param, BLACK); + this->setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.aux_param, BLACK); SEGMENT_RUNTIME.aux_param = random(SEGMENT_LENGTH); // aux_param stores the random led index - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.aux_param, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.aux_param, SEGMENT.colors[0]); return (SEGMENT.speed / SEGMENT_LENGTH); } @@ -792,15 +799,15 @@ uint16_t WS2812FX::mode_sparkle(void) { uint16_t WS2812FX::mode_flash_sparkle(void) { if(SEGMENT_RUNTIME.counter_mode_call == 0) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, SEGMENT.colors[0]); + this->setPixelColor(i, SEGMENT.colors[0]); } } - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.aux_param, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.aux_param, SEGMENT.colors[0]); if(random(5) == 0) { SEGMENT_RUNTIME.aux_param = random(SEGMENT_LENGTH); // aux_param stores the random led index - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.aux_param, WHITE); + this->setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.aux_param, WHITE); return 20; } return SEGMENT.speed; @@ -813,12 +820,12 @@ uint16_t WS2812FX::mode_flash_sparkle(void) { */ uint16_t WS2812FX::mode_hyper_sparkle(void) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, SEGMENT.colors[0]); + this->setPixelColor(i, SEGMENT.colors[0]); } if(random(5) < 2) { for(uint16_t i=0; i < max(1, SEGMENT_LENGTH/3); i++) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), WHITE); + this->setPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), WHITE); } return 20; } @@ -831,14 +838,14 @@ uint16_t WS2812FX::mode_hyper_sparkle(void) { */ uint16_t WS2812FX::mode_multi_strobe(void) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, BLACK); + this->setPixelColor(i, BLACK); } uint16_t delay = SEGMENT.speed / (2 * ((SEGMENT.speed / 10) + 1)); if(SEGMENT_RUNTIME.counter_mode_step < (2 * ((SEGMENT.speed / 10) + 1))) { if((SEGMENT_RUNTIME.counter_mode_step & 1) == 0) { for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, SEGMENT.colors[0]); + this->setPixelColor(i, SEGMENT.colors[0]); } delay = 20; } else { @@ -861,13 +868,13 @@ uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3) { uint16_t b = (a + 1) % SEGMENT_LENGTH; uint16_t c = (b + 1) % SEGMENT_LENGTH; if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - a, color1); - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - b, color2); - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - c, color3); + this->setPixelColor(SEGMENT.stop - a, color1); + this->setPixelColor(SEGMENT.stop - b, color2); + this->setPixelColor(SEGMENT.stop - c, color3); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + a, color1); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + b, color2); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + c, color3); + this->setPixelColor(SEGMENT.start + a, color1); + this->setPixelColor(SEGMENT.start + b, color2); + this->setPixelColor(SEGMENT.start + c, color3); } SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % SEGMENT_LENGTH; @@ -963,7 +970,7 @@ uint16_t WS2812FX::mode_chase_flash(void) { uint8_t flash_step = SEGMENT_RUNTIME.counter_mode_call % ((flash_count * 2) + 1); for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { - Adafruit_NeoPixel::setPixelColor(i, SEGMENT.colors[0]); + this->setPixelColor(i, SEGMENT.colors[0]); } uint16_t delay = (SEGMENT.speed / SEGMENT_LENGTH); @@ -972,11 +979,11 @@ uint16_t WS2812FX::mode_chase_flash(void) { uint16_t n = SEGMENT_RUNTIME.counter_mode_step; uint16_t m = (SEGMENT_RUNTIME.counter_mode_step + 1) % SEGMENT_LENGTH; if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - n, WHITE); - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - m, WHITE); + this->setPixelColor(SEGMENT.stop - n, WHITE); + this->setPixelColor(SEGMENT.stop - m, WHITE); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + n, WHITE); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + m, WHITE); + this->setPixelColor(SEGMENT.start + n, WHITE); + this->setPixelColor(SEGMENT.start + m, WHITE); } delay = 20; } else { @@ -997,7 +1004,7 @@ uint16_t WS2812FX::mode_chase_flash_random(void) { uint8_t flash_step = SEGMENT_RUNTIME.counter_mode_call % ((flash_count * 2) + 1); for(uint16_t i=0; i < SEGMENT_RUNTIME.counter_mode_step; i++) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color_wheel(SEGMENT_RUNTIME.aux_param)); + this->setPixelColor(SEGMENT.start + i, color_wheel(SEGMENT_RUNTIME.aux_param)); } uint16_t delay = (SEGMENT.speed / SEGMENT_LENGTH); @@ -1005,12 +1012,12 @@ uint16_t WS2812FX::mode_chase_flash_random(void) { uint16_t n = SEGMENT_RUNTIME.counter_mode_step; uint16_t m = (SEGMENT_RUNTIME.counter_mode_step + 1) % SEGMENT_LENGTH; if(flash_step % 2 == 0) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + n, WHITE); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + m, WHITE); + this->setPixelColor(SEGMENT.start + n, WHITE); + this->setPixelColor(SEGMENT.start + m, WHITE); delay = 20; } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + n, color_wheel(SEGMENT_RUNTIME.aux_param)); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + m, BLACK); + this->setPixelColor(SEGMENT.start + n, color_wheel(SEGMENT_RUNTIME.aux_param)); + this->setPixelColor(SEGMENT.start + m, BLACK); delay = 30; } } else { @@ -1031,15 +1038,15 @@ uint16_t WS2812FX::running(uint32_t color1, uint32_t color2) { for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { if((i + SEGMENT_RUNTIME.counter_mode_step) % 4 < 2) { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color1); + this->setPixelColor(SEGMENT.start + i, color1); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, color1); + this->setPixelColor(SEGMENT.stop - i, color1); } } else { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color2); + this->setPixelColor(SEGMENT.start + i, color2); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, color2); + this->setPixelColor(SEGMENT.stop - i, color2); } } } @@ -1085,18 +1092,18 @@ uint16_t WS2812FX::mode_halloween(void) { uint16_t WS2812FX::mode_running_random(void) { for(uint16_t i=SEGMENT_LENGTH-1; i > 0; i--) { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, Adafruit_NeoPixel::getPixelColor(SEGMENT.stop - i + 1)); + this->setPixelColor(SEGMENT.stop - i, Adafruit_NeoPixel::getPixelColor(SEGMENT.stop - i + 1)); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, Adafruit_NeoPixel::getPixelColor(SEGMENT.start + i - 1)); + this->setPixelColor(SEGMENT.start + i, Adafruit_NeoPixel::getPixelColor(SEGMENT.start + i - 1)); } } if(SEGMENT_RUNTIME.counter_mode_step == 0) { SEGMENT_RUNTIME.aux_param = get_random_wheel_index(SEGMENT_RUNTIME.aux_param); if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop, color_wheel(SEGMENT_RUNTIME.aux_param)); + this->setPixelColor(SEGMENT.stop, color_wheel(SEGMENT_RUNTIME.aux_param)); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start, color_wheel(SEGMENT_RUNTIME.aux_param)); + this->setPixelColor(SEGMENT.start, color_wheel(SEGMENT_RUNTIME.aux_param)); } } @@ -1113,15 +1120,15 @@ uint16_t WS2812FX::mode_larson_scanner(void) { if(SEGMENT_RUNTIME.counter_mode_step < SEGMENT_LENGTH) { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.stop - SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); } } else { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - ((SEGMENT_LENGTH * 2) - SEGMENT_RUNTIME.counter_mode_step) + 2, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.stop - ((SEGMENT_LENGTH * 2) - SEGMENT_RUNTIME.counter_mode_step) + 2, SEGMENT.colors[0]); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + ((SEGMENT_LENGTH * 2) - SEGMENT_RUNTIME.counter_mode_step) - 2, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + ((SEGMENT_LENGTH * 2) - SEGMENT_RUNTIME.counter_mode_step) - 2, SEGMENT.colors[0]); } } @@ -1137,9 +1144,9 @@ uint16_t WS2812FX::mode_comet(void) { fade_out(); if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.stop - SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); } SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % SEGMENT_LENGTH; @@ -1166,7 +1173,7 @@ uint32_t prevLed, thisLed, nextLed; px_r = (((Adafruit_NeoPixel::getPixelColor(SEGMENT.start+1) & 0xFF0000) >> 16) >> 1) + ((Adafruit_NeoPixel::getPixelColor(SEGMENT.start) & 0xFF0000) >> 16); px_g = (((Adafruit_NeoPixel::getPixelColor(SEGMENT.start+1) & 0x00FF00) >> 8) >> 1) + ((Adafruit_NeoPixel::getPixelColor(SEGMENT.start) & 0x00FF00) >> 8); px_b = (((Adafruit_NeoPixel::getPixelColor(SEGMENT.start+1) & 0x0000FF) ) >> 1) + ((Adafruit_NeoPixel::getPixelColor(SEGMENT.start) & 0x0000FF)); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start, px_r, px_g, px_b); + this->setPixelColor(SEGMENT.start, px_r, px_g, px_b); */ // set brightness(i) = ((brightness(i-1)/2 + brightness(i+1)) / 2) + brightness(i) for(uint16_t i=SEGMENT.start + 1; i > 2) & 0x3F3F3F3F; thisLed = Adafruit_NeoPixel::getPixelColor(i); nextLed = (Adafruit_NeoPixel::getPixelColor(i+1) >> 2) & 0x3F3F3F3F; - Adafruit_NeoPixel::setPixelColor(i, prevLed + thisLed + nextLed); + this->setPixelColor(i, prevLed + thisLed + nextLed); /* the old way px_r = (( @@ -1192,7 +1199,7 @@ uint32_t prevLed, thisLed, nextLed; (((Adafruit_NeoPixel::getPixelColor(i+1) & 0x0000FF) ) ) ) >> 1) + (((Adafruit_NeoPixel::getPixelColor(i ) & 0x0000FF) ) ); - Adafruit_NeoPixel::setPixelColor(i, px_r, px_g, px_b); + this->setPixelColor(i, px_r, px_g, px_b); */ } @@ -1201,17 +1208,17 @@ uint32_t prevLed, thisLed, nextLed; px_r = (((Adafruit_NeoPixel::getPixelColor(SEGMENT.stop-1) & 0xFF0000) >> 16) >> 2) + ((Adafruit_NeoPixel::getPixelColor(SEGMENT.stop) & 0xFF0000) >> 16); px_g = (((Adafruit_NeoPixel::getPixelColor(SEGMENT.stop-1) & 0x00FF00) >> 8) >> 2) + ((Adafruit_NeoPixel::getPixelColor(SEGMENT.stop) & 0x00FF00) >> 8); px_b = (((Adafruit_NeoPixel::getPixelColor(SEGMENT.stop-1) & 0x0000FF) ) >> 2) + ((Adafruit_NeoPixel::getPixelColor(SEGMENT.stop) & 0x0000FF)); - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop, px_r, px_g, px_b); + this->setPixelColor(SEGMENT.stop, px_r, px_g, px_b); */ if(!_triggered) { for(uint16_t i=0; isetPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), color); } } } else { for(uint16_t i=0; isetPixelColor(SEGMENT.start + random(SEGMENT_LENGTH), color); } } return (SEGMENT.speed / SEGMENT_LENGTH); @@ -1247,7 +1254,7 @@ uint16_t WS2812FX::fire_flicker(int rev_intensity) { byte lum = max(w, max(r, max(g, b))) / rev_intensity; for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { int flicker = random(0, lum); - Adafruit_NeoPixel::setPixelColor(i, max(r - flicker, 0), max(g - flicker, 0), max(b - flicker, 0), max(w - flicker, 0)); + this->setPixelColor(i, max(r - flicker, 0), max(g - flicker, 0), max(b - flicker, 0), max(w - flicker, 0)); } return (SEGMENT.speed / SEGMENT_LENGTH); } @@ -1260,14 +1267,14 @@ uint16_t WS2812FX::mode_fire_flicker(void) { } /* -* Random flickering, less intesity. +* Random flickering, less intensity. */ uint16_t WS2812FX::mode_fire_flicker_soft(void) { return fire_flicker(6); } /* -* Random flickering, more intesity. +* Random flickering, more intensity. */ uint16_t WS2812FX::mode_fire_flicker_intense(void) { return fire_flicker(1.7); @@ -1281,21 +1288,21 @@ uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2, uint32_t col for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { if((i + SEGMENT_RUNTIME.counter_mode_step) % 6 < 2) { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color1); + this->setPixelColor(SEGMENT.start + i, color1); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, color1); + this->setPixelColor(SEGMENT.stop - i, color1); } } else if((i + SEGMENT_RUNTIME.counter_mode_step) % 6 < 4) { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color2); + this->setPixelColor(SEGMENT.start + i, color2); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, color2); + this->setPixelColor(SEGMENT.stop - i, color2); } } else { if(SEGMENT.reverse) { - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + i, color3); + this->setPixelColor(SEGMENT.start + i, color3); } else { - Adafruit_NeoPixel::setPixelColor(SEGMENT.stop - i, color3); + this->setPixelColor(SEGMENT.stop - i, color3); } } } @@ -1323,24 +1330,24 @@ uint16_t WS2812FX::mode_circus_combustus(void) { /* * ICU mode */ -uint16_t WS2812FX::mode_icu() { +uint16_t WS2812FX::mode_icu(void) { uint16_t dest = SEGMENT_RUNTIME.counter_mode_step & 0xFFFF; - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + dest, SEGMENT.colors[0]); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + dest, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, SEGMENT.colors[0]); if(SEGMENT_RUNTIME.aux_param == dest) { // pause between eye movements if(random(6) == 0) { // blink once in a while - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + dest, 0); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, 0); + this->setPixelColor(SEGMENT.start + dest, 0); + this->setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, 0); return 200; } SEGMENT_RUNTIME.aux_param = random(SEGMENT_LENGTH/2); return 1000 + random(2000); } - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + dest, 0); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, 0); + this->setPixelColor(SEGMENT.start + dest, 0); + this->setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, 0); if(SEGMENT_RUNTIME.aux_param > SEGMENT_RUNTIME.counter_mode_step) { SEGMENT_RUNTIME.counter_mode_step++; @@ -1350,8 +1357,8 @@ uint16_t WS2812FX::mode_icu() { dest--; } - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + dest, SEGMENT.colors[0]); - Adafruit_NeoPixel::setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + dest, SEGMENT.colors[0]); + this->setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, SEGMENT.colors[0]); return (SEGMENT.speed / SEGMENT_LENGTH); } @@ -1372,6 +1379,5 @@ uint16_t WS2812FX::mode_custom() { * Custom mode helper */ void WS2812FX::setCustomMode(uint16_t (*p)()) { - setMode(FX_MODE_CUSTOM); customMode = p; -} \ No newline at end of file +} diff --git a/Arduino/McLighting/WS2812FX.h b/Arduino/McLighting/WS2812FX.h index 089f70e..6b0a8fd 100644 --- a/Arduino/McLighting/WS2812FX.h +++ b/Arduino/McLighting/WS2812FX.h @@ -5,9 +5,9 @@ www.aldick.org FEATURES * A lot of blinken modes and counting - * WS2812FX can be used as drop-in replacement for Adafruit Neopixel Library + * WS2812FX can be used as drop-in replacement for Adafruit NeoPixel Library NOTES - * Uses the Adafruit Neopixel library. Get it here: + * Uses the Adafruit NeoPixel library. Get it here: https://github.com/adafruit/Adafruit_NeoPixel LICENSE The MIT License (MIT) @@ -50,8 +50,8 @@ #define BRIGHTNESS_MIN 0 #define BRIGHTNESS_MAX 255 - - +/* each segment uses 36 bytes of SRAM memory, so if you're application fails because of + insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #define MAX_NUM_SEGMENTS 10 #define NUM_COLORS 4 /* number of colors per segment */ #define SEGMENT _segments[_segment_index] @@ -138,25 +138,26 @@ class WS2812FX : public Adafruit_NeoPixel { // segment parameters public: - typedef struct segment { - uint8_t mode; - uint32_t colors[NUM_COLORS]; - uint16_t speed; + typedef struct Segment { // 20 bytes uint16_t start; uint16_t stop; + uint16_t speed; + uint8_t mode; bool reverse; + uint32_t colors[NUM_COLORS]; } segment; // segment runtime parameters - typedef struct segment_runtime { - uint32_t counter_mode_step; - uint32_t counter_mode_call; - unsigned long next_time; - uint16_t aux_param; - } segment_runtime; + public: + typedef struct Segment_runtime { // 16 bytes + unsigned long next_time; + uint32_t counter_mode_step; + uint32_t counter_mode_call; + uint16_t aux_param; + uint16_t aux_param2; + } segment_runtime; public: - WS2812FX(uint16_t n, uint8_t p, neoPixelType t) : Adafruit_NeoPixel(n, p, t) { _mode[FX_MODE_STATIC] = &WS2812FX::mode_static; _mode[FX_MODE_BLINK] = &WS2812FX::mode_blink; @@ -211,7 +212,7 @@ class WS2812FX : public Adafruit_NeoPixel { _mode[FX_MODE_CIRCUS_COMBUSTUS] = &WS2812FX::mode_circus_combustus; _mode[FX_MODE_BICOLOR_CHASE] = &WS2812FX::mode_bicolor_chase; _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; -// if flash memory is constrained (i'm looking at you Adruino Nano), replace modes +// if flash memory is constrained (I'm looking at you Arduino Nano), replace modes // that use a lot of flash with mode_static (reduces flash footprint by about 3600 bytes) #ifdef REDUCED_MODES _mode[FX_MODE_BREATH] = &WS2812FX::mode_static; @@ -337,7 +338,13 @@ class WS2812FX : public Adafruit_NeoPixel { const __FlashStringHelper* getModeName(uint8_t m); - WS2812FX::segment* + WS2812FX::Segment + getSegment(void); + + WS2812FX::Segment_runtime + getSegmentRuntime(void); + + WS2812FX::Segment* getSegments(void); private: @@ -431,10 +438,10 @@ class WS2812FX : public Adafruit_NeoPixel { uint8_t _segment_index = 0; uint8_t _num_segments = 1; segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 20 bytes per element - // mode, color[], speed, start, stop, reverse - { FX_MODE_STATIC, {DEFAULT_COLOR}, DEFAULT_SPEED, 0, 7, false} + // start, stop, speed, mode, reverse, color[] + { 0, 7, DEFAULT_SPEED, FX_MODE_STATIC, false, {DEFAULT_COLOR}} }; - segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 14 bytes per element + segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 16 bytes per element }; #endif diff --git a/Arduino/McLighting/data/index2.htm b/Arduino/McLighting/data/index2.htm new file mode 100644 index 0000000..6aa58cc --- /dev/null +++ b/Arduino/McLighting/data/index2.htm @@ -0,0 +1,97 @@ + + + + + + ESP Monitor + + + + +
+ + + + +
+
+
+
+ + \ No newline at end of file diff --git a/Arduino/McLighting/definitions.h b/Arduino/McLighting/definitions.h index 9219f4e..486e386 100644 --- a/Arduino/McLighting/definitions.h +++ b/Arduino/McLighting/definitions.h @@ -1,14 +1,37 @@ +//#define USE_NEOANIMATIONFX // Uses NeoAnimationFX, PIN is ignored & set to RX/GPIO3, see: https://github.com/debsahu/NeoAnimationFX +#define USE_WS2812FX // Uses WS2812FX, see: https://github.com/kitesurfer1404/WS2812FX + // Neopixel -#define PIN 2 // PIN (5 / D1) where neopixel / WS2811 strip is attached -#define NUMLEDS 144 // Number of leds in the strip -//#define BUILTIN_LED 2 // ESP-12F has the built in LED on GPIO2, see https://github.com/esp8266/Arduino/issues/2192 -#define BUTTON 0 // Input pin (4 / D2) for switching the LED strip on / off, connect this PIN to ground to trigger button. +#define PIN 15 // PIN (15 / D8) where neopixel / WS2811 strip is attached +#define NUMLEDS 194 // 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 14 // Input pin (14 / D5) for switching the LED strip on / off, connect this PIN to ground to trigger button. +#define BUTTON2 12 // Input pin (12 / D6) for read color data with RGB sensor, connect this PIN to ground to trigger button. -const char HOSTNAME[] = "ESPLightRGBW01"; // Friedly hostname +const char HOSTNAME[] = "ESPLightRGBW02"; // 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 ENABLE_BUTTON // If defined, enable button handling code, see: https://github.com/toblum/McLighting/wiki/Button-control +#define HTTP_OTA // If defined, enable Added ESP8266HTTPUpdateServer +//#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_BUTTON2 // +//#define MQTT_HOME_ASSISTANT_SUPPORT // If defined, use AMQTT and select Tools -> IwIP Variant -> Higher Bandwidth + + +#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) @@ -18,16 +41,39 @@ uint32_t autoParams[][4] = { // color, speed, mode, duration (seconds) {0x000000ff, 200, 46, 15.0} // fireworks for 15 seconds }; -#ifdef ENABLE_MQTT - #define MQTT_MAX_PACKET_SIZE 256 - #define MQTT_MAX_RECONNECT_TRIES 4 - - int mqtt_reconnect_retries = 0; - char mqtt_intopic[strlen(HOSTNAME) + 4]; // Topic in will be: /in - char mqtt_outtopic[strlen(HOSTNAME) + 5]; // Topic out will be: /out - - const char mqtt_clientid[] = "ESPLightMax"; // MQTT ClientID - +#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 + 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"; + + const char* on_cmd = "ON"; + const char* off_cmd = "OFF"; + bool stateOn = false; + bool animation_on = false; + 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 + char mqtt_host[64] = ""; char mqtt_port[6] = ""; char mqtt_user[32] = ""; @@ -41,7 +87,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, AUTO }; +enum MODE { SET_MODE, HOLD, OFF, ALL, SETCOLOR, SETSPEED, BRIGHTNESS, WIPE, RAINBOW, RAINBOWCYCLE, THEATERCHASE, TWINKLERANDOM, THEATERCHASERAINBOW, TV, CUSTOM, AUTO }; MODE mode = RAINBOW; // Standard mode that is active when software starts @@ -66,7 +112,8 @@ 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 = { 0, 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 +#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 char current_state[36]; // Keeps the current state representation char last_state[36]; // Save the last state as string representation @@ -74,12 +121,20 @@ LEDState main_color = { 0, 255, 0, 0}; // Store the "main color" of the strip u 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 || ENABLE_BUTTON2 + boolean buttonState = false; +#endif + #ifdef ENABLE_BUTTON - #define BTN_MODE_SHORT "STA| 1| 0|245|196| 0|255|255|255" // Static white - #define BTN_MODE_MEDIUM "STA| 1| 48|245|196| 0|255|102| 0" // Fire flicker - #define BTN_MODE_LONG "STA| 1| 46|253|196| 0|255|102| 0" // Fireworks random + #define BTN_MODE_SHORT "STA| 1| 0|245|196|255| 0| 0| 0" // Static white + #define BTN_MODE_MEDIUM "STA| 1| 48|245|196| 0|255|102| 0" // Fire flicker + #define BTN_MODE_LONG "STA| 1| 46|253|196| 0|255|102| 0" // Fireworks random unsigned long keyPrevMillis = 0; const unsigned long keySampleIntervalMs = 25; @@ -87,5 +142,18 @@ LEDState main_color = { 0, 255, 0, 0}; // Store the "main color" of the strip u byte mediumKeyPressCountMin = 20; // 20 * 25 = 500 ms byte KeyPressCount = 0; byte prevKeyState = HIGH; // button is active low - boolean buttonState = false; -#endif +#endif + +#ifdef ENABLE_BUTTON2 + #define BTN_MODE_SHORT "STA| 1| 0|245|196|255| 0| 0| 0" // Static white + #define BTN_MODE_MEDIUM "STA| 1| 48|245|196| 0|255|102| 0" // Fire flicker + #define BTN_MODE_LONG "STA| 1| 46|253|196| 0|255|102| 0" // Fireworks random + + unsigned long keyPrevMillis2 = 0; + const unsigned long keySampleIntervalMs2 = 25; + byte longKeyPressCountMax2 = 80; // 80 * 25 = 2000 ms + byte mediumKeyPressCountMin2 = 20; // 20 * 25 = 500 ms + byte KeyPressCount2 = 0; + byte prevKeyState2 = HIGH; // button is active low +#endif + diff --git a/Arduino/McLighting/request_handlers.h b/Arduino/McLighting/request_handlers.h index 23dddaa..757a4a6 100644 --- a/Arduino/McLighting/request_handlers.h +++ b/Arduino/McLighting/request_handlers.h @@ -1,18 +1,29 @@ // *************************************************************************** // 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); + uint32_t rgb = (uint32_t) strtoul(server.arg("rgb").c_str(), NULL, 16); main_color.white = ((rgb >> 24) & 0xFF); - main_color.red = ((rgb >> 16) & 0xFF); - main_color.green = ((rgb >> 8) & 0xFF); - main_color.blue = ((rgb >> 0) & 0xFF); + main_color.red = ((rgb >> 16) & 0xFF); + main_color.green = ((rgb >> 8) & 0xFF); + main_color.blue = ((rgb ) & 0xFF); } else { main_color.white = server.arg("w").toInt(); - main_color.red = server.arg("r").toInt(); + main_color.red = server.arg("r").toInt(); main_color.green = server.arg("g").toInt(); - main_color.blue = server.arg("b").toInt(); + main_color.blue = server.arg("b").toInt(); } ws2812fx_speed = constrain(server.arg("s").toInt(), 0, 255); if (server.arg("s") == "") { @@ -24,9 +35,9 @@ void getArgs() { } main_color.white = constrain(main_color.white, 0, 255); - main_color.red = constrain(main_color.red, 0, 255); + 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); + main_color.blue = constrain(main_color.blue, 0, 255); DBG_OUTPUT_PORT.print("Mode: "); DBG_OUTPUT_PORT.print(mode); @@ -45,8 +56,9 @@ void getArgs() { } -long convertSpeed(int mcl_speed) { - long ws2812_speed = mcl_speed * 256; +uint16_t convertSpeed(uint8_t mcl_speed) { + //long ws2812_speed = mcl_speed * 256; + uint16_t ws2812_speed = 61760 * (exp(0.0002336 * mcl_speed) - exp(-0.03181 * mcl_speed)); ws2812_speed = SPEED_MAX - ws2812_speed; if (ws2812_speed < SPEED_MIN) { ws2812_speed = SPEED_MIN; @@ -63,28 +75,28 @@ long convertSpeed(int mcl_speed) { // *************************************************************************** void handleSetMainColor(uint8_t * mypayload) { // decode rgb data - uint32_t rgb = (uint32_t) strtol((const char *) &mypayload[1], NULL, 16); + uint32_t rgb = (uint32_t) strtoul((const char *) &mypayload[1], NULL, 16); main_color.white = ((rgb >> 24) & 0xFF); - main_color.red = ((rgb >> 16) & 0xFF); - main_color.green = ((rgb >> 8) & 0xFF); - main_color.blue = ((rgb >> 0) & 0xFF); + main_color.red = ((rgb >> 16) & 0xFF); + main_color.green = ((rgb >> 8) & 0xFF); + main_color.blue = ((rgb ) & 0xFF); strip.setColor(main_color.white, 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); + uint32_t rgb = (uint32_t) strtoul((const char *) &mypayload[1], NULL, 16); main_color.white = ((rgb >> 24) & 0xFF); - main_color.red = ((rgb >> 16) & 0xFF); - main_color.green = ((rgb >> 8) & 0xFF); - main_color.blue = ((rgb >> 0) & 0xFF); + main_color.red = ((rgb >> 16) & 0xFF); + main_color.green = ((rgb >> 8) & 0xFF); + main_color.blue = ((rgb ) & 0xFF); for (int i = 0; i < strip.numPixels(); i++) { - strip.setPixelColor(i, main_color.white, main_color.red, main_color.green, main_color.blue); + strip.setPixelColor(i, main_color.red, main_color.green, main_color.blue, main_color.white); } strip.show(); - DBG_OUTPUT_PORT.printf("WS: Set all leds to main color: [%u] [%u] [%u]\n", main_color.white, main_color.red, main_color.green, main_color.blue); + DBG_OUTPUT_PORT.printf("WS: Set all leds to main color: W: [%u] R: [%u] G: [%u] B: [%u]\n", main_color.white, main_color.red, main_color.green, main_color.blue); exit_func = true; mode = ALL; } @@ -114,7 +126,7 @@ void handleSetSingleLED(uint8_t * mypayload, uint8_t firstChar = 0) { DBG_OUTPUT_PORT.printf("WS: Set single led [%i] to [%i] [%i] [%i] [%i] (%s)!\n", led, ledstates[led].white, ledstates[led].red, ledstates[led].green, ledstates[led].blue, mypayload); - strip.setPixelColor(led, ledstates[led].white, ledstates[led].red, ledstates[led].green, ledstates[led].blue); + strip.setPixelColor(led, ledstates[led].red, ledstates[led].green, ledstates[led].blue, ledstates[led].white); strip.show(); } exit_func = true; @@ -184,6 +196,7 @@ void setModeByStateString(String saved_state_string) { main_color.green = str_green.toInt(); String str_blue = getValue(saved_state_string, '|', 8); 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); @@ -191,7 +204,7 @@ void setModeByStateString(String saved_state_string) { 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); @@ -203,30 +216,57 @@ void handleSetNamedMode(String str_mode) { 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 } } @@ -271,7 +311,6 @@ void getModesJSON() { server.send ( 200, "application/json", listModesJSON() ); } - // *************************************************************************** // HTTP request handlers // *************************************************************************** @@ -367,8 +406,21 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght // # ==> 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, main_color.white); + DBG_OUTPUT_PORT.printf("Set main color to: R: [%u] G: [%u] B: [%u] W: [%u]\n", main_color.red, main_color.green, main_color.blue, main_color.white); 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 @@ -378,6 +430,15 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght strip.setSpeed(convertSpeed(ws2812fx_speed)); DBG_OUTPUT_PORT.printf("WS: Set speed to: [%u]\n", ws2812fx_speed); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_HOMEASSISTANT + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif } // % ==> Set brightness @@ -387,30 +448,74 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght DBG_OUTPUT_PORT.printf("WS: Set brightness to: [%u]\n", brightness); strip.setBrightness(brightness); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + 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 @@ -422,6 +527,18 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght DBG_OUTPUT_PORT.printf("Activated mode [%u]!\n", mode); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + 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. @@ -431,6 +548,13 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght String json = listStatusJSON(); DBG_OUTPUT_PORT.println(json); webSocket.sendTXT(num, json); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, listStatusJSON()); + #endif + #ifdef ENABLE_AMQTT + String liststat = (String) listStatusJSON(); + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, liststat.c_str()); + #endif } // ~ ==> Get WS2812 modes. @@ -440,12 +564,37 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght String json = listModesJSON(); DBG_OUTPUT_PORT.println(json); webSocket.sendTXT(num, json); + #ifdef ENABLE_MQTT + DBG_OUTPUT_PORT.printf("Error: Not implemented. Message too large for pubsubclient."); + mqtt_client.publish(mqtt_outtopic, "ERROR: Not implemented. Message too large for pubsubclient."); + //String json_modes = listModesJSON(); + //DBG_OUTPUT_PORT.printf(json_modes.c_str()); + + //int res = mqtt_client.publish(mqtt_outtopic, json_modes.c_str(), json_modes.length()); + //DBG_OUTPUT_PORT.printf("Result: %d / %d", res, json_modes.length()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String("ERROR: Not implemented. Message too large for AsyncMQTT.").c_str()); + #endif } // / ==> Set WS2812 mode. if (payload[0] == '/') { handleSetWS2812FXMode(payload); webSocket.sendTXT(num, "OK"); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + 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 @@ -466,139 +615,557 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght void checkForRequests() { webSocket.loop(); server.handleClient(); + #ifdef ENABLE_MQTT + mqtt_client.loop(); + #endif } // *************************************************************************** // MQTT callback / connection handler // *************************************************************************** -#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); +#if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) - // # ==> 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, main_color.white); - mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #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(6); + //StaticJsonBuffer jsonBuffer; + DynamicJsonBuffer jsonBuffer(bufferSize); + JsonObject& root = jsonBuffer.createObject(); + + root["state"] = (stateOn) ? on_cmd : off_cmd; + JsonObject& color = root.createNestedObject("color"); + color["r"] = main_color.red; + color["g"] = main_color.green; + color["b"] = main_color.blue; + + root["brightness"] = brightness; + + root["color_temp"] = color_temp; + + root["speed"] = ws2812fx_speed; + + char modeName[30]; + strncpy_P(modeName, (PGM_P)strip.getModeName(strip.getMode()), sizeof(modeName)); // copy from progmem + root["effect"] = modeName; + + char buffer[root.measureLength() + 1]; + root.printTo(buffer, sizeof(buffer)); + + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_ha_state_out.c_str(), buffer, true); + DBG_OUTPUT_PORT.printf("MQTT: Send [%s]: %s\n", mqtt_ha_state_out.c_str(), buffer); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_ha_state_out.c_str(), 1, true, buffer); + DBG_OUTPUT_PORT.printf("MQTT: Send [%s]: %s\n", mqtt_ha_state_out.c_str(), buffer); + #endif + new_ha_mqtt_msg = false; + ha_send_data.detach(); + DBG_OUTPUT_PORT.printf("Heap size: %u\n", ESP.getFreeHeap()); + } + + bool processJson(char* message) { + 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("speed")) { + uint8_t json_speed = constrain((uint8_t) root["speed"], 0, 255); + if (json_speed != ws2812fx_speed) { + ws2812fx_speed = json_speed; + if(stateOn) mode = SETSPEED; + } + } + + 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 + #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_HOMEASSISTANT + if(!ha_send_data.active()) ha_send_data.once(5, tickerSendState); + #endif + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif + } + + // % ==> Set brightness + if (payload[0] == '%') { + uint8_t b = (uint8_t) strtol((const char *) &payload[1], NULL, 10); + brightness = constrain(b, 0, 255); + strip.setBrightness(brightness); + DBG_OUTPUT_PORT.printf("MQTT: Set brightness to [%u]\n", brightness); + #ifdef ENABLE_MQTT + mqtt_client.publish(mqtt_outtopic, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_AMQTT + amqttClient.publish(mqtt_outtopic.c_str(), qospub, false, String(String("OK ") + String((char *)payload)).c_str()); + #endif + #ifdef ENABLE_HOMEASSISTANT + stateOn = true; + 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); } - // ? ==> 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()); + #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); + #ifdef ENABLE_HOMEASSISTANT + ha_send_data.detach(); + mqtt_client.subscribe(mqtt_ha_state_in.c_str(), qossub); + #ifdef MQTT_HOME_ASSISTANT_SUPPORT + DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(strip.getModeCount()) + JSON_OBJECT_SIZE(11)); + JsonObject& json = jsonBuffer.createObject(); + json["name"] = HOSTNAME; + json["platform"] = "mqtt_json"; + json["state_topic"] = mqtt_ha_state_out; + json["command_topic"] = mqtt_ha_state_in; + json["on_command_type"] = "first"; + json["brightness"] = "true"; + json["rgb"] = "true"; + json["optimistic"] = "false"; + json["color_temp"] = "true"; + json["effect"] = "true"; + JsonArray& effect_list = json.createNestedArray("effect_list"); + for (uint8_t i = 0; i < strip.getModeCount(); i++) { + effect_list.add(strip.getModeName(i)); + } + char buffer[json.measureLength() + 1]; + json.printTo(buffer, sizeof(buffer)); + mqtt_client.publish(String("homeassistant/light/" + String(HOSTNAME) + "/config").c_str(), buffer, true); + #endif + #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(); + } - // % ==> 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()); - } + void connectToMqtt() { + DBG_OUTPUT_PORT.println("Connecting to MQTT..."); + amqttClient.connect(); + } - // * ==> 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()); - } + void onWifiConnect(const WiFiEventStationModeGotIP& event) { + DBG_OUTPUT_PORT.println("Connected to Wi-Fi."); + connectToMqtt(); + } - // ! ==> 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()); - } + 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); + } - // + ==> 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()); - } - - // $ ==> Get status Info. - if (payload[0] == '$') { - DBG_OUTPUT_PORT.printf("MQTT: Get status info.\n"); - 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()); - } - - 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... + 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); - mqtt_client.publish(mqtt_outtopic, message); - // ... and resubscribe - mqtt_client.subscribe(mqtt_intopic); - - 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); + 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); + #ifdef MQTT_HOME_ASSISTANT_SUPPORT + DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(strip.getModeCount()) + JSON_OBJECT_SIZE(11)); + JsonObject& json = jsonBuffer.createObject(); + json["name"] = HOSTNAME; + json["platform"] = "mqtt_json"; + json["state_topic"] = mqtt_ha_state_out; + json["command_topic"] = mqtt_ha_state_in; + json["on_command_type"] = "first"; + json["brightness"] = "true"; + json["rgb"] = "true"; + json["optimistic"] = "false"; + json["color_temp"] = "true"; + json["effect"] = "true"; + JsonArray& effect_list = json.createNestedArray("effect_list"); + for (uint8_t i = 0; i < strip.getModeCount(); i++) { + effect_list.add(strip.getModeName(i)); + } + char buffer[json.measureLength() + 1]; + json.printTo(buffer, sizeof(buffer)); + DBG_OUTPUT_PORT.println(buffer); + amqttClient.publish(String("homeassistant/light/" + String(HOSTNAME) + "/config").c_str(), qospub, true, buffer); + #endif + #endif } - } - if (mqtt_reconnect_retries >= MQTT_MAX_RECONNECT_TRIES) { - DBG_OUTPUT_PORT.printf("MQTT connection failed, giving up after %d tries ...\n", mqtt_reconnect_retries); - } -} + + 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 @@ -606,70 +1173,380 @@ void mqtt_reconnect() { // 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 + 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 less than 2 seconds -void mediumKeyPress() { - DBG_OUTPUT_PORT.printf("Medium button press\n"); - setModeByStateString(BTN_MODE_MEDIUM); - buttonState = true; - #ifdef ENABLE_MQTT - mqtt_client.publish(mqtt_outtopic, String("OK =fire flicker").c_str()); - #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 + } -// 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); - buttonState = true; - #ifdef ENABLE_MQTT - mqtt_client.publish(mqtt_outtopic, String("OK =fireworks random").c_str()); - #endif -} + void button() { + if (millis() - keyPrevMillis >= keySampleIntervalMs) { + keyPrevMillis = millis(); -void button() { - if (millis() - keyPrevMillis >= keySampleIntervalMs) { - keyPrevMillis = millis(); + byte currKeyState = digitalRead(BUTTON); - 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(); + if ((prevKeyState == HIGH) && (currKeyState == LOW)) { + // key goes from not pressed to pressed + KeyPressCount = 0; } - else { - if (KeyPressCount < mediumKeyPressCountMin) { - shortKeyPress(); + 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(); + else if (currKeyState == LOW) { + KeyPressCount++; + if (KeyPressCount >= longKeyPressCountMax) { + longKeyPress(); + } } + prevKeyState = currKeyState; } - prevKeyState = currKeyState; + } +#endif + +#ifdef ENABLE_BUTTON2 + void shortKeyPress2() { + DBG_OUTPUT_PORT.printf("Short button2 press\n"); + if (buttonState == false) { + uint16_t red, green, blue, cl, ct, lux; +// tcs.setInterrupt(false); // turn on LED + delay(60); // takes 50ms to read + tcs.getRawData(&red, &green, &blue, &cl, &ct); +// ct = tcs.calculateColorTemperature(red, green, blue); + lux = tcs.calculateLux(red, green, blue); +// tcs.setInterrupt(true); // turn off LED + // Figure out some basic hex code for visualization + uint32_t sum = cl; +/* float r, g, b, col; + r = red; r /= sum; + g = green; g /= sum; + b = blue; b /= sum; + r *= 256; g *= 256; b *= 256;*/ + uint8_t r, g, b, col; + tcs.getData(&r, &g, &b, &col); + DBG_OUTPUT_PORT.printf("Colors: R: [%d] G: [%d] B: [%d] Clear: [%d] Colortemp: [%d] Lux: [%d]\n", (int)r, (int)g, (int)b, (int)cl, (int)ct, (int)lux ); + char newmode[38]; + sprintf(newmode, "STA| 1| 0|245|196|0|%3d|%3d|%3d", (int)r, (int)g, (int)b); + DBG_OUTPUT_PORT.println(newmode); + // setModeByStateString(newmode); + main_color.white = 0; main_color.red = gammatable[(int)r]; main_color.green = gammatable[(int)g]; main_color.blue = gammatable[(int)b]; + mode = HOLD; + strip.setMode(0); + strip.setSpeed(convertSpeed(245)); + strip.setBrightness(196); + strip.setColor(main_color.white, main_color.red, main_color.green, main_color.blue); + buttonState = false; + #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 mediumKeyPress2() { + DBG_OUTPUT_PORT.printf("Medium button2 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 longKeyPress2() { + DBG_OUTPUT_PORT.printf("Long button2 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 button2() { + if (millis() - keyPrevMillis2 >= keySampleIntervalMs2) { + keyPrevMillis2 = millis(); + + byte currKeyState2 = digitalRead(BUTTON2); + + if ((prevKeyState2 == HIGH) && (currKeyState2 == LOW)) { + // key goes from not pressed to pressed + KeyPressCount2 = 0; + } + else if ((prevKeyState2 == LOW) && (currKeyState2 == HIGH)) { + if (KeyPressCount2 < longKeyPressCountMax2 && KeyPressCount2 >= mediumKeyPressCountMin2) { + mediumKeyPress2(); + } + else { + if (KeyPressCount2 < mediumKeyPressCountMin2) { + shortKeyPress2(); + } + } + } + else if (currKeyState2 == LOW) { + KeyPressCount2++; + if (KeyPressCount2 >= longKeyPressCountMax2) { + longKeyPress2(); + } + } + prevKeyState2 = currKeyState2; + } + } +#endif + +#ifdef ENABLE_STATE_SAVE_SPIFFS +bool updateFS = false; +#if defined(ENABLE_MQTT) or defined(ENABLE_AMQTT) +// 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; } } -#endif + +// 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; +} +#endif + +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["white"] = main_color.white; + 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.white = json["white"]; + 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.white, 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/README.md b/README.md index bc6e79b..231d988 100644 --- a/README.md +++ b/README.md @@ -6,27 +6,51 @@ > Because of it's open architecture and APIs it's easy to build new clients for different platforms (iOS, Android, Windows Universal Apps, Siri/Cortana integration, ...). +[![Demo video WebClient](https://j.gifs.com/kRPrzN.gif)](https://youtu.be/rc6QVHKAXBs) + +[![Demo video Apple Homekit integration](https://j.gifs.com/gJP2o6.gif)](https://youtu.be/4JnGXZaPnrw) + ___ -Update 17.02.2018: +Update 07.04.2018: +And even more changes to McLighting! Most of them were contributed by user @debsahu. Thank you! +- Update arduino-esp8266 to latest, at least version 2.4.1 +- AMQTT is now the default MQTT library, it's a bit more lightweight and stable. You can still use PubSubClient if you want to. +- You can use @debsahu great NeoAnimationFX library as a alternative to WS2812FX. It's based on the NeoPixelBus instead of Adafruits NeoPixel library. It can handle longer strips more efficient. If you want, give it a try. WS2812FX is still the default. +- Some more changes regarding Homeassistant integration. +Please see the [Wiki](https://github.com/toblum/McLighting/wiki/Software-installation) for details on the required libraries. +If you have problems with the new version, let us know. You can get the last version [here](https://github.com/toblum/McLighting/tree/Before_AMQTT_NeoAnimationFX). + +I'm also working on a alternative web interface for McLighting in the meanwhile, but it may take some more time. +For the german users: McLighting was used in [Kliemannsland](https://youtu.be/3TUjszkS3bY?t=1211) (a funny web show) when they built a really big Neopixel installation. + +Update 18.03.2018: +The code for integration with homeassistant was merged into master. It's currently active by default. You can safely disable it in definitions.h when use do not want to use it, or want to use McLighting on a small ESP_01. +There are some informations in the [Wiki](https://github.com/toblum/McLighting/wiki/Homeassistant-integration). + +Update 17.02.2018: User @debsahu contributed code for integration with homeassistant. It's currently in a separate branch (https://github.com/toblum/McLighting/tree/feature/ha_integration). If you're using Homeassistant, please try it out and give feedback. User @FabLab-Luenen created a version of McLighting (https://github.com/FabLab-Luenen/McLighting) for 6812 and other RGBW strips. Give it a try, if you own such strips. A thank you goes to all contributors. -Update 31.01.2018: +Update 12. / 15.02.2018: +Added Home Assistant Support using MQTT Light. A better implementation would be using MQTT Light JSON. +Replaced Home Assistant Support using MQTT Light to MQTT JSON Light. + +Update 31.01.2018: User @codmpm did a very professional McLighting installation and even designed his own PCBs. He has a great writeup for his project at: https://allgeek.de/2018/01/29/esp8266-neopixel-controller/ (in german). -Update 27.01.2018: +Update 27.01.2018: Many people asked if it's possible to connect more than one strip (currently not) or at least "sync" multiple McLighting nodes. Although it may be possible to connect more then one WS2812 strip to the same data pin (works in many cases, you just have to try), syncing many McLighting instances would be a benefit. This could easily be achieved done by software like [NodeRed](https://nodered.org/). I added a example flow to demonstrate that [here](https://github.com/toblum/McLighting/blob/master/clients/node_red/websocket_proxy.json). Have a look at the short video [here](https://youtu.be/g3CHtG9c520). -Update 21.01.2018: +Update 21.01.2018: User @szepnorbee contributed code for button control. Thank you! It's merged into the master branch now. There is a short manual for configuration [here](https://github.com/toblum/McLighting/wiki/Button-control). -Update 06.01.2018: +Update 06.01.2018: After som etesting I merged the "feature/save_state" banch into master, so everybody should now be able to use this new functionality. Basically McLighting now saves the current mode to EEPROM and restores the setting on reboot. So you wont need to select your favorite mode again. If you don't want to use this, you can disable it in definitions.h. ~~Some people noticed that there are currently problems compiling McLighting whe using ESP8266 core in version 2.4.0. This is due to a [problem](https://github.com/kitesurfer1404/WS2812FX/issues/58) with WS2812FX when using this version. For the moment you can stick to the 2.4.0 RC2 (also easily available via the boards manager).~~ (fixed now ) Funny! McLighting was featured in the german radio show ["Netzbasteln"](https://www.deutschlandfunknova.de/beitrag/netzbasteln-wolkenlampe-mit-cloud-anschluss) on Deutschlandfunk Nova with a nice audio tutorial. -Update 16.12.2017: +Update 16.12.2017: There was a breaking change in the WS2812FX library: Speeds have a new format (65535-0 instead of 0-255). I released a new version that converts the speeds settings. Please use the latest [WS2812FX library](https://github.com/kitesurfer1404/WS2812FX) (14.12.2017 or later) if use have an existing version installed. I got many messages from people who use McLighting for own projects. User Brian Lough built a lighting system for his wedding and made a nice instruction video for his build: https://goo.gl/NbfKi8 @@ -34,25 +58,25 @@ Update 30.09.2017: Thanks to [@moose4lord](https://github.com/moose4lord) Mclighting works with the newest version of WS1812FX and has a possibility to define autocycle patterns [Wiki](https://github.com/toblum/McLighting/wiki/Autocycling). Thank for contributing to McLighting everyone! I was also informed of a new project that is loosely based on McLighting: [Responsive_LED_Control](https://github.com/doctormord/Responsive_LED_Control) That looks very promising. -Update 07.08.2017: +Update 07.08.2017: As requested by many of you, McLighting now also features MQTT support. Thanks at @LeonVos for his attempts on this. I implemented the same API as used in WebSockets now for MQTT. Please have a look here for details: https://github.com/toblum/McLighting/wiki/MQTT-API I will try to add a new instruction video soon. Many of you also took McLighting and adapted the software according your needs. This is great. I found some videos on YouTube that show these projects. I collected them here: https://goo.gl/yG7M4h If you have done something similar with McLighting, please drop me a note. I'm always interested in what you've done with it. -Update 19.02.2017: +Update 19.02.2017: Added OTA support as promised by @markbajaj. Minor other improvements. -Update 05.02.2017: +Update 05.02.2017: After a long time I was able to work a bit on McLighting v2 and it's finally out now. The main difference, among minor improvements and library updates, is the usage of the great WS2812FX library for color animations. It brings a lot (almost 50!) of new animations. The API changed a little bit, because the speed can now be set as a value from 0 to 255, not the delay anymore. So the web inferface had to change accordingly. The new animation mode have to be set also by their number, instead of a dedicated url. The list of all animation modes can also be received by the API. All existing API endpoints are kept for downward compatibility. So you should be able to use the new version without big changes. The original version is kept as branch "mclighting_legacy". Documentation will be updated soon. -Update 04.01.2017: +Update 04.01.2017: Now, there are two forks of McLighting (using the famous FastLED library). I did not notice it first, because I currently do not receive notification e-mails by Github (I have no idea why). Maybe you want to give them also a try, I will definitely do so as soon as I find time. https://github.com/russp81/LEDLAMP_FASTLEDs And this one was also forked: https://github.com/jake-b/Griswold-LED-Controller -Update 12.08.2016: +Update 12.08.2016: There is now a [gitter.im](https://gitter.im/mclighting/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link) chat room for this project. Update 11.06.2016: @@ -60,11 +84,6 @@ Today I presented the project at [Pi and More 9](https://piandmore.de/) and got ___ -[![Demo video WebClient](https://j.gifs.com/kRPrzN.gif)](https://youtu.be/rc6QVHKAXBs) - -[![Demo video Apple Homekit integration](https://j.gifs.com/gJP2o6.gif)](https://youtu.be/4JnGXZaPnrw) - - ## The Hardware The project ist based on the famous ESP8266 microcontroller and WD2811/WS2812 LED strips. There are many variations of the ESP chip out there, but I chose the NodeMCU dev board, because it's powered by micro USB and has a voltage converter included to power the ESP which uses 3.3V. @@ -110,15 +129,14 @@ I hope I didn't miss any sources and mentioned every author. In case I forgot so ## Todos - [x] MQTT support -- [ ] Support multiple strips and control them separately or together +- [ ] Support multiple strips and control them separately or together [Issue](https://github.com/toblum/McLighting/issues/118) - [ ] Save favourite effects? [Issue](https://github.com/toblum/McLighting/issues/35) -- [ ] Make number of pixels, MQTT and PIN configurable via front end [Issue](https://github.com/toblum/McLighting/issues/93) -- [ ] OTA update [Issue](https://github.com/toblum/McLighting/issues/93) +- [ ] Make number of pixels, MQTT and PIN configurable via front end [Issue](https://github.com/toblum/McLighting/issues/93) and [Issue](https://github.com/toblum/McLighting/issues/101) +- [x] OTA update [Issue](https://github.com/toblum/McLighting/issues/93) - [ ] Bundle webpages instead of SPIFFS [Issue](https://github.com/toblum/McLighting/issues/93) - [ ] Remove old / wrong EEPROM settings completely (https://github.com/toblum/McLighting/issues/92) - [x] Fix issue with websockets connection problems -- [ ] Add support for 433MHz wireless socket using the [RC switch](https://github.com/sui77/rc-switch) library. -- [ ] Switch to the [NeoPixelBus library](https://github.com/Makuna/NeoPixelBus/wiki) +- [x] Switch to the [NeoPixelBus library](https://github.com/Makuna/NeoPixelBus/wiki) - [x] Use the led strip for status information in connection phase - [x] Enhance the documentation - [x] Stability improvements @@ -128,9 +146,11 @@ I hope I didn't miss any sources and mentioned every author. In case I forgot so - [x] Button control [Issue](https://github.com/toblum/McLighting/issues/36) - [x] Retain last state [Issue](https://github.com/toblum/McLighting/issues/47) - [ ] Additional clients -- [ ] If no wifi, at least enable button mode. [Issue](https://github.com/toblum/McLighting/issues/88) +- [ ] If no wifi, at least enable button mode. - [ ] Also enable McLighting in Wifi AP mode. -- [ ] Make a set of NodeRed nodes. +- [x] Make a set of NodeRed nodes. +- [ ] Multiple buttons/GPIO Inputs. [Issue](https://github.com/toblum/McLighting/issues/119) +- [ ] Music visualizer / Bring back ArtNet [Issue](https://github.com/toblum/McLighting/issues/111) ## Licence diff --git a/clients/HomeAssistant/light.yaml b/clients/HomeAssistant/light.yaml new file mode 100644 index 0000000..dc43d4f --- /dev/null +++ b/clients/HomeAssistant/light.yaml @@ -0,0 +1,104 @@ +light: + - platform: mqtt_json + 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: + ###### + - "Blink" + - "Breath" + - "Color Wipe" + - "Color Wipe Inverse" + - "Color Wipe Reverse" + - "Color Wipe Reverse Inverse" + - "Color Wipe Random" + - "Random Color" + - "Single Dynamic" + - "Multi Dynamic" + - "Rainbow" + - "Rainbow Cycle" + - "Scan" + - "Dual Scan" + - "Fade" + - "Theater Chase" + - "Theater Chase Rainbow" + - "Running Lights" + - "Twinkle" + - "Twinkle Random" + - "Twinkle Fade" + - "Twinkle Fade Random" + - "Sparkle" + - "Flash Sparkle" + - "Hyper Sparkle" + - "Strobe" + - "Strobe Rainbow" + - "Multi Strobe" + - "Blink Rainbow" + - "Chase White" + - "Chase Color" + - "Chase Random" + - "Chase Rainbow" + - "Chase Flash" + - "Chase Flash Random" + - "Chase Rainbow White" + - "Chase Blackout" + - "Chase Blackout Rainbow" + - "Color Sweep Random" + - "Running Color" + - "Running Red Blue" + - "Running Random" + - "Larson Scanner" + - "Comet" + - "Fireworks" + - "Fireworks Random" + - "Merry Christmas" + - "Fire Flicker" + - "Fire Flicker (soft)" + - "Fire Flicker (intense)" + - "Circus Combustus" + - "Halloween" + - "Bicolor Chase" + - "Tricolor Chase" + - "ICU" + brightness: true + color_temp: true + rgb: true + optimistic: false + qos: 0 + retain: true + +input_number: + neopixel_animation_speed: + name: NeoPixel Animation Speed + initial: 200 + min: 0 + max: 255 + step: 5 + +automation: + - id: 71938579813759813757 + alias: NeoPixel Animation Speed Send + initial_state: true + hide_entity: false + trigger: + - entity_id: input_number.neopixel_animation_speed + platform: state + action: + - data_template: + payload_template: '{"speed": {{ trigger.to_state.state | int }}}' + retain: true + topic: home/McLighting01_ha/state/in + service: mqtt.publish + + - id: 93786598732698756967 + alias: NeoPixel Animation Speed Receive + trigger: + - platform: mqtt + topic: home/McLighting01_ha/state/out + action: + - data_template: + entity_id: input_number.neopixel_animation_speed + value: '{{ trigger.payload_json.speed | int }}' + service: input_number.set_value \ No newline at end of file diff --git a/documentation/pics/arduino_boards_manager.png b/documentation/pics/arduino_boards_manager.png index 13934f5..642009b 100644 Binary files a/documentation/pics/arduino_boards_manager.png and b/documentation/pics/arduino_boards_manager.png differ diff --git a/documentation/pics/board_settings.png b/documentation/pics/board_settings.png new file mode 100644 index 0000000..95a65f7 Binary files /dev/null and b/documentation/pics/board_settings.png differ