diff --git a/README.md b/README.md index cf7cd0c8b..18f93efe4 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.16** - See ```sonoff/_releasenotes.ino``` for change information. +Current version is **3.9.17** - 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 70b1540e6..415809a70 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 a98a944c3..e5288d83c 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,9 @@ -/* 3.9.16 20170214 +/* 3.9.17 20170217 + * Fix possible ArduinoJSON related memory fragmentation + * Changed console logging using less memory + * Add GPIO04 as user selectable for Sonoff Dual (#75) + * + * 3.9.16 20170214 * Update latching relay handler * Add support for IR led using IRremoteESP8266 library (#59) * Add Hue argument passing using ArduinoJSON library (#59) diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index d389ee5c2..53509c819 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -10,7 +10,7 @@ * ==================================================== */ -#define VERSION 0x03091000 // 3.9.16 +#define VERSION 0x03091100 // 3.9.17 //#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 @@ -127,7 +127,7 @@ enum emul_t {EMUL_NONE, EMUL_WEMO, EMUL_HUE, EMUL_MAX}; #ifdef USE_MQTT_TLS #define MAX_LOG_LINES 10 // Max number of lines in weblog #else - #define MAX_LOG_LINES 60 // Max number of lines in weblog + #define MAX_LOG_LINES 20 // Max number of lines in weblog #endif #define APP_BAUDRATE 115200 // Default serial baudrate @@ -141,6 +141,7 @@ enum butt_t {PRESSED, NOT_PRESSED}; #include // MQTT, Ota #include // Ota #include // MQTT +#include // WemoHue, IRremote, Domoticz #ifdef USE_WEBSERVER #include // WifiManager, Webserver #include // WifiManager @@ -154,12 +155,6 @@ enum butt_t {PRESSED, NOT_PRESSED}; #ifdef USE_I2C #include // I2C support library #endif // USE_I2C -#if defined USE_EMULATION || defined USE_IR_REMOTE - #include - - const size_t bufferSize = JSON_ARRAY_SIZE(2) + JSON_OBJECT_SIZE(10) + 130; // Required size for complete HUE light JSON object or other JSON objects - DynamicJsonBuffer jsonBuffer(bufferSize); -#endif // USE_EMULATION || USE_IR_REMOTE typedef void (*rtcCallback)(); @@ -900,6 +895,7 @@ void sl_setColor(byte type) } } + void json2legacy(char* stopic, char* svalue) { char *p, *token; @@ -2458,7 +2454,7 @@ void GPIO_init() uint8_t mpin; mytmplt def_module; - if (!sysCfg.module || (sysCfg.module >= MAXMODULE)) sysCfg.module = SONOFF_BASIC; // Sonoff Basic + if (!sysCfg.module || (sysCfg.module >= MAXMODULE)) sysCfg.module = MODULE; memcpy_P(&def_module, &modules[sysCfg.module], sizeof(def_module)); strlcpy(my_module.name, def_module.name, sizeof(my_module.name)); diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index 492e64218..34b9bac74 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -155,7 +155,8 @@ const mytmplt modules[MAXMODULE] PROGMEM = { GPIO_TXD, // GPIO01 Relay control 0, GPIO_RXD, // GPIO03 Relay control - 0, 0, 0, 0, 0, 0, 0, 0, 0, + GPIO_USER, // GPIO04 Optional sensor + 0, 0, 0, 0, 0, 0, 0, 0, GPIO_LED1_INV, // GPIO13 Blue Led (0 = On, 1 = Off) 0, 0, 0 }, diff --git a/sonoff/webserver.ino b/sonoff/webserver.ino index 247be80ff..80679698e 100644 --- a/sonoff/webserver.ino +++ b/sonoff/webserver.ino @@ -60,8 +60,19 @@ const char HTTP_HEAD[] PROGMEM = "var x=new XMLHttpRequest();" "x.onreadystatechange=function(){" "if(x.readyState==4&&x.status==200){" - "e.value=x.responseText;" - "e.scrollTop=100000;" + "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;" + "}" + "}" + "e.scrollTop=99999;" "sn=e.scrollTop;" "}" "};" @@ -75,7 +86,7 @@ const char HTTP_HEAD[] PROGMEM = "div,fieldset,input,select{padding:5px;font-size:1em;}" "input{width:95%;}" "select{width:100%;}" - "textarea{resize:none;width:98%;height:312px;padding:5px;overflow:auto;}" + "textarea{resize:none;width:98%;height:318px;padding:5px;overflow:auto;}" "body{text-align:center;font-family:verdana;}" "td{padding:0px;}" "button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;-webkit-transition-duration:0.4s;transition-duration:0.4s;}" @@ -197,9 +208,9 @@ const char HTTP_FORM_UPG[] PROGMEM = "" ""; const char HTTP_FORM_CMND[] PROGMEM = - "


" + "


" "
" - "
" + "
" // "
" "
"; const char HTTP_COUNTER[] PROGMEM = @@ -427,7 +438,7 @@ void pollDnsWeb() void showPage(String &page) { page.replace("{ha}", my_module.name); - page.replace("{h}", String(sysCfg.friendlyname[0])); + page.replace("{h}", sysCfg.friendlyname[0]); if (_httpflag == HTTP_MANAGER) { if (WIFI_configCounter()) { page.replace("", ""); @@ -681,11 +692,11 @@ void handleWifi(boolean scan) } page += FPSTR(HTTP_FORM_WIFI); - page.replace("{h1}", String(sysCfg.hostname)); - page.replace("{s1}", String(sysCfg.sta_ssid[0])); - page.replace("{p1}", String(sysCfg.sta_pwd[0])); - page.replace("{s2}", String(sysCfg.sta_ssid[1])); - page.replace("{p2}", String(sysCfg.sta_pwd[1])); + page.replace("{h1}", sysCfg.hostname); + page.replace("{s1}", sysCfg.sta_ssid[0]); + page.replace("{p1}", sysCfg.sta_pwd[0]); + page.replace("{s2}", sysCfg.sta_ssid[1]); + page.replace("{p2}", sysCfg.sta_pwd[1]); page += FPSTR(HTTP_FORM_END); if (_httpflag == HTTP_MANAGER) { page += FPSTR(HTTP_BTN_RSTRT); @@ -706,12 +717,12 @@ void handleMqtt() char str[sizeof(sysCfg.mqtt_client)]; getClient(str, MQTT_CLIENT_ID, sizeof(sysCfg.mqtt_client)); page.replace("{m0}", str); - page.replace("{m1}", String(sysCfg.mqtt_host)); + page.replace("{m1}", sysCfg.mqtt_host); page.replace("{m2}", String(sysCfg.mqtt_port)); - page.replace("{m3}", String(sysCfg.mqtt_client)); - page.replace("{m4}", String(sysCfg.mqtt_user)); - page.replace("{m5}", String(sysCfg.mqtt_pwd)); - page.replace("{m6}", String(sysCfg.mqtt_topic)); + page.replace("{m3}", sysCfg.mqtt_client); + page.replace("{m4}", sysCfg.mqtt_user); + page.replace("{m5}", sysCfg.mqtt_pwd); + page.replace("{m6}", sysCfg.mqtt_topic); page += FPSTR(HTTP_FORM_END); page += FPSTR(HTTP_BTN_CONF); showPage(page); @@ -755,7 +766,7 @@ void handleLog() } } page += FPSTR(HTTP_FORM_LOG3); - page.replace("{l2}", String(sysCfg.syslog_host)); + page.replace("{l2}", sysCfg.syslog_host); page.replace("{l3}", String(sysCfg.syslog_port)); page.replace("{l4}", String(sysCfg.tele_period)); page += FPSTR(HTTP_FORM_END); @@ -776,7 +787,7 @@ void handleOther() page += FPSTR(HTTP_FORM_OTHER2); page.replace("{1", "1"); page.replace("{2", FRIENDLY_NAME); - page.replace("{3", String(sysCfg.friendlyname[0])); + page.replace("{3", sysCfg.friendlyname[0]); #ifdef USE_EMULATION page += FPSTR(HTTP_FORM_OTHER3); page.replace("{r2}", (sysCfg.emulation == EMUL_NONE) ? " checked" : ""); @@ -787,7 +798,7 @@ void handleOther() page.replace("{1", String(i +1)); snprintf_P(stemp, sizeof(stemp), PSTR(FRIENDLY_NAME"%d"), i +1); page.replace("{2", stemp); - page.replace("{3", String(sysCfg.friendlyname[i])); + page.replace("{3", sysCfg.friendlyname[i]); } page += F("
"); #endif // USE_EMULATION @@ -966,7 +977,7 @@ void handleUpgrade() String page = FPSTR(HTTP_HEAD); page.replace("{v}", "Firmware upgrade"); page += FPSTR(HTTP_FORM_UPG); - page.replace("{o1}", String(sysCfg.otaUrl)); + page.replace("{o1}", sysCfg.otaUrl); page += FPSTR(HTTP_BTN_MAIN); showPage(page); @@ -1269,7 +1280,7 @@ void handleInfo() for (byte i = 0; i < Maxdevice; i++) { page += F("Friendly name "); page += i +1; - page += F(""); page += String(sysCfg.friendlyname[i]); page += F(""); + 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("%)"); @@ -1387,7 +1398,7 @@ void handleUPnPsetupWemo() addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo setup")); String setup_xml = FPSTR(WEMO_SETUP_XML); - setup_xml.replace("{x1}", String(sysCfg.friendlyname[0])); + setup_xml.replace("{x1}", sysCfg.friendlyname[0]); setup_xml.replace("{x2}", wemo_UUID()); setup_xml.replace("{x3}", wemo_serial()); webServer->send(200, "text/xml", setup_xml); @@ -1432,7 +1443,7 @@ void hue_config_response(String *response) response->replace("{gw}", WiFi.gatewayIP().toString()); snprintf_P(buffer, sizeof(buffer), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), rtcTime.Year, rtcTime.Month, rtcTime.Day, rtcTime.Hour, rtcTime.Minute, rtcTime.Second); - response->replace("{dt}", String(buffer)); + response->replace("{dt}", buffer); } void hue_global_cfg(String *path) @@ -1471,13 +1482,9 @@ void hue_global_cfg(String *path) void hue_auth(String *path) { - String response; - char uid[7]; + char response[38]; - snprintf_P(uid, sizeof(uid), PSTR("%03x"), ESP.getChipId()); - response="[{\"success\":{\"username\":\""; - response+=String(uid); - response+="\"}}]"; + snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%03x\"}}]"), ESP.getChipId()); webServer->send(200, "application/json", response); } @@ -1493,6 +1500,9 @@ void hue_config(String *path) 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; uint8_t device = 1; int16_t pos = 0; @@ -1530,7 +1540,7 @@ void hue_lights(String *path) else if (path->endsWith("/state")) // Got ID/state { path->remove(0,8); // Remove /lights/ - path->remove(path->indexOf("/state")); // Remove /state + path->remove(path->indexOf("/state")); // Remove /state device = atoi(path->c_str()); if ((device < 1) || (device > Maxdevice)) device = 1; response = "["; @@ -1540,6 +1550,7 @@ void hue_lights(String *path) response.replace("{cmd}", "state/on"); if (webServer->args() == 1) { + StaticJsonBuffer<400> jsonBuffer; JsonObject &hue_json = jsonBuffer.parseObject(webServer->arg(0)); on = hue_json["on"]; switch(on) diff --git a/sonoff/xdrv_domoticz.ino b/sonoff/xdrv_domoticz.ino index 48835bf8f..378d0bfbc 100644 --- a/sonoff/xdrv_domoticz.ino +++ b/sonoff/xdrv_domoticz.ino @@ -40,36 +40,6 @@ const char domoticz_sensors[DOMOTICZ_MAX_SENSORS][14] PROGMEM = int domoticz_update_timer = 0; byte domoticz_update_flag = 1; -unsigned long getKeyIntValue(const char *json, const char *key) -{ - char *p, *b; - int i; - - // search key - p = strstr(json, key); - if (!p) return 0; - // search following separator : - b = strchr(p + strlen(key), ':'); - if (!b) return 0; - // Only the following chars are allowed between key and separator : - for(i = b - json + strlen(key); i < p-json; i++) { - switch (json[i]) { - case ' ': - case '\n': - case '\t': - case '\r': - continue; - default: - return 0; - } - } - b++; - // Allow integers as string too (used in "svalue" : "9") - while ((b[0] == ' ') || (b[0] == '"')) b++; - // Convert to integer - return atoi(b); -} - void mqtt_publishDomoticzPowerState(byte device) { char svalue[MESSZ]; @@ -130,6 +100,23 @@ boolean domoticz_update() return domoticz_update_flag; } +/* + * ArduinoJSON Domoticz Switch entry used to calculate jsonBuf: JSON_OBJECT_SIZE(11) + 129 = 313 +{ + "Battery" : 255, + "RSSI" : 12, + "dtype" : "Light/Switch", + "id" : "000140E7", + "idx" : 159, + "name" : "Sonoff1", + "nvalue" : 1, + "stype" : "Switch", + "svalue1" : "0", + "switchType" : "Dimmer", + "unit" : 1 +} +*/ + boolean domoticz_mqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uint16_t sdataBuf) { char log[LOGSZ], stemp1[10]; @@ -139,8 +126,12 @@ boolean domoticz_mqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uin domoticz_update_flag = 1; if (!strncmp(topicBuf, sysCfg.domoticz_out_topic, strlen(sysCfg.domoticz_out_topic)) != 0) { if (sdataBuf < 20) return 1; - idx = getKeyIntValue(dataBuf,"\"idx\""); - nvalue = getKeyIntValue(dataBuf,"\"nvalue\""); + StaticJsonBuffer<400> jsonBuf; + JsonObject& domoticz = jsonBuf.parseObject(dataBuf); + if (!domoticz.success()) return 1; +// if (strcmp(domoticz["dtype"],"Light/Switch")) return 1; + idx = domoticz["idx"]; + nvalue = domoticz["nvalue"]; snprintf_P(log, sizeof(log), PSTR("DMTZ: idx %d, nvalue %d"), idx, nvalue); addLog(LOG_LEVEL_DEBUG_MORE, log); @@ -150,7 +141,7 @@ boolean domoticz_mqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uin if ((idx > 0) && (idx == sysCfg.domoticz_relay_idx[i])) { snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), i +1); if (nvalue == 2) { - nvalue = getKeyIntValue(dataBuf,"\"svalue1\""); + nvalue = domoticz["svalue1"]; if ((pin[GPIO_WS2812] < 99) && (sysCfg.ws_dimmer == nvalue)) return 1; if ((sysCfg.module == SONOFF_LED) && (sysCfg.led_dimmer[i] == nvalue)) return 1; snprintf_P(topicBuf, stopicBuf, PSTR("%s/%s/DIMMER%s"), diff --git a/sonoff/xdrv_ir_send.ino b/sonoff/xdrv_ir_send.ino index 1e3a54f43..3b26c6818 100644 --- a/sonoff/xdrv_ir_send.ino +++ b/sonoff/xdrv_ir_send.ino @@ -42,6 +42,11 @@ void ir_send_init(void) * Commands \*********************************************************************************************/ +/* + * ArduinoJSON IRsend entry used to calculate jsonBuf: JSON_OBJECT_SIZE(3) + 40 = 96 + { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 } +*/ + boolean ir_send_command(char *type, uint16_t index, char *dataBuf, uint16_t data_len, int16_t payload, char *svalue, uint16_t ssvalue) { boolean serviced = true; @@ -52,7 +57,8 @@ boolean ir_send_command(char *type, uint16_t index, char *dataBuf, uint16_t data if (!strcmp(type,"IRSEND")) { if (data_len) { - JsonObject &ir_json = jsonBuffer.parseObject(dataBuf); + StaticJsonBuffer<128> jsonBuf; + JsonObject &ir_json = jsonBuf.parseObject(dataBuf); if (!ir_json.success()) { snprintf_P(svalue, ssvalue, PSTR("{\"IRSend\":\"Invalid JSON\"}")); // JSON decode failed } else {