diff --git a/README.md b/README.md index 7217eeb11..5e2af2338 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Sonoff-Tasmota Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE. -Current version is **5.2.4** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/master/sonoff/_releasenotes.ino) for change information. +Current version is **5.3.0** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/master/sonoff/_releasenotes.ino) for change information. ### **** ATTENTION Version 5.x.x specific information **** diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index 3c47625d1..c77e42fd2 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,12 @@ -/* 5.2.4 20170703 +/* 5.3.0 20170715 + * Major Hue rewrite which might introduce Alexa problems. If so, initiate an issue + * Add support for Sonoff Led and BN-SZ01 Ceiling Led brightness control to Hue + * Fix Sonoff Led Power, Dimmer and Color MQTT response (#176) + * Add commands Delay and Backlog to allow multiple commands at once separated by ";" (#593) + * Use default flashmode DOUT to solve restart hangs on esp8285 chips (#453, #598) + * Change Web console column width from 99 to 300 (#599) + * + * 5.2.4 20170703 * Removed flash mode update after selecting different module solving esp8285 related problems * Add device type flag to sonoff_template.ino * Change Sonoff Led Wakeup and add support for Sonoff BN-SZ01 Led (#567) diff --git a/sonoff/settings.ino b/sonoff/settings.ino index aae172d69..2af936703 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -130,7 +130,7 @@ extern "C" uint32_t _SPIFFS_end; // Version 4.2 config = eeprom area #define CFG_LOCATION SPIFFS_END // No need for SPIFFS as it uses EEPROM area // Version 5.2 allow for more flash space -#define CFG_ROTATES 8 // Number of additional flash sectors used (handles uploads) +#define CFG_ROTATES 8 // Number of flash sectors used (handles uploads) uint32_t _cfgHash = 0; uint32_t _cfgLocation = CFG_LOCATION; @@ -171,16 +171,6 @@ void setFlashMode(byte option, byte mode) delete[] _buffer; } -void setModuleFlashMode(byte option) -{ - uint8_t mode = 0; // QIO - ESP8266 -// if ((SONOFF_TOUCH == sysCfg.module) || (SONOFF_4CH == sysCfg.module)) { - if (sysCfg.my_module.flag &1) { - mode = 3; // DOUT - ESP8285 - } - setFlashMode(option, mode); -} - uint32_t getHash() { uint32_t hash = 0; diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 0c13f819f..576b0ebde 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -20,11 +20,12 @@ Prerequisites: - Change libraries/PubSubClient/src/PubSubClient.h #define MQTT_MAX_PACKET_SIZE 512 - - - Select IDE Tools - Flash size: "1M (no SPIFFS)" + + - Select IDE Tools - Flash Mode: "DOUT" + - Select IDE Tools - Flash Size: "1M (no SPIFFS)" ====================================================*/ -#define VERSION 0x05020400 // 5.2.4 +#define VERSION 0x05030000 // 5.3.0 enum log_t {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE, LOG_LEVEL_ALL}; enum week_t {Last, First, Second, Third, Fourth}; @@ -136,7 +137,7 @@ enum emul_t {EMUL_NONE, EMUL_WEMO, EMUL_HUE, EMUL_MAX}; #define SERIALLOG_TIMER 600 // Seconds to disable SerialLog #define OTA_ATTEMPTS 10 // Number of times to try fetching the new firmware -#define INPUT_BUFFER_SIZE 100 // Max number of characters in serial buffer +#define INPUT_BUFFER_SIZE 250 // Max number of characters in (serial) command buffer #define CMDSZ 20 // Max number of characters in command #define TOPSZ 100 // Max number of characters in topic string #define LOGSZ 128 // Max number of characters in log string @@ -145,6 +146,8 @@ enum emul_t {EMUL_NONE, EMUL_WEMO, EMUL_HUE, EMUL_MAX}; #else #define MAX_LOG_LINES 20 // Max number of lines in weblog #endif +#define MAX_BACKLOG 16 // Max number of commands in backlog (chk blogidx and blogptr code) +#define MIN_BACKLOG_DELAY 2 // Minimal backlog delay in 0.1 seconds #define APP_BAUDRATE 115200 // Default serial baudrate #define MAX_STATUS 11 // Max number of status lines @@ -179,7 +182,7 @@ enum opt_t {P_HOLD_TIME, P_MAX_POWER_RETRY, P_MAX_PARAM8}; // Index in sysCf #ifdef USE_I2C #include // I2C support library #endif // USE_I2C -#ifdef USI_SPI +#ifdef USE_SPI #include // SPI support, TFT #endif // USE_SPI #include "settings.h" @@ -257,6 +260,11 @@ uint8_t blink_powersave; // Blink start power save state uint16_t mqtt_cmnd_publish = 0; // ignore flag for publish command uint8_t latching_power = 0; // Power state at latching start uint8_t latching_relay_pulse = 0; // Latching relay pulse timer +String Backlog[MAX_BACKLOG]; // Command backlog +uint8_t blogidx = 0; // Command backlog index +uint8_t blogptr = 0; // Command backlog pointer +uint8_t blogmutex = 0; // Command backlog pending +uint16_t blogdelay = 0; // Command backlog delay #ifdef USE_MQTT_TLS WiFiClientSecure espClient; // Wifi Secure Client @@ -936,6 +944,7 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) payload = (int16_t) lnum; // -32766 - 32767 payload16 = (uint16_t) lnum; // 0 - 65535 } + blogdelay = MIN_BACKLOG_DELAY; // Reset backlog delay if (!strcmp_P(dataBufUc,PSTR("OFF")) || !strcmp_P(dataBufUc,PSTR("FALSE")) || !strcmp_P(dataBufUc,PSTR("STOP")) || !strcmp_P(dataBufUc,PSTR("CELSIUS"))) { payload = 0; @@ -956,7 +965,34 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) // snprintf_P(svalue, sizeof(svalue), PSTR("RSLT: Payload %d, Payload16 %d"), payload, payload16); // addLog(LOG_LEVEL_DEBUG, svalue); - if (!strcmp_P(type,PSTR("POWER")) && (index > 0) && (index <= Maxdevice)) { + if (!strcmp_P(type,PSTR("BACKLOG"))) { + if (data_len) { + char *blcommand = strtok(dataBuf, ";"); + while (blcommand != NULL) { + Backlog[blogidx] = String(blcommand); + blogidx++; +/* + if (blogidx >= MAX_BACKLOG) { + blogidx = 0; + } +*/ + blogidx &= 0xF; + blcommand = strtok(NULL, ";"); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Backlog\":\"Appended\"}")); + } else { + uint8_t blflag = (blogptr == blogidx); + blogptr = blogidx; + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Backlog\":\"%s\"}"), blflag ? "Empty" : "Aborted"); + } + } + else if (!strcmp_P(type,PSTR("DELAY"))) { + if ((payload >= MIN_BACKLOG_DELAY) && (payload <= 3600)) { + blogdelay = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Delay\":%d}"), blogdelay); + } + else if (!strcmp_P(type,PSTR("POWER")) && (index > 0) && (index <= Maxdevice)) { if ((payload < 0) || (payload > 4)) { payload = 9; } @@ -1148,7 +1184,6 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) for (byte i = 0; i < MAX_GPIO_PIN; i++) { sysCfg.my_module.gp.io[i] = 0; } -// setModuleFlashMode(0); // Fails on esp8285 based devices } restartflag = 2; } @@ -1646,12 +1681,19 @@ void do_cmnd_power(byte device, byte state) { // device = Relay number 1 and up // state 0 = Relay Off -// state 1 = Relay on (turn off after sysCfg.pulsetime * 100 mSec if enabled) +// state 1 = Relay On (turn off after sysCfg.pulsetime * 100 mSec if enabled) // state 2 = Toggle relay // state 3 = Blink relay // state 4 = Stop blinking relay +// state 6 = Relay Off and no publishPowerState +// state 7 = Relay On and no publishPowerState // state 9 = Show power state + uint8_t publishPower = 1; + if ((6 == state) || (7 == state)) { + state &= 1; + publishPower = 0; + } if ((device < 1) || (device > Maxdevice)) { device = 1; } @@ -1698,7 +1740,9 @@ void do_cmnd_power(byte device, byte state) } return; } - mqtt_publishPowerState(device); + if (publishPower) { + mqtt_publishPowerState(device); + } } void stop_all_power_blink() @@ -1718,7 +1762,7 @@ void stop_all_power_blink() void do_cmnd(char *cmnd) { char stopic[CMDSZ]; - char svalue[128]; + char svalue[INPUT_BUFFER_SIZE]; char *start; char *token; @@ -2045,7 +2089,7 @@ void stateloop() if (mqtt_cmnd_publish) { mqtt_cmnd_publish--; // Clean up } - + if (latching_relay_pulse) { latching_relay_pulse--; if (!latching_relay_pulse) { @@ -2274,9 +2318,25 @@ void stateloop() } } + if (blogdelay) { + blogdelay--; + } + if ((blogptr != blogidx) && !blogdelay && !blogmutex) { + blogmutex = 1; + do_cmnd((char*)Backlog[blogptr].c_str()); + blogmutex = 0; + blogptr++; +/* + if (blogptr >= MAX_BACKLOG) { + blogptr = 0; + } +*/ + blogptr &= 0xF; + } + switch (state) { case (STATES/10)*2: - if (otaflag) { + if (otaflag && (blogptr == blogidx)) { otaflag--; if (2 == otaflag) { otaretry = OTA_ATTEMPTS; @@ -2304,7 +2364,7 @@ void stateloop() if (90 == otaflag) { // Allow MQTT to reconnect otaflag = 0; if (otaok) { - setModuleFlashMode(1); // QIO - ESP8266, DOUT - ESP8285 (Sonoff 4CH, Touch and BN-SZ01) + setFlashMode(1, 3); // DOUT for both ESP8266 and ESP8285 snprintf_P(svalue, sizeof(svalue), PSTR("Successful. Restarting")); } else { snprintf_P(svalue, sizeof(svalue), PSTR("Failed %s"), ESPhttpUpdate.getLastErrorString().c_str()); @@ -2318,7 +2378,7 @@ void stateloop() if (rtc_midnight_now()) { counter_savestate(); } - if (savedatacounter) { + if (savedatacounter && (blogptr == blogidx)) { savedatacounter--; if (savedatacounter <= 0) { if (sysCfg.flag.savestate) { @@ -2336,7 +2396,7 @@ void stateloop() savedatacounter = sysCfg.savedata; } } - if (restartflag) { + if (restartflag && (blogptr == blogidx)) { if (211 == restartflag) { CFG_Default(); restartflag = 2; @@ -2459,7 +2519,7 @@ void GPIO_init() } memcpy_P(&def_module, &modules[sysCfg.module], sizeof(def_module)); - sysCfg.my_module.flag = def_module.flag; +// sysCfg.my_module.flag = def_module.flag; strlcpy(my_module.name, def_module.name, sizeof(my_module.name)); for (byte i = 0; i < MAX_GPIO_PIN; i++) { if (sysCfg.my_module.gp.io[i] > GPIO_NONE) { diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index 4a318c451..7508b1dff 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -158,14 +158,14 @@ typedef struct MYIO { typedef struct MYTMPLT { char name[14]; - uint8_t flag; // bit 0 = flashmode (0 = esp8266, 1 = esp8285) + uint8_t flag; // not used myio gp; } mytmplt; // Default module settings const mytmplt modules[MAXMODULE] PROGMEM = { { "Sonoff Basic", // Sonoff Basic (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor 0, // GPIO02 @@ -174,7 +174,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, // GPIO05 0, // GPIO06 (SD_CLK Flash) 0, // GPIO07 (SD_DATA0 Flash QIO/DIO/DOUT) - 0, // GPIO08 (SD_DATA1 Flash QIO/DIO) + 0, // GPIO08 (SD_DATA1 Flash QIO/DIO/DOUT) 0, // GPIO09 (SD_DATA2 Flash QIO) 0, // GPIO10 (SD_DATA3 Flash QIO) 0, // GPIO11 (SD_CMD Flash) @@ -186,7 +186,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0 // ADC0 Analog input }, { "Sonoff RF", // Sonoff RF (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor 0, @@ -200,7 +200,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0 }, { "Sonoff SV", // Sonoff SV (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor 0, @@ -215,7 +215,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { GPIO_ADC0 // ADC0 Analog input }, { "Sonoff TH", // Sonoff TH10/16 (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor 0, @@ -229,7 +229,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0 }, { "Sonoff Dual", // Sonoff Dual (ESP8266) - 0, // esp8266 + 0, // not used 0, GPIO_TXD, // GPIO01 Relay control 0, @@ -242,7 +242,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0, 0 }, { "Sonoff Pow", // Sonoff Pow (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button 0, 0, 0, 0, GPIO_HLW_SEL, // GPIO05 HLW8012 Sel output @@ -254,7 +254,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0 }, { "Sonoff 4CH", // Sonoff 4CH (ESP8285) - 1, // esp8285 + 0, // not used GPIO_KEY1, // GPIO00 Button 1 GPIO_USER, // GPIO01 Serial RXD and Optional sensor GPIO_USER, // GPIO02 Optional sensor @@ -264,7 +264,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0, // Flash connection GPIO_KEY2, // GPIO09 Button 2 GPIO_KEY3, // GPIO10 Button 3 - 0, + 0, // Flash connection GPIO_REL1, // GPIO12 Red Led and Relay 1 (0 = Off, 1 = On) GPIO_LED1_INV, // GPIO13 Blue Led (0 = On, 1 = Off) GPIO_KEY4, // GPIO14 Button 4 @@ -272,7 +272,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0 }, { "S20 Socket", // S20 Smart Socket (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor 0, @@ -284,7 +284,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0, 0 }, { "Slampher", // Slampher (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor 0, @@ -296,20 +296,21 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0, 0 }, { "Sonoff Touch", // Sonoff Touch (ESP8285) - 1, // esp8285 + 0, // not used GPIO_KEY1, // GPIO00 Button GPIO_USER, // GPIO01 Serial RXD and Optional sensor 0, GPIO_USER, // GPIO03 Serial TXD and Optional sensor 0, 0, 0, 0, 0, // Flash connection - 0, 0, 0, + 0, 0, + 0, // Flash connection GPIO_REL1, // GPIO12 Red Led and Relay (0 = Off, 1 = On) GPIO_LED1_INV, // GPIO13 Blue Led (0 = On, 1 = Off) 0, 0, 0, 0 }, { "Sonoff LED", // Sonoff LED (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button 0, 0, 0, GPIO_USER, // GPIO04 Optional sensor (PWM3 Green) @@ -321,8 +322,8 @@ const mytmplt modules[MAXMODULE] PROGMEM = { GPIO_USER, // GPIO15 Optional sensor (PWM4 Blue) 0, 0 }, - { "1 Channel", // 1 Channel Inching/Latching Relay using (PSA-B01 - ESP8266) - 0, // esp8266 + { "1 Channel", // 1 Channel Inching/Latching Relay using (PSA-B01 - ESP8266 and PSF-B01 - ESP8285) + 0, // not used GPIO_KEY1, // GPIO00 Button 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Flash connection @@ -331,7 +332,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0, 0 }, { "4 Channel", // 4 Channel Inching/Latching Relays (ESP8266) - 0, // esp8266 + 0, // not used 0, GPIO_TXD, // GPIO01 Relay control 0, @@ -343,7 +344,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0, 0 }, { "Motor C/AC", // Motor Clockwise / Anti clockwise (PSA-B01 - ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Flash connection @@ -352,7 +353,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0, 0, 0 }, { "ElectroDragon", // ElectroDragon IoT Relay Board (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY2, // GPIO00 Button 2 GPIO_USER, // GPIO01 Serial RXD and Optional sensor GPIO_KEY1, // GPIO02 Button 1 @@ -369,7 +370,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { }, { "EXS Relay", // Latching relay https://ex-store.de/ESP8266-WiFi-Relay-V31 (ESP8266) // Module Pin 1 VCC 3V3, Module Pin 6 GND - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Module Pin 8 - Button (firmware flash) GPIO_USER, // GPIO01 Module Pin 2 = UART0_TXD GPIO_USER, // GPIO02 Module Pin 7 @@ -385,7 +386,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0 }, { "WiOn", // Indoor Tap https://www.amazon.com/gp/product/B00ZYLUBJU/ref=s9_acsd_al_bw_c_x_3_w (ESP8266) - 0, // esp8266 + 0, // not used GPIO_USER, // GPIO00 Optional sensor (pm clock) 0, GPIO_LED1, // GPIO02 Green Led (1 = On, 0 = Off) @@ -398,7 +399,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0 }, { "WeMos D1 mini", // WeMos and NodeMCU hardware (ESP8266) - 0, // esp8266 + 0, // not used GPIO_USER, // GPIO00 D3 Wemos Button Shield GPIO_USER, // GPIO01 TX Serial RXD GPIO_USER, // GPIO02 D4 Wemos DHT Shield @@ -414,7 +415,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { GPIO_ADC0 // ADC0 A0 Analog input }, { "Sonoff Dev", // Sonoff Dev (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 E-FW Button GPIO_USER, // GPIO01 TX Serial RXD and Optional sensor 0, // GPIO02 @@ -430,7 +431,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { GPIO_ADC0 // ADC0 A0 Analog input }, { "H801", // Lixada H801 Wifi (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 E-FW Button GPIO_LED1, // GPIO01 Green LED GPIO_TXD, // GPIO02 RX - Pin next to TX on the PCB @@ -445,7 +446,7 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, 0 }, { "Sonoff SC", // Sonoff SC (ESP8266) - 0, // esp8266 + 0, // not used GPIO_KEY1, // GPIO00 Button GPIO_TXD, // GPIO01 RXD to ATMEGA328P GPIO_USER, // GPIO02 Optional sensor @@ -456,11 +457,12 @@ const mytmplt modules[MAXMODULE] PROGMEM = { GPIO_LED1_INV, // GPIO13 Green Led (0 = On, 1 = Off) 0, 0, 0, 0 }, - { "Sonoff BN-SZ", // Sonoff BN-SZ01 LED (ESP8285) - 1, // esp8285 + { "Sonoff BN-SZ", // Sonoff BN-SZ01 Ceiling led (ESP8285) + 0, // not used 0, 0, 0, 0, 0, 0, 0, 0, 0, // Flash connection - 0, 0, 0, + 0, 0, + 0, // Flash connection GPIO_PWM1, // GPIO12 Light GPIO_LED1_INV, // GPIO13 Red Led (0 = On, 1 = Off) 0, 0, diff --git a/sonoff/support.ino b/sonoff/support.ino index 3b5894a7b..e7e98783e 100644 --- a/sonoff/support.ino +++ b/sonoff/support.ino @@ -753,7 +753,7 @@ uint8_t midnightnow = 0; String getBuildDateTime() { - // "2017-03-07T11:08:02" + // "2017-03-07T11:08:02" - ISO8601:2004 char bdt[21]; char *str; char *p; @@ -784,7 +784,7 @@ String getBuildDateTime() String getDateTime() { - // "2017-03-07T11:08:02" + // "2017-03-07T11:08:02" - ISO8601:2004 char dt[21]; snprintf_P(dt, sizeof(dt), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), @@ -792,6 +792,20 @@ String getDateTime() return String(dt); } +String getUTCDateTime() +{ + // "2017-03-07T11:08:02" - ISO8601:2004 + char dt[21]; + + TIME_T tmpTime; + breakTime(utctime, tmpTime); + tmpTime.Year += 1970; + + snprintf_P(dt, sizeof(dt), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), + tmpTime.Year, tmpTime.Month, tmpTime.Day, tmpTime.Hour, tmpTime.Minute, tmpTime.Second); + return String(dt); +} + void breakTime(uint32_t timeInput, TIME_T &tm) { // break the given timeInput into time components diff --git a/sonoff/user_config.h b/sonoff/user_config.h index 093b1b491..63c432983 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -165,7 +165,7 @@ #define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+3k code, 0.3k mem) // #define USE_IR_HVAC // Support for HVAC system using IR (+2k code) -#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+8k code, +1k mem) - Disable by // +#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+11k code, +1k mem) - Disable by // #define USE_WS2812_CTYPE 1 // WS2812 Color type (0 - RGB, 1 - GRB) // #define USE_WS2812_DMA // DMA supports only GPIO03 (= Serial RXD) (+1k mem) // When USE_WS2812_DMA is enabled expect Exceptions on Pow diff --git a/sonoff/webserver.ino b/sonoff/webserver.ino index c30cae337..e120a577b 100644 --- a/sonoff/webserver.ino +++ b/sonoff/webserver.ino @@ -22,7 +22,7 @@ * Web server and WiFi Manager * * Enables configuration and reconfiguration of WiFi credentials using a Captive Portal - * Source by AlexT (https://github.com/tzapu) + * Based on source by AlexT (https://github.com/tzapu) \*********************************************************************************************/ #define STR_HELPER(x) #x @@ -235,7 +235,7 @@ const char HTTP_FORM_RST_UPG[] PROGMEM = "" ""; const char HTTP_FORM_CMND[] PROGMEM = - "


" + "


" "
" "
" // "
" @@ -261,8 +261,11 @@ const char HTTP_END[] PROGMEM = "" ""; -const char HDR_CCNTL[] PROGMEM = "Cache-Control"; -const char HDR_REVAL[] PROGMEM = "no-cache, no-store, must-revalidate"; +const char HDR_CTYPE_PLAIN[] PROGMEM = "text/plain"; +const char HDR_CTYPE_HTML[] PROGMEM = "text/html"; +const char HDR_CTYPE_XML[] PROGMEM = "text/xml"; +const char HDR_CTYPE_JSON[] PROGMEM = "application/json"; +const char HDR_CTYPE_STREAM[] PROGMEM = "application/octet-stream"; #define DNS_PORT 53 enum http_t {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER}; @@ -376,6 +379,13 @@ void pollDnsWeb() } } +void setHeader() +{ + webServer->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); + webServer->sendHeader(F("Pragma"), F("no-cache")); + webServer->sendHeader(F("Expires"), F("-1")); +} + void showPage(String &page) { if((HTTP_ADMIN == _httpflag) && (sysCfg.web_password[0] != 0) && !webServer->authenticate(WEB_USERNAME, sysCfg.web_password)) { @@ -390,16 +400,13 @@ void showPage(String &page) } } page += FPSTR(HTTP_END); - - webServer->sendHeader(FPSTR(HDR_CCNTL), FPSTR(HDR_REVAL)); - webServer->sendHeader(F("Pragma"), F("no-cache")); - webServer->sendHeader(F("Expires"), F("-1")); - webServer->send(200, F("text/html"), page); + setHeader(); + webServer->send(200, FPSTR(HDR_CTYPE_HTML), page); } void handleRoot() { - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle root")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Root")); if (captivePortal()) { // If captive portal redirect instead of displaying the page. return; @@ -515,7 +522,7 @@ void handleAjax2() page += line; } */ - webServer->send(200, F("text/html"), page); + webServer->send(200, FPSTR(HDR_CTYPE_HTML), page); } boolean httpUser() @@ -532,7 +539,7 @@ void handleConfig() if (httpUser()) { return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle config")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Config")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Configuration")); @@ -594,7 +601,7 @@ void handleModule() } char stemp[20], line[128]; - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Module config")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Module config")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Config module")); @@ -658,7 +665,7 @@ void handleWifi(boolean scan) } char log[LOGSZ]; - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Wifi config")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Wifi config")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Configure Wifi")); @@ -757,7 +764,7 @@ void handleMqtt() if (httpUser()) { return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle MQTT config")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: MQTT config")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Configure MQTT")); @@ -782,7 +789,7 @@ void handleLog() if (httpUser()) { return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Log config")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Log config")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Config logging")); @@ -830,7 +837,7 @@ void handleOther() if (httpUser()) { return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle other config")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Other config")); char stemp[40]; String page = FPSTR(HTTP_HEAD); @@ -871,7 +878,7 @@ void handleDownload() if (httpUser()) { return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle download config")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Download config")); uint8_t buffer[sizeof(sysCfg)]; @@ -882,7 +889,7 @@ void handleDownload() snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), sysCfg.friendlyname[0], Version); webServer->sendHeader(F("Content-Disposition"), attachment); - webServer->send(200, F("application/octet-stream"), ""); + webServer->send(200, FPSTR(HDR_CTYPE_STREAM), ""); memcpy(buffer, &sysCfg, sizeof(sysCfg)); buffer[0] = CONFIG_FILE_SIGN; buffer[1] = (!CONFIG_FILE_XOR)?0:1; @@ -995,7 +1002,6 @@ void handleSave() gpios += F(", GPIO"); gpios += String(i); gpios += F(" "); gpios += String(sysCfg.my_module.gp.io[i]); } } -// setModuleFlashMode(0); // Fails on esp8285 based devices snprintf_P(stemp, sizeof(stemp), modules[sysCfg.module].name); snprintf_P(log, sizeof(log), PSTR("HTTP: %s Module%s"), stemp, gpios.c_str()); addLog(LOG_LEVEL_INFO, log); @@ -1049,7 +1055,7 @@ void handleRestore() if (httpUser()) { return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle restore")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Restore")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Restore Configuration")); @@ -1068,7 +1074,7 @@ void handleUpgrade() if (httpUser()) { return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle upgrade")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Upgrade")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Firmware upgrade")); @@ -1218,11 +1224,7 @@ void handleUploadLoop() _uploaderror = 4; return; } -// if ((SONOFF_TOUCH == sysCfg.module) || (SONOFF_4CH == sysCfg.module)) { - if (sysCfg.my_module.flag &1) { - upload.buf[2] = 3; // DOUT - ESP8285 - addLog_P(LOG_LEVEL_DEBUG, PSTR("FLSH: Set Flash Mode to 3")); - } + upload.buf[2] = 3; // Force DOUT - ESP8285 } } if (_uploadfiletype) { // config @@ -1282,9 +1284,9 @@ void handleCmnd() if (httpUser()) { return; } - char svalue[128]; // was MESSZ + char svalue[INPUT_BUFFER_SIZE]; // big to serve Backlog - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle cmnd")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Command")); uint8_t valid = 1; if (sysCfg.web_password[0] != 0) { @@ -1332,7 +1334,7 @@ void handleCmnd() } else { message = F("Need user=&password=\n"); } - webServer->send(200, F("text/plain"), message); + webServer->send(200, FPSTR(HDR_CTYPE_PLAIN), message); } void handleConsole() @@ -1341,7 +1343,7 @@ void handleConsole() return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle console")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Console")); String page = FPSTR(HTTP_HEAD); page.replace(F("{v}"), F("Console")); @@ -1358,7 +1360,7 @@ void handleAjax() return; } char log[LOGSZ]; - char svalue[128]; // was MESSZ + char svalue[INPUT_BUFFER_SIZE]; // big to serve Backlog byte cflg = 1; byte counter = 99; @@ -1406,7 +1408,7 @@ void handleAjax() } while (counter != logidx); } message += F(""); - webServer->send(200, F("text/xml"), message); + webServer->send(200, FPSTR(HDR_CTYPE_XML), message); } void handleInfo() @@ -1414,7 +1416,7 @@ void handleInfo() if (httpUser()) { return; } - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle info")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Info")); char stopic[TOPSZ]; @@ -1440,8 +1442,8 @@ void handleInfo() page += F(""); page += sysCfg.friendlyname[i]; page += F(""); } page += F(" "); -// page += F("SSId (RSSI)"); page += (sysCfg.sta_active)? sysCfg.sta_ssid2 : sysCfg.sta_ssid1; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)"); - page += F("AP"); page += String(sysCfg.sta_active +1); page += F(" SSId (RSSI)"); page += sysCfg.sta_ssid[sysCfg.sta_active]; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)"); + page += F("AP"); page += String(sysCfg.sta_active +1); + page += F(" SSId (RSSI)"); page += sysCfg.sta_ssid[sysCfg.sta_active]; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)"); page += F("Hostname"); page += Hostname; page += F(""); if (static_cast(WiFi.localIP()) != 0) { page += F("IP address"); page += WiFi.localIP().toString(); page += F(""); @@ -1555,18 +1557,15 @@ void handleNotFound() String message = F("File Not Found\n\nURI: "); message += webServer->uri(); message += F("\nMethod: "); - message += ( webServer->method() == HTTP_GET ) ? F("GET") : F("POST"); + message += (webServer->method() == HTTP_GET) ? F("GET") : F("POST"); message += F("\nArguments: "); message += webServer->args(); message += F("\n"); for ( uint8_t i = 0; i < webServer->args(); i++ ) { - message += " " + webServer->argName ( i ) + ": " + webServer->arg ( i ) + "\n"; + message += " " + webServer->argName(i) + ": " + webServer->arg(i) + "\n"; } - - webServer->sendHeader(FPSTR(HDR_CCNTL), FPSTR(HDR_REVAL)); - webServer->sendHeader(F("Pragma"), F("no-cache")); - webServer->sendHeader(F("Expires"), F("-1")); - webServer->send(404, F("text/plain"), message); + setHeader(); + webServer->send(404, FPSTR(HDR_CTYPE_PLAIN), message); } } @@ -1577,7 +1576,7 @@ boolean captivePortal() addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Request redirected to captive portal")); webServer->sendHeader(F("Location"), String("http://") + webServer->client().localIP().toString(), true); - webServer->send(302, F("text/plain"), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. + webServer->send(302, FPSTR(HDR_CTYPE_PLAIN), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. webServer->client().stop(); // Stop is needed because we sent no content length return true; } diff --git a/sonoff/xdrv_domoticz.ino b/sonoff/xdrv_domoticz.ino index a4e6ca841..d496b2f92 100644 --- a/sonoff/xdrv_domoticz.ino +++ b/sonoff/xdrv_domoticz.ino @@ -51,11 +51,10 @@ void mqtt_publishDomoticzPowerState(byte device) { char svalue[64]; // was MESSZ - if (sysCfg.domoticz_relay_idx[device -1]) { + if (sysCfg.flag.mqtt_enabled && sysCfg.domoticz_relay_idx[device -1]) { if ((device < 1) || (device > Maxdevice)) { device = 1; } - if (sfl_flg) { snprintf_P(svalue, sizeof(svalue), PSTR("{\"idx\":%d,\"nvalue\":2,\"svalue\":\"%d\"}"), sysCfg.domoticz_relay_idx[device -1], sysCfg.led_dimmer[device -1]); @@ -74,10 +73,10 @@ void mqtt_publishDomoticzPowerState(byte device) void domoticz_updatePowerState(byte device) { - if (domoticz_update_flag) { + if (domoticz_update_flag) { mqtt_publishDomoticzPowerState(device); - } - domoticz_update_flag = 1; + } + domoticz_update_flag = 1; } void domoticz_mqttUpdate() diff --git a/sonoff/xdrv_snfled.ino b/sonoff/xdrv_snfled.ino index 17f5392e9..a7a86d017 100644 --- a/sonoff/xdrv_snfled.ino +++ b/sonoff/xdrv_snfled.ino @@ -49,6 +49,8 @@ uint8_t sl_wakeupActive = 0; uint8_t sl_wakeupDimmer = 0; uint16_t sl_wakeupCntr = 0; +/********************************************************************************************/ + void sl_setDim(uint8_t myDimmer) { if ((1 == sfl_flg) && (100 == myDimmer)) { @@ -61,11 +63,9 @@ void sl_setDim(uint8_t myDimmer) sl_dcolor[1] = (uint8_t)fmyWrm; } -/********************************************************************************************/ - void sl_init(void) { - sysCfg.pwmvalue[0] = 0; // We use dimmer / led_color + sysCfg.pwmvalue[0] = 0; // We use dimmer / led_color if (2 == sfl_flg) { sysCfg.pwmvalue[1] = 0; // We use led_color } @@ -74,6 +74,51 @@ void sl_init(void) sl_wakeupActive = 0; } +void sl_setColor(char* colstr) +{ + uint8_t my_color[2]; + char *p; + + uint16_t temp = strtol(colstr, &p, 16); + my_color[1] = temp & 0xFF; // Warm + temp >>= 8; + my_color[0] = temp & 0xFF; // Cold + if (temp < my_color[1]) { + temp = my_color[1]; + } + float mDim = (float)temp / 2.55; + sysCfg.led_dimmer[0] = (uint8_t)mDim; + float newDim = 100 / mDim; + float fmyCold = (float)my_color[0] * newDim; + float fmyWarm = (float)my_color[1] * newDim; + sysCfg.led_color[0] = (uint8_t)fmyCold; + sysCfg.led_color[1] = (uint8_t)fmyWarm; +} + +void sl_prepPower(char *svalue, uint16_t ssvalue) +{ +// do_cmnd_power(index, (sysCfg.led_dimmer[0]>0)); + if (sysCfg.led_dimmer[0] && !(power&1)) { + do_cmnd_power(1, 7); // No publishPowerState + } + else if (!sysCfg.led_dimmer[0] && (power&1)) { + do_cmnd_power(1, 6); // No publishPowerState + } +#ifdef USE_DOMOTICZ + mqtt_publishDomoticzPowerState(1); +#endif // USE_DOMOTICZ + sl_setDim(sysCfg.led_dimmer[0]); + if (2 == sfl_flg) { + uint16_t color = (uint16_t)sl_dcolor[0] << 8; + color += (uint16_t)sl_dcolor[1]; + snprintf_P(svalue, ssvalue, PSTR("{\"POWER\":\"%s\", \"Dimmer\":%d, \"Color\":\"%04X\"}"), + getStateText(power &1), sysCfg.led_dimmer[0], color); + } else { + snprintf_P(svalue, ssvalue, PSTR("{\"POWER\":\"%s\", \"Dimmer\":%d}"), + getStateText(power &1), sysCfg.led_dimmer[0]); + } +} + void sl_setPower(uint8_t power) { sl_power = power &1; @@ -153,6 +198,34 @@ void sl_animate() } } +/*********************************************************************************************\ + * Hue support +\*********************************************************************************************/ + +void sl_replaceHSB(String *response) +{ + response->replace("{h}", "0"); + response->replace("{s}", "0"); + response->replace("{b}", String((uint8_t)(2.54f * (float)sysCfg.led_dimmer[0]))); +} + +void sl_getHSB(float *hue, float *sat, float *bri) +{ + *hue = 0; + *sat = 0; + *bri = (2.54f * (float)sysCfg.led_dimmer[0]); +} + +void sl_setHSB(float hue, float sat, float bri) +{ + char svalue[MESSZ]; + + uint8_t tmp = (uint8_t)(bri * 100); + sysCfg.led_dimmer[0] = tmp; + sl_prepPower(svalue, sizeof(svalue)); + mqtt_publish_topic_P(5, "DIMMER", svalue); +} + /*********************************************************************************************\ * Commands \*********************************************************************************************/ @@ -166,20 +239,7 @@ boolean sl_command(char *type, uint16_t index, char *dataBufUc, uint16_t data_le uint8_t my_color[2]; char *p; if (4 == data_len) { - uint16_t temp = strtol(dataBufUc, &p, 16); - my_color[1] = temp & 0xFF; // Warm - temp >>= 8; - my_color[0] = temp & 0xFF; // Cold - if (temp < my_color[1]) { - temp = my_color[1]; - } - float mDim = (float)temp / 2.55; - sysCfg.led_dimmer[0] = (uint8_t)mDim; - float newDim = 100 / mDim; - float fmyCold = (float)my_color[0] * newDim; - float fmyWarm = (float)my_color[1] * newDim; - sysCfg.led_color[0] = (uint8_t)fmyCold; - sysCfg.led_color[1] = (uint8_t)fmyWarm; + sl_setColor(dataBufUc); coldim = true; } else { sl_setDim(sysCfg.led_dimmer[0]); @@ -245,26 +305,7 @@ boolean sl_command(char *type, uint16_t index, char *dataBufUc, uint16_t data_le serviced = false; // Unknown command } if (coldim) { -// do_cmnd_power(index, (sysCfg.led_dimmer[0]>0)); - if (sysCfg.led_dimmer[0] && !(power&1)) { - do_cmnd_power(1, 1); - } - else if (!sysCfg.led_dimmer[0] && (power&1)) { - do_cmnd_power(1, 0); - } -#ifdef USE_DOMOTICZ - mqtt_publishDomoticzPowerState(1); -#endif // USE_DOMOTICZ - sl_setDim(sysCfg.led_dimmer[0]); - if (2 == sfl_flg) { - uint16_t color = (uint16_t)sl_dcolor[0] << 8; - color += (uint16_t)sl_dcolor[1]; - snprintf_P(svalue, ssvalue, PSTR("{\"POWER\":\"%s\", \"Dimmer\":%d, \"Color\":\"%04X\"}"), - getStateText(power &1), sysCfg.led_dimmer[0], color); - } else { - snprintf_P(svalue, ssvalue, PSTR("{\"POWER\":\"%s\", \"Dimmer\":%d}"), - getStateText(power &1), sysCfg.led_dimmer[0]); - } + sl_prepPower(svalue, ssvalue); } return serviced; } diff --git a/sonoff/xdrv_wemohue.ino b/sonoff/xdrv_wemohue.ino index d4cffd8d0..08bcba878 100644 --- a/sonoff/xdrv_wemohue.ino +++ b/sonoff/xdrv_wemohue.ino @@ -17,6 +17,10 @@ along with this program. If not, see . */ +/*********************************************************************************************\ + * Belkin WeMo and Philips Hue bridge emulation +\*********************************************************************************************/ + #ifdef USE_EMULATION #define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message @@ -30,6 +34,7 @@ uint32_t portMulticast = 1900; // Multicast address and port /*********************************************************************************************\ * WeMo UPNP support routines \*********************************************************************************************/ + const char WEMO_MSEARCH[] PROGMEM = "HTTP/1.1 200 OK\r\n" "CACHE-CONTROL: max-age=86400\r\n" @@ -83,44 +88,53 @@ void wemo_respondToMSearch() /*********************************************************************************************\ * Hue Bridge UPNP support routines * Need to send 3 response packets with varying ST and USN + * + * Using Espressif Inc Mac Address of 5C:CF:7F:00:00:00 + * Philips Lighting is 00:17:88:00:00:00 \*********************************************************************************************/ + const char HUE_RESPONSE[] PROGMEM = - "HTTP/1.0 200 OK\r\n" + "HTTP/1.1 200 OK\r\n" "HOST: 239.255.255.250:1900\r\n" "CACHE-CONTROL: max-age=100\r\n" "EXT:\r\n" "LOCATION: http://{r1}:80/description.xml\r\n" - "SERVER: FreeRTOS/7.4.2 UPnP/1.0 IpBridge/1.15.0\r\n" + "SERVER: FreeRTOS/7.4.2 UPnP/1.0 IpBridge/1.16.0\r\n" "hue-bridgeid: {r2}\r\n"; const char HUE_ST1[] PROGMEM = - "ST: upnp:rootdevice\r\n"; -const char HUE_USN1[] PROGMEM = + "ST: upnp:rootdevice\r\n" "USN: uuid:{r3}::upnp:rootdevice\r\n" "\r\n"; - const char HUE_ST2[] PROGMEM = - "ST: uuid:{r3}\r\n"; -const char HUE_USN2[] PROGMEM = + "ST: uuid:{r3}\r\n" "USN: uuid:{r3}\r\n" "\r\n"; - const char HUE_ST3[] PROGMEM = - "ST: urn:schemas-upnp-org:device:basic:1\r\n"; + "ST: urn:schemas-upnp-org:device:Basic:1\r\n" + "USN: uuid:{r3}\r\n" + "\r\n"; String hue_bridgeid() { - char bridgeid[16]; - - snprintf_P(bridgeid, sizeof(bridgeid), PSTR("5CCF7FFFFE%03X"), ESP.getChipId()); - return String(bridgeid); + String temp = WiFi.macAddress(); + temp.replace(":", ""); + String bridgeid = temp.substring(0, 6) + "FFFE" + temp.substring(6); + return bridgeid; // 5CCF7FFFFE139F3D +} + +String hue_serial() +{ + String serial = WiFi.macAddress(); + serial.replace(":", ""); + serial.toLowerCase(); + return serial; // 5ccf7f139f3d } String hue_UUID() { - char serial[36]; - - snprintf_P(serial, sizeof(serial), PSTR("f6543a06-da50-11ba-8d8f-5ccf7f%03x"), ESP.getChipId()); - return String(serial); + String uuid = F("f6543a06-da50-11ba-8d8f-"); + uuid += hue_serial(); + return uuid; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d } void hue_respondToMSearch() @@ -129,40 +143,35 @@ void hue_respondToMSearch() char log[LOGSZ]; if (portUDP.beginPacket(portUDP.remoteIP(), portUDP.remotePort())) { - String response = FPSTR(HUE_RESPONSE); - String response_st = FPSTR(HUE_ST1); - String response_usn = FPSTR(HUE_USN1); - response += response_st + response_usn; - response.replace("{r1}", WiFi.localIP().toString()); - response.replace("{r2}", hue_bridgeid()); - response.replace("{r3}", hue_UUID()); - portUDP.write(response.c_str()); - portUDP.endPacket(); -// addLog(LOG_LEVEL_DEBUG_MORE, response.c_str()); - - response = FPSTR(HUE_RESPONSE); - response_st = FPSTR(HUE_ST2); - response_usn = FPSTR(HUE_USN2); - response += response_st + response_usn; - response.replace("{r1}", WiFi.localIP().toString()); - response.replace("{r2}", hue_bridgeid()); - response.replace("{r3}", hue_UUID()); - portUDP.write(response.c_str()); - portUDP.endPacket(); -// addLog(LOG_LEVEL_DEBUG_MORE, response.c_str()); - - response = FPSTR(HUE_RESPONSE); - response_st = FPSTR(HUE_ST3); - response += response_st + response_usn; - response.replace("{r1}", WiFi.localIP().toString()); - response.replace("{r2}", hue_bridgeid()); - response.replace("{r3}", hue_UUID()); - portUDP.write(response.c_str()); - portUDP.endPacket(); - - snprintf_P(message, sizeof(message), PSTR("3 response packets sent")); -// addLog(LOG_LEVEL_DEBUG_MORE, response.c_str()); + String response1 = FPSTR(HUE_RESPONSE); + response1.replace("{r1}", WiFi.localIP().toString()); + response1.replace("{r2}", hue_bridgeid()); + String response = response1; + response += FPSTR(HUE_ST1); + response.replace("{r3}", hue_UUID()); + portUDP.write(response.c_str()); + portUDP.endPacket(); + +//addLog(LOG_LEVEL_DEBUG_MORE, response.c_str()); + + response = response1; + response += FPSTR(HUE_ST2); + response.replace("{r3}", hue_UUID()); + portUDP.write(response.c_str()); + portUDP.endPacket(); + +//addLog(LOG_LEVEL_DEBUG_MORE, response.c_str()); + + response = response1; + response += FPSTR(HUE_ST3); + response.replace("{r3}", hue_UUID()); + portUDP.write(response.c_str()); + portUDP.endPacket(); + +//addLog(LOG_LEVEL_DEBUG_MORE, response.c_str()); + + snprintf_P(message, sizeof(message), PSTR("3 response packets sent")); } else { snprintf_P(message, sizeof(message), PSTR("Failed to send response")); } @@ -171,7 +180,9 @@ void hue_respondToMSearch() addLog(LOG_LEVEL_DEBUG, log); } -/********************************************************************************************/ +/*********************************************************************************************\ + * Belkin WeMo and Philips Hue bridge UDP multicast support +\*********************************************************************************************/ boolean UDP_Disconnect() { @@ -211,10 +222,20 @@ void pollUDP() // addLog_P(LOG_LEVEL_DEBUG_MORE, packetBuffer); if (request.indexOf("M-SEARCH") >= 0) { - if ((EMUL_WEMO == sysCfg.flag.emulation) &&(request.indexOf("urn:Belkin:device:**") > 0)) { + request.toLowerCase(); + request.replace(" ", ""); + +// addLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet received")); +// addLog_P(LOG_LEVEL_DEBUG_MORE, request.c_str()); + + if ((EMUL_WEMO == sysCfg.flag.emulation) && (request.indexOf(F("urn:belkin:device:**")) > 0)) { wemo_respondToMSearch(); } - else if ((EMUL_HUE == sysCfg.flag.emulation) && ((request.indexOf("ST: urn:schemas-upnp-org:device:basic:1") > 0) || (request.indexOf("ST: upnp:rootdevice") > 0) || (request.indexOf("ST: ssdp:all") > 0))) { + else if ((EMUL_HUE == sysCfg.flag.emulation) && + ((request.indexOf(F("st:urn:schemas-upnp-org:device:basic:1")) > 0) || + (request.indexOf(F("st:upnp:rootdevice")) > 0) || + (request.indexOf(F("st:ssdpsearch:all")) > 0) || + (request.indexOf(F("st:ssdp:all")) > 0))) { hue_respondToMSearch(); } } @@ -224,8 +245,9 @@ void pollUDP() #ifdef USE_WEBSERVER /*********************************************************************************************\ - * Web server additions + * Wemo web server additions \*********************************************************************************************/ + const char WEMO_EVENTSERVICE_XML[] PROGMEM = "" "" @@ -278,45 +300,93 @@ const char WEMO_SETUP_XML[] PROGMEM = "" "\r\n" "\r\n"; + +/********************************************************************************************/ + +void handleUPnPevent() +{ + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: WeMo basic event")); + + String request = webServer->arg(0); + if (request.indexOf(F("State>1 0) { +// do_cmnd_power(1, 1); + do_cmnd_power(Maxdevice, 1); + } + if (request.indexOf(F("State>0 0) { +// do_cmnd_power(1, 0); + do_cmnd_power(Maxdevice, 0); + } + webServer->send(200, FPSTR(HDR_CTYPE_PLAIN), ""); +} + +void handleUPnPservice() +{ + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: WeMo event service")); + + webServer->send(200, FPSTR(HDR_CTYPE_PLAIN), FPSTR(WEMO_EVENTSERVICE_XML)); +} + +void handleUPnPsetupWemo() +{ + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: WeMo setup")); + + String setup_xml = FPSTR(WEMO_SETUP_XML); + setup_xml.replace("{x1}", sysCfg.friendlyname[0]); + setup_xml.replace("{x2}", wemo_UUID()); + setup_xml.replace("{x3}", wemo_serial()); + webServer->send(200, FPSTR(HDR_CTYPE_XML), setup_xml); +} + +/*********************************************************************************************\ + * Hue web server additions +\*********************************************************************************************/ + const char HUE_DESCRIPTION_XML[] PROGMEM = "" "" "" - "1" - "0" + "1" + "0" "" - "http://{x1}/" +// "http://{x1}/" + "http://{x1}:80/" "" - "urn:schemas-upnp-org:device:Basic:1" - "Amazon-Echo-HA-Bridge ({x1})" - "Royal Philips Electronics" - "Philips hue bridge 2012" - "929000226503" - "uuid:{x2}" + "urn:schemas-upnp-org:device:Basic:1" + "Amazon-Echo-HA-Bridge ({x1})" +// "Philips hue ({x1})" + "Royal Philips Electronics" + "Philips hue Personal Wireless Lighting" + "Philips hue bridge 2012" + "929000226503" + "{x3}" + "uuid:{x2}" "" "\r\n" "\r\n"; const char HUE_LIGHT_STATUS_JSON[] PROGMEM = - "{\"state\":" - "{\"on\":{state}," - "\"bri\":{b}," - "\"hue\":{h}," - "\"sat\":{s}," - "\"ct\":500," - "\"xy\":[0.5, 0.5]," - "\"alert\":\"none\"," - "\"effect\":\"none\"," - "\"colormode\":\"hs\"," - "\"reachable\":true" - "}," + "\"on\":{state}," + "\"bri\":{b}," + "\"hue\":{h}," + "\"sat\":{s}," + "\"xy\":[0.5, 0.5]," + "\"ct\":500," + "\"alert\":\"none\"," + "\"effect\":\"none\"," + "\"colormode\":\"hs\"," + "\"reachable\":true"; +const char HUE_LIGHTS_STATUS_JSON[] PROGMEM = "\"type\":\"Extended color light\"," "\"name\":\"{j1}\"," "\"modelid\":\"LCT007\"," "\"uniqueid\":\"{j2}\"," "\"swversion\":\"5.50.1.19085\"" "}"; -const char HUE_LIGHT_RESPONSE_JSON[] PROGMEM = - "{\"success\":{\"{api}/{id}/{cmd}\":{res}}}"; +const char HUE_GROUP0_STATUS_JSON[] PROGMEM = + "{\"name\":\"Group 0\"," + "\"lights\":[{l1}]," + "\"type\":\"LightGroup\"," + "\"action\":{"; +// "\"scene\":\"none\","; const char HUE_CONFIG_RESPONSE_JSON[] PROGMEM = "{\"name\":\"Philips hue\"," "\"mac\":\"{mac}\"," @@ -324,8 +394,9 @@ const char HUE_CONFIG_RESPONSE_JSON[] PROGMEM = "\"ipaddress\":\"{ip}\"," "\"netmask\":\"{mask}\"," "\"gateway\":\"{gw}\"," - "\"proxyaddress\":\"\"," + "\"proxyaddress\":\"none\"," "\"proxyport\":0," + "\"bridgeid\":\"{bid}\"," "\"UTC\":\"{dt}\"," "\"whitelist\":{\"{id}\":{" "\"last use date\":\"{dt}\"," @@ -337,60 +408,37 @@ const char HUE_CONFIG_RESPONSE_JSON[] PROGMEM = "\"linkbutton\":false," "\"portalservices\":false" "}"; +const char HUE_LIGHT_RESPONSE_JSON[] PROGMEM = + "{\"success\":{\"/lights/{id}/state/{cmd}\":{res}}}"; const char HUE_ERROR_JSON[] PROGMEM = "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"; -void handleUPnPevent() -{ - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo basic event")); - - String request = webServer->arg(0); - if (request.indexOf("State>1 0) { -// do_cmnd_power(1, 1); - do_cmnd_power(Maxdevice, 1); - } - if (request.indexOf("State>0 0) { -// do_cmnd_power(1, 0); - do_cmnd_power(Maxdevice, 0); - } - webServer->send(200, F("text/plain"), ""); -} - -void handleUPnPservice() -{ - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo event service")); - webServer->send_P(200, PSTR("text/plain"), WEMO_EVENTSERVICE_XML); -} - -void handleUPnPsetupWemo() -{ - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo setup")); - - String setup_xml = FPSTR(WEMO_SETUP_XML); - setup_xml.replace("{x1}", sysCfg.friendlyname[0]); - setup_xml.replace("{x2}", wemo_UUID()); - setup_xml.replace("{x3}", wemo_serial()); - webServer->send(200, F("text/xml"), setup_xml); -} - /********************************************************************************************/ String hue_deviceId(uint8_t id) { - char deviceid[16]; - - snprintf_P(deviceid, sizeof(deviceid), PSTR("5CCF7F%03X-%0d"), ESP.getChipId(), id); - return String(deviceid); + String deviceid = WiFi.macAddress() + F(":00:11-") + String(id); + deviceid.toLowerCase(); + return deviceid; // 5c:cf:7f:13:9f:3d:00:11-1 +} + +String hue_userId() +{ + char userid[7]; + + snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP.getChipId()); + return String(userid); } void handleUPnPsetupHue() { - addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle Hue Bridge setup")); + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Hue Bridge setup")); String description_xml = FPSTR(HUE_DESCRIPTION_XML); description_xml.replace("{x1}", WiFi.localIP().toString()); description_xml.replace("{x2}", hue_UUID()); - webServer->send(200, F("text/xml"), description_xml); + description_xml.replace("{x3}", hue_serial()); + webServer->send(200, FPSTR(HDR_CTYPE_XML), description_xml); } void hue_todo(String *path) @@ -399,6 +447,8 @@ void hue_todo(String *path) snprintf_P(log, sizeof(log), PSTR("HTTP: HUE API not implemented (%s)"),path->c_str()); addLog(LOG_LEVEL_DEBUG_MORE, log); + + webServer->send(200, FPSTR(HDR_CTYPE_JSON), "{}"); } void hue_config_response(String *response) @@ -408,7 +458,35 @@ void hue_config_response(String *response) response->replace("{ip}", WiFi.localIP().toString()); response->replace("{mask}", WiFi.subnetMask().toString()); response->replace("{gw}", WiFi.gatewayIP().toString()); - response->replace("{dt}", getDateTime()); + response->replace("{bid}", hue_bridgeid()); + response->replace("{dt}", getUTCDateTime()); + response->replace("{id}", hue_userId()); +} + +void hue_config(String *path) +{ + String response = ""; + hue_config_response(&response); + webServer->send(200, FPSTR(HDR_CTYPE_JSON), response); +} + +void hue_light_status(byte device, String *response) +{ + *response += FPSTR(HUE_LIGHT_STATUS_JSON); + response->replace("{state}", (power & (0x01 << (device-1))) ? "true" : "false"); + + if (sfl_flg) { + sl_replaceHSB(response); +#ifdef USE_WS2812 + } + else if (pin[GPIO_WS2812] < 99) { + ws2812_replaceHSB(response); +#endif // USE_WS2812 + } else { + response->replace("{h}", "0"); + response->replace("{s}", "0"); + response->replace("{b}", "0"); + } } void hue_global_cfg(String *path) @@ -416,51 +494,31 @@ void hue_global_cfg(String *path) String response; path->remove(0,1); // cut leading / to get - response = "{\"lights\":{\""; + response = F("{\"lights\":{\""); for (uint8_t i = 1; i <= Maxdevice; i++) { response += i; - response += "\":"; - response += FPSTR(HUE_LIGHT_STATUS_JSON); + response += F("\":{\"state\":{"); + hue_light_status(i, &response); + response += "},"; + response += FPSTR(HUE_LIGHTS_STATUS_JSON); + response.replace("{j1}", sysCfg.friendlyname[i-1]); + response.replace("{j2}", hue_deviceId(i)); if (i < Maxdevice) { response += ",\""; } - response.replace("{state}", (power & (0x01 << (i-1))) ? "true" : "false"); - response.replace("{j1}", sysCfg.friendlyname[i-1]); - response.replace("{j2}", hue_deviceId(i)); - if (pin[GPIO_WS2812] < 99) { -#ifdef USE_WS2812 - ws2812_replaceHSB(&response); -#endif // USE_WS2812 - } else { - response.replace("{h}", "0"); - response.replace("{s}", "0"); - response.replace("{b}", "0"); - } } response += F("},\"groups\":{},\"schedules\":{},\"config\":"); - hue_config_response(&response); - response.replace("{id}", *path); response += "}"; - webServer->send(200, F("application/json"), response); + webServer->send(200, FPSTR(HDR_CTYPE_JSON), response); } void hue_auth(String *path) { char response[38]; - snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%03x\"}}]"), ESP.getChipId()); - webServer->send(200, F("application/json"), response); -} - -void hue_config(String *path) -{ - String response = ""; - - path->remove(0,1); // cut leading / to get - hue_config_response(&response); - response.replace("{id}", *path); - webServer->send(200, F("application/json"), response); + snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), hue_userId().c_str()); + webServer->send(200, FPSTR(HDR_CTYPE_JSON), response); } void hue_lights(String *path) @@ -468,13 +526,14 @@ void hue_lights(String *path) /* * http://sonoff/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40} */ - String response; + String response; uint8_t device = 1; - uint16_t tmp=0; + uint16_t tmp = 0; int16_t pos = 0; - float bri = 0; - float hue = 0; - float sat = 0; + float bri = 0; + float hue = 0; + float sat = 0; + bool resp = false; bool on = false; bool change = false; char id[4]; @@ -484,26 +543,18 @@ void hue_lights(String *path) response = "{\""; for (uint8_t i = 1; i <= Maxdevice; i++) { response += i; - response += "\":"; - response += FPSTR(HUE_LIGHT_STATUS_JSON); + response += F("\":{\"state\":{"); + hue_light_status(i, &response); + response += "},"; + response += FPSTR(HUE_LIGHTS_STATUS_JSON); + response.replace("{j1}", sysCfg.friendlyname[i-1]); + response.replace("{j2}", hue_deviceId(i)); if (i < Maxdevice) { response += ",\""; } - response.replace("{state}", (power & (0x01 << (i-1))) ? "true" : "false"); - response.replace("{j1}", sysCfg.friendlyname[i-1]); - response.replace("{j2}", hue_deviceId(i)); - if (pin[GPIO_WS2812] < 99) { -#ifdef USE_WS2812 - ws2812_replaceHSB(&response); -#endif // USE_WS2812 - } else { - response.replace("{h}", "0"); - response.replace("{s}", "0"); - response.replace("{b}", "0"); - } } response += "}"; - webServer->send(200, F("application/json"), response); + webServer->send(200, FPSTR(HDR_CTYPE_JSON), response); } else if (path->endsWith("/state")) { // Got ID/state path->remove(0,8); // Remove /lights/ @@ -512,15 +563,17 @@ void hue_lights(String *path) if ((device < 1) || (device > Maxdevice)) { device = 1; } - response = "["; - response += FPSTR(HUE_LIGHT_RESPONSE_JSON); - response.replace("{api}", "/lights"); - response.replace("{id}", String(device)); - response.replace("{cmd}", "state/on"); if (1 == webServer->args()) { + response = "["; + StaticJsonBuffer<400> jsonBuffer; JsonObject &hue_json = jsonBuffer.parseObject(webServer->arg(0)); if (hue_json.containsKey("on")) { + + response += FPSTR(HUE_LIGHT_RESPONSE_JSON); + response.replace("{id}", String(device)); + response.replace("{cmd}", "on"); + on = hue_json["on"]; switch(on) { @@ -533,53 +586,76 @@ void hue_lights(String *path) default : response.replace("{res}", (power & (0x01 << (device-1))) ? "true" : "false"); break; } + resp = true; } + + if (sfl_flg) { + sl_getHSB(&hue,&sat,&bri); #ifdef USE_WS2812 - ws2812_getHSB(&hue,&sat,&bri); + } + else if (pin[GPIO_WS2812] < 99) { + ws2812_getHSB(&hue,&sat,&bri); +#endif // USE_WS2812 + } + if (hue_json.containsKey("bri")) { tmp = hue_json["bri"]; - bri = (float)tmp/254.0f; - response += ","; + bri = (float)tmp / 254.0f; + if (resp) { + response += ","; + } response += FPSTR(HUE_LIGHT_RESPONSE_JSON); - response.replace("{api}", "/lights"); response.replace("{id}", String(device)); - response.replace("{cmd}", "state/bri"); + response.replace("{cmd}", "bri"); response.replace("{res}", String(tmp)); + resp = true; change = true; } if (hue_json.containsKey("hue")) { tmp = hue_json["hue"]; - hue = (float)tmp/65535.0f; - response += ","; + hue = (float)tmp / 65535.0f; + if (resp) { + response += ","; + } response += FPSTR(HUE_LIGHT_RESPONSE_JSON); - response.replace("{api}", "/lights"); response.replace("{id}", String(device)); - response.replace("{cmd}", "state/hue"); + response.replace("{cmd}", "hue"); response.replace("{res}", String(tmp)); + resp = true; change = true; } if (hue_json.containsKey("sat")) { tmp = hue_json["sat"]; - sat = (float)tmp/254.0f; - response += ","; + sat = (float)tmp / 254.0f; + if (resp) { + response += ","; + } response += FPSTR(HUE_LIGHT_RESPONSE_JSON); - response.replace("{api}", "/lights"); response.replace("{id}", String(device)); - response.replace("{cmd}", "state/sat"); + response.replace("{cmd}", "sat"); response.replace("{res}", String(tmp)); change = true; } - if (change && (pin[GPIO_WS2812] < 99)) { - ws2812_setHSB(hue, sat, bri); + if (change) { + if (sfl_flg) { + sl_setHSB(hue, sat, bri); +#ifdef USE_WS2812 + } + else if (pin[GPIO_WS2812] < 99) { + ws2812_setHSB(hue, sat, bri); +#endif // USE_WS2812 + } change = false; } -#endif // USE_WS2812 response += "]"; + if (2 == response.length()) { + response = FPSTR(HUE_ERROR_JSON); + } } else { - response=FPSTR(HUE_ERROR_JSON); + response = FPSTR(HUE_ERROR_JSON); } - webServer->send(200, F("application/json"), response); + webServer->send(200, FPSTR(HDR_CTYPE_JSON), response); } else if(path->indexOf("/lights/") >= 0) { // Got /lights/ID path->remove(0,8); // Remove /lights/ @@ -587,22 +663,35 @@ void hue_lights(String *path) if ((device < 1) || (device > Maxdevice)) { device = 1; } - response = FPSTR(HUE_LIGHT_STATUS_JSON); - response.replace("{state}", (power & (0x01 << (device -1))) ? "true" : "false"); - response.replace("{j1}", sysCfg.friendlyname[device -1]); + response += F("{\"state\":{"); + hue_light_status(device, &response); + response += "},"; + response += FPSTR(HUE_LIGHTS_STATUS_JSON); + response.replace("{j1}", sysCfg.friendlyname[device-1]); response.replace("{j2}", hue_deviceId(device)); - if (pin[GPIO_WS2812] < 99) { -#ifdef USE_WS2812 - ws2812_replaceHSB(&response); -#endif // USE_WS2812 - } else { - response.replace("{h}", "0"); - response.replace("{s}", "0"); - response.replace("{b}", "0"); - } - webServer->send(200, F("application/json"), response); + webServer->send(200, FPSTR(HDR_CTYPE_JSON), response); } - else webServer->send(406, F("application/json"), "{}"); + else { + webServer->send(406, FPSTR(HDR_CTYPE_JSON), "{}"); + } +} + +void hue_groups(String *path) +{ + String response = "{}"; + + if (path->endsWith("/0")) { + response = FPSTR(HUE_GROUP0_STATUS_JSON); + String lights = F("\"1\""); + for (uint8_t i = 2; i <= Maxdevice; i++) { + lights += ",\"" + String(i) + "\""; + } + response.replace("{l1}", lights); + hue_light_status(1, &response); + response += F("}}"); + } + + webServer->send(200, FPSTR(HDR_CTYPE_JSON), response); } void handle_hue_api(String *path) @@ -617,8 +706,9 @@ void handle_hue_api(String *path) char log[LOGSZ]; uint8_t args = 0; - path->remove(0, 4); // remove /api - snprintf_P(log, sizeof(log), PSTR("HTTP: Handle Hue API (%s)"), path->c_str()); + path->remove(0, 4); // remove /api + uint16_t apilen = path->length(); + snprintf_P(log, sizeof(log), PSTR("HTTP: Hue API (%s)"), path->c_str()); addLog(LOG_LEVEL_DEBUG_MORE, log); for (args = 0; args < webServer->args(); args++) { String json = webServer->arg(args); @@ -627,10 +717,11 @@ void handle_hue_api(String *path) } if (path->endsWith("/invalid/")) {} // Just ignore + else if (!apilen) hue_auth(path); // New HUE App setup else if (path->endsWith("/")) hue_auth(path); // New HUE App setup else if (path->endsWith("/config")) hue_config(path); else if (path->indexOf("/lights") >= 0) hue_lights(path); - else if (path->endsWith("/groups")) hue_todo(path); + else if (path->indexOf("/groups") >= 0) hue_groups(path); else if (path->endsWith("/schedules")) hue_todo(path); else if (path->endsWith("/sensors")) hue_todo(path); else if (path->endsWith("/scenes")) hue_todo(path);