diff --git a/README.md b/README.md index 8edc4f8e2..ee5810873 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 **3.9.20** - See ```sonoff/_releasenotes.ino``` for change information. +Current version is **3.9.21** - See ```sonoff/_releasenotes.ino``` for change information. - This version provides all (Sonoff) modules in one file and starts up with Sonoff Basic. - Once uploaded select module using the configuration webpage or the commands ```Modules``` and ```Module```. diff --git a/api/arduino/sonoff.ino.bin b/api/arduino/sonoff.ino.bin index 5064ad5c4..d2fb0d14d 100644 Binary files a/api/arduino/sonoff.ino.bin and b/api/arduino/sonoff.ino.bin differ diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index 9fbfddea4..0bcd5e4d5 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,9 @@ -/* 3.9.20 20170221 +/* 3.9.21 20170224 + * Add ajax to web root page and web console (#79) + * Add commands SwitchMode1..4 and enable user switches 2, 3 and 4 (#84, #88) + * Fix MQTT upgrade when webserver is active + * + * 3.9.20 20170221 * Add minimal basic authentication to Web Admin mode (#87) * Fix Hue and add HSB support (#89) * diff --git a/sonoff/settings.h b/sonoff/settings.h index 6da85fe97..55ee022c4 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -114,7 +114,7 @@ struct SYSCFG { uint8_t power; uint8_t ledstate; - uint8_t switchmode; + uint8_t ex_switchmode; // Not used since 3.9.21 char domoticz_in_topic[33]; char domoticz_out_topic[33]; @@ -185,5 +185,7 @@ struct SYSCFG { uint8_t emulation; char web_password[33]; + uint8_t switchmode[4]; + } sysCfg; diff --git a/sonoff/settings.ino b/sonoff/settings.ino index 1abd9b1d9..9112638a2 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -367,7 +367,7 @@ void CFG_DefaultSet2() sysCfg.poweronstate = APP_POWERON_STATE; sysCfg.pulsetime = APP_PULSETIME; sysCfg.ledstate = APP_LEDSTATE; - sysCfg.switchmode = SWITCH_MODE; +// sysCfg.switchmode = SWITCH_MODE; sysCfg.blinktime = APP_BLINKTIME; sysCfg.blinkcount = APP_BLINKCOUNT; sysCfg.sleep = APP_SLEEP; @@ -376,6 +376,7 @@ void CFG_DefaultSet2() strlcpy(sysCfg.domoticz_out_topic, DOMOTICZ_OUT_TOPIC, sizeof(sysCfg.domoticz_out_topic)); sysCfg.domoticz_update_timer = DOMOTICZ_UPDATE_TIMER; for (byte i = 0; i < 4; i++) { + sysCfg.switchmode[i] = SWITCH_MODE; sysCfg.domoticz_relay_idx[i] = 0; sysCfg.domoticz_key_idx[i] = 0; sysCfg.domoticz_switch_idx[i] = 0; @@ -545,7 +546,7 @@ void CFG_Migrate_Part2() sysCfg.hlw_kWhdoy = sysCfg2.hlw_kWhdoy; } if (sysCfg2.version >= 0x02001200) { // 2.0.18 - sysCfg.switchmode = sysCfg2.switchmode; + sysCfg.switchmode[0] = sysCfg2.switchmode; } if (sysCfg2.version >= 0x02010000) { // 2.1.0 strlcpy(sysCfg.mqtt_fingerprint, sysCfg2.mqtt_fingerprint, sizeof(sysCfg.mqtt_fingerprint)); @@ -629,6 +630,9 @@ void CFG_Delta() if (sysCfg.version < 0x03091301) { strlcpy(sysCfg.web_password, WEB_PASSWORD, sizeof(sysCfg.web_password)); } + if (sysCfg.version < 0x03091500) { + for (byte i = 0; i < 4; i++) sysCfg.switchmode[i] = sysCfg.ex_switchmode; + } sysCfg.version = VERSION; } diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index d68449037..1b6fc9c7d 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -10,7 +10,7 @@ * ==================================================== */ -#define VERSION 0x03091400 // 3.9.20 +#define VERSION 0x03091500 // 3.9.21 //#define BE_MINIMAL // Compile a minimal version if upgrade memory gets tight (still 404k) // To be used as step 1. Next step is compile and use desired version @@ -118,7 +118,7 @@ enum emul_t {EMUL_NONE, EMUL_WEMO, EMUL_HUE, EMUL_MAX}; #define STATES 10 // loops per second #define SYSLOG_TIMER 600 // Seconds to restore syslog_level #define SERIALLOG_TIMER 600 // Seconds to disable SerialLog -#define OTA_ATTEMPTS 5 // Number of times to try fetching the new firmware +#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 TOPSZ 60 // Max number of characters in topic string @@ -213,12 +213,14 @@ int state = 0; // State per second flag int mqttflag = 2; // MQTT connection messages flag int otaflag = 0; // OTA state flag int otaok = 0; // OTA result +byte otaretry = OTA_ATTEMPTS; // OTA retry counter int restartflag = 0; // Sonoff restart flag int wificheckflag = WIFI_RESTART; // Wifi state flag int uptime = 0; // Current uptime in hours int tele_period = 0; // Tele period timer String Log[MAX_LOG_LINES]; // Web log buffer byte logidx = 0; // Index in Web log buffer +byte logajaxflg = 0; // Reset web console log byte Maxdevice = MAX_DEVICE; // Max number of devices supported int status_update_timer = 0; // Refresh initial status uint16_t pulse_timer = 0; // Power off timer @@ -891,11 +893,11 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) } snprintf_P(svalue, sizeof(svalue), PSTR("{\"FriendlyName%d\":\"%s\"}"), index, sysCfg.friendlyname[index -1]); } - else if (swt_flg && !strcmp(type,"SWITCHMODE")) { + else if (swt_flg && !strcmp(type,"SWITCHMODE") && (index > 0) && (index <= 4)) { if ((data_len > 0) && (payload >= 0) && (payload < MAX_SWITCH_OPTION)) { - sysCfg.switchmode = payload; + sysCfg.switchmode[index -1] = payload; } - snprintf_P(svalue, sizeof(svalue), PSTR("{\"SwitchMode\":%d}"), sysCfg.switchmode); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"SwitchMode%d\":%d}"), index, sysCfg.switchmode[index-1]); } #ifdef USE_WEBSERVER else if (!strcmp(type,"WEBSERVER")) { @@ -1184,7 +1186,7 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) snprintf_P(svalue, sizeof(svalue), PSTR("{\"Commands3\":\"%s%s, PulseTime, BlinkTime, BlinkCount"), (Maxdevice == 1) ? "Power, Light" : "Power1, Power2, Light1 Light2", (sysCfg.module != MOTOR) ? ", PowerOnState" : ""); #ifdef USE_WEBSERVER - snprintf_P(svalue, sizeof(svalue), PSTR("%s, Weblog, Webserver, Emulation"), svalue); + snprintf_P(svalue, sizeof(svalue), PSTR("%s, Weblog, Webserver, WebPassword, Emulation"), svalue); #endif if (swt_flg) snprintf_P(svalue, sizeof(svalue), PSTR("%s, SwitchMode"), svalue); #ifdef USE_I2C @@ -1223,10 +1225,10 @@ void send_button_power(byte key, byte device, byte state) char stopic[TOPSZ], svalue[TOPSZ], stemp1[10]; - if (device > Maxdevice) device = 1; + if (!key && (device > Maxdevice)) device = 1; snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), device); snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s%s"), - SUB_PREFIX, (key) ? sysCfg.switch_topic : sysCfg.button_topic, sysCfg.mqtt_subtopic, (Maxdevice > 1) ? stemp1 : ""); + SUB_PREFIX, (key) ? sysCfg.switch_topic : sysCfg.button_topic, sysCfg.mqtt_subtopic, (key || (Maxdevice > 1)) ? stemp1 : ""); if (state == 3) { svalue[0] = '\0'; @@ -1676,11 +1678,12 @@ void stateloop() lastbutton[i] = button; } - for (byte i = 0; i < Maxdevice; i++) if (pin[GPIO_SWT1 +i] < 99) { +// for (byte i = 0; i < Maxdevice; i++) if (pin[GPIO_SWT1 +i] < 99) { + for (byte i = 0; i < 4; i++) if (pin[GPIO_SWT1 +i] < 99) { button = digitalRead(pin[GPIO_SWT1 +i]); if (button != lastwallswitch[i]) { switchflag = 3; - switch (sysCfg.switchmode) { + switch (sysCfg.switchmode[i]) { case TOGGLE: switchflag = 2; // Toggle break; @@ -1698,9 +1701,9 @@ void stateloop() } if (switchflag < 3) { if (sysCfg.mqtt_enabled && mqttClient.connected() && strcmp(sysCfg.switch_topic,"0")) { - send_button_power(1, i +1, switchflag); // Execute commend via MQTT + send_button_power(1, i +1, switchflag); // Execute commend via MQTT } else { - do_cmnd_power(i +1, switchflag); // Execute command internally + do_cmnd_power(i +1, switchflag); // Execute command internally (if i < Maxdevice) } } lastwallswitch[i] = button; @@ -1730,30 +1733,34 @@ void stateloop() case (STATES/10)*2: if (otaflag) { otaflag--; - if (otaflag <= 0) { - otaflag = 12; + if (otaflag == 2){ + otaretry = OTA_ATTEMPTS; ESPhttpUpdate.rebootOnUpdate(false); sl_blank(1); - // Try multiple times to get the update, in case we have a transient issue. - // e.g. Someone issued "cmnd/sonoffs/update 1" and all the devices - // are hammering the OTAURL. - for (byte i = 0; i < OTA_ATTEMPTS && !otaok; i++) { - // Delay an increasing pseudo-random time for each interation. - // Starting at 0 (no delay) up to a maximum of OTA_ATTEMPTS-1 seconds. - delay((ESP.getChipId() % 1000) * i); + } + if (otaflag <= 0) { +#ifdef USE_WEBSERVER + if (sysCfg.webserver) stopWebserver(); +#endif // USE_WEBSERVER + otaflag = 92; + otaok = 0; + otaretry--; + if (otaretry) { +// snprintf_P(log, sizeof(log), PSTR("OTA: Attempt %d"), OTA_ATTEMPTS - otaretry); +// addLog(LOG_LEVEL_INFO, log); otaok = (ESPhttpUpdate.update(sysCfg.otaUrl) == HTTP_UPDATE_OK); + if (!otaok) otaflag = 2; } } - if (otaflag == 10) { // Allow MQTT to reconnect + if (otaflag == 90) { // Allow MQTT to reconnect otaflag = 0; if (otaok) { if ((sysCfg.module == SONOFF_TOUCH) || (sysCfg.module == SONOFF_4CH)) setFlashChipMode(1, 3); // DOUT - ESP8285 snprintf_P(svalue, sizeof(svalue), PSTR("Successful. Restarting")); - restartflag = 2; } else { - sl_blank(0); snprintf_P(svalue, sizeof(svalue), PSTR("Failed %s"), ESPhttpUpdate.getLastErrorString().c_str()); } + restartflag = 2; // Restart anyway to keep memory clean webserver mqtt_publish_topic_P(0, PSTR("UPGRADE"), svalue); } } diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index b8c0f64b1..598992632 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -14,6 +14,9 @@ enum upins_t { GPIO_WS2812, // WS2812 Led string GPIO_IRSEND, // IR remote GPIO_SWT1, // User connected external switches + GPIO_SWT2, + GPIO_SWT3, + GPIO_SWT4, GPIO_SENSOR_END }; // Text in webpage Module Parameters and commands GPIOS and GPIO @@ -27,14 +30,14 @@ const char sensors[GPIO_SENSOR_END][9] PROGMEM = { "I2C SDA", "WS2812", "IRremote", - "Switch" }; - + "Switch1", + "Switch2", + "Switch3", + "Switch4" }; + // Programmer selectable GPIO functionality offset by user selectable GPIOs enum fpins_t { - GPIO_SWT2 = GPIO_SENSOR_END, - GPIO_SWT3, - GPIO_SWT4, - GPIO_KEY1, // Button usually connected to GPIO0 + GPIO_KEY1 = GPIO_SENSOR_END, // Button usually connected to GPIO0 GPIO_KEY2, GPIO_KEY3, GPIO_KEY4, @@ -107,14 +110,22 @@ typedef struct MYTMPLT { const mytmplt modules[MAXMODULE] PROGMEM = { { "Sonoff Basic", // Sonoff Basic GPIO_KEY1, // GPIO00 Button - 0, 0, + 0, // GPIO01 Serial RXD + 0, // GPIO02 GPIO_USER, // GPIO03 Serial TXD and Optional sensor GPIO_USER, // GPIO04 Optional sensor - 0, 0, 0, 0, 0, 0, 0, + 0, // GPIO05 + 0, // GPIO06 (SD_CLK Flash) + 0, // GPIO07 (SD_DATA0 Flash QIO/DIO/DOUT) + 0, // GPIO08 (SD_DATA1 Flash QIO/DIO) + 0, // GPIO09 (SD_DATA2 Flash QIO) + 0, // GPIO10 (SD_DATA3 Flash QIO) + 0, // GPIO11 (SD_CMD Flash) GPIO_REL1, // GPIO12 Red Led and Relay (0 = Off, 1 = On) GPIO_LED1_INV, // GPIO13 Green Led (0 = On, 1 = Off) GPIO_USER, // GPIO14 Optional sensor - 0, 0 + 0, // GPIO15 + 0 // GPIO16 }, { "Sonoff RF", // Sonoff RF GPIO_KEY1, // GPIO00 Button diff --git a/sonoff/webserver.ino b/sonoff/webserver.ino index ab4332408..83233d952 100644 --- a/sonoff/webserver.ino +++ b/sonoff/webserver.ino @@ -53,34 +53,58 @@ const char HTTP_HEAD[] PROGMEM = "document.getElementById('s1').value=l.innerText||l.textContent;" "document.getElementById('p1').focus();" "}" - "var sn=0;" + "var x=null;" // Allow for abortion + "var lt;" // Enable clearTimeout + "function la(p){" + "var a='';" + "if(la.arguments.length==1){" + "a='?o='+p;" + "clearTimeout(lt);" + "}" + "if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1) + "x=new XMLHttpRequest();" + "x.onreadystatechange=function(){" + "if(x.readyState==4&&x.status==200){" + "document.getElementById('l1').innerHTML=x.responseText;" + "}" + "};" + "x.open('GET','ay'+a,true);" + "x.send();" + "lt=setTimeout(la,2000);" + "}" + "var sn=0;" // Scroll position + "var id=99;" // Get most of weblog initially "function l(){" "var e=document.getElementById('t1');" - "if(e.scrollTop>=sn){" - "var x=new XMLHttpRequest();" + "if(e.scrollTop>=sn){" // User scrolled back so no updates + "if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1) + "x=new XMLHttpRequest();" "x.onreadystatechange=function(){" "if(x.readyState==4&&x.status==200){" - "var s1=x.responseText;" - "if(e.value.length==0){" - "e.value=s1;" - "}else{" - "var s2=e.value.slice(e.value.lastIndexOf(\"\\n\")+2);" - "var p2=s1.search(s2);" - "if(p2>-1){" - "e.value=e.value.replace(s2,s1.slice(p2));" - "}else{" - "e.value=e.value+\"\\n\"+s1;" - "}" - "}" + "var z,d;" + "d=x.responseXML;" + "id=d.getElementsByTagName('i')[0].childNodes[0].nodeValue;" + "if(d.getElementsByTagName('j')[0].childNodes[0].nodeValue==0){e.value=\"\";}" + "z=d.getElementsByTagName('l')[0].childNodes;" + "if(z.length>0){e.value+=z[0].nodeValue;}" "e.scrollTop=99999;" "sn=e.scrollTop;" "}" "};" - "x.open('GET','ax',true);" + "x.open('GET','ax?c2='+id,true);" "x.send();" "}" "setTimeout(l,2000);" "}" + "function lc(){" + "var e=document.getElementById('c1');" +// "if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1) + "var x=new XMLHttpRequest();" + "x.open('GET','ax?c1='+e.value+'&c2='+id,true);" + "x.send();" + "e.value=\"\";" + "return false;" + "}" "" "