diff --git a/README.md b/README.md index 00eb424bd..561968a0f 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.0.6** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/master/sonoff/_releasenotes.ino) for change information. +Current version is **5.0.7** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/master/sonoff/_releasenotes.ino) for change information. ### **** ATTENTION Version 5.0.x specific information **** diff --git a/api/arduino/sonoff.ino.bin b/api/arduino/sonoff.ino.bin index a8f879992..8f8759383 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 c63487736..5cded0c3b 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,5 +1,14 @@ -/* 5.0.6 20170510 - * Remove hyphen in case of a single DHT sensor connecetd (#427) +/* 5.0.7 20170511 + * Fix possible exception 28 on empty command + * Add command SetOption0 as replacement for SaveState + * Add command SetOption1 as replacement for ButtonRestrict + * Add command SetOption2 as replacement for Units + * Add command SetOption4 as replacement for MqttResponse + * Add command SetOption8 as replacement for TempUnit + * Add command SetOption10 On|Off to select between Offline or Removing previous retained topic (#417, #436) + * + * 5.0.6 20170510 + * Remove hyphen in case of a single DHT sensor connected (#427) * Add command MqttRetry to change default MQTT reconnect retry timer from minimal 10 seconds (#429) * * 5.0.5 20170508 diff --git a/sonoff/settings.h b/sonoff/settings.h index 21d98205f..2990dbe18 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -2,33 +2,36 @@ * Config settings \*********************************************************************************************/ -typedef struct { - uint32_t savestate : 1; - uint32_t button_restrict : 1; - uint32_t value_units : 1; - uint32_t mqtt_enabled : 1; - uint32_t mqtt_response : 1; - uint32_t mqtt_power_retain : 1; - uint32_t mqtt_button_retain : 1; - uint32_t mqtt_switch_retain : 1; - uint32_t temperature_conversion : 1; - uint32_t mqtt_sensor_retain : 1; - uint32_t spare22 : 1; - uint32_t spare21 : 1; - uint32_t spare20 : 1; - uint32_t spare19 : 1; - uint32_t spare18 : 1; - uint32_t spare17 : 1; - uint32_t spare16 : 1; - uint32_t spare15 : 1; - uint32_t spare14 : 1; - uint32_t spare13 : 1; - uint32_t spare12 : 1; - uint32_t emulation : 2; - uint32_t energy_resolution : 3; - uint32_t pressure_resolution : 2; - uint32_t humidity_resolution : 2; - uint32_t temperature_resolution : 2; +typedef union { // Restricted by MISRA-C Rule 18.4 but so usefull... + uint32_t data; // Allow bit manipulation using SetOption + struct { + uint32_t savestate : 1; // bit 0 + uint32_t button_restrict : 1; // bit 1 + uint32_t value_units : 1; // bit 2 + uint32_t mqtt_enabled : 1; + uint32_t mqtt_response : 1; // bit 4 + uint32_t mqtt_power_retain : 1; + uint32_t mqtt_button_retain : 1; + uint32_t mqtt_switch_retain : 1; + uint32_t temperature_conversion : 1; // bit 8 + uint32_t mqtt_sensor_retain : 1; + uint32_t mqtt_offline : 1; // bit 10 + uint32_t spare11 : 1; + uint32_t spare12 : 1; + uint32_t spare13 : 1; + uint32_t spare14 : 1; + uint32_t spare15 : 1; + uint32_t spare16 : 1; + uint32_t spare17 : 1; + uint32_t spare18 : 1; + uint32_t spare19 : 1; + uint32_t spare20 : 1; + uint32_t emulation : 2; + uint32_t energy_resolution : 3; + uint32_t pressure_resolution : 2; + uint32_t humidity_resolution : 2; + uint32_t temperature_resolution : 2; + }; } sysBitfield; struct SYSCFG { diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index f982c1b89..da230869d 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -10,7 +10,7 @@ * ==================================================== */ -#define VERSION 0x05000600 // 5.0.6 +#define VERSION 0x05000700 // 5.0.7 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}; @@ -749,8 +749,12 @@ boolean mqtt_command(boolean grpflg, char *type, uint16_t index, char *dataBuf, if (!strcmp(dataBuf, MQTTClient)) { payload = 1; } - strlcpy(sysCfg.mqtt_topic, (1 == payload) ? MQTT_TOPIC : dataBuf, sizeof(sysCfg.mqtt_topic)); - restartflag = 2; + strlcpy(stemp1, (1 == payload) ? MQTT_TOPIC : dataBuf, sizeof(stemp1)); + if (strcmp(stemp1, sysCfg.mqtt_topic)) { + mqtt_publish_topic_P(2, PSTR("LWT"), (sysCfg.flag.mqtt_offline) ? "Offline" : "", true); // Offline or remove previous retained topic + strlcpy(sysCfg.mqtt_topic, stemp1, sizeof(sysCfg.mqtt_topic)); + restartflag = 2; + } } snprintf_P(svalue, ssvalue, PSTR("{\"Topic\":\"%s\"}"), sysCfg.mqtt_topic); } @@ -1013,6 +1017,20 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) } snprintf_P(svalue, sizeof(svalue), PSTR("{\"SaveData\":\"%s\"}"), (sysCfg.savedata > 1) ? stemp1 : getStateText(sysCfg.savedata)); } + else if (!strcmp_P(type,PSTR("SETOPTION")) && (index >= 0) && (index <= 10)) { + if ((data_len > 0) && (payload >= 0) && (payload <= 1)) { + switch (index) { + case 0: // savestate + case 1: // button_restrict + case 2: // value_units + case 4: // mqtt_response + case 8: // temperature_conversion + case 10: // mqtt_offline + bitWrite(sysCfg.flag.data, index, payload); + } + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"SetOption%d\":\"%s\"}"), index, getStateText(bitRead(sysCfg.flag.data, index))); + } else if (!strcmp_P(type,PSTR("SAVESTATE"))) { if ((data_len > 0) && (payload >= 0) && (payload <= 1)) { sysCfg.flag.savestate = payload; @@ -1619,7 +1637,7 @@ void do_cmnd(char *cmnd) token = start +1; } } - snprintf_P(stopic, sizeof(stopic), PSTR("/%s"), token); + snprintf_P(stopic, sizeof(stopic), PSTR("/%s"), (token == NULL) ? "" : token); token = strtok(NULL, ""); snprintf_P(svalue, sizeof(svalue), PSTR("%s"), (token == NULL) ? "" : token); mqttDataCb(stopic, (byte*)svalue, strlen(svalue)); diff --git a/sonoff/webserver.ino b/sonoff/webserver.ino index f26301eff..5db7f00bb 100644 --- a/sonoff/webserver.ino +++ b/sonoff/webserver.ino @@ -278,6 +278,9 @@ const char HTTP_END[] PROGMEM = "" ""; +const char HDR_CCNTL[] PROGMEM = "Cache-Control"; +const char HDR_REVAL[] PROGMEM = "no-cache, no-store, must-revalidate"; + #define DNS_PORT 53 enum http_t {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER}; @@ -405,10 +408,10 @@ void showPage(String &page) } page += FPSTR(HTTP_END); - webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - webServer->sendHeader("Pragma", "no-cache"); - webServer->sendHeader("Expires", "-1"); - webServer->send(200, "text/html", page); + 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); } void handleRoot() @@ -529,10 +532,7 @@ void handleAjax2() page += line; } */ - webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - webServer->sendHeader("Pragma", "no-cache"); - webServer->sendHeader("Expires", "-1"); - webServer->send(200, "text/plain", page); + webServer->send(200, F("text/html"), page); } boolean httpUser() @@ -896,8 +896,8 @@ void handleDownload() char attachment[100]; snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), sysCfg.friendlyname[0], Version); - webServer->sendHeader("Content-Disposition", attachment); - webServer->send(200, "application/octet-stream", ""); + webServer->sendHeader(F("Content-Disposition"), attachment); + webServer->send(200, F("application/octet-stream"), ""); memcpy(buffer, &sysCfg, sizeof(sysCfg)); buffer[0] = CONFIG_FILE_SIGN; buffer[1] = (!CONFIG_FILE_XOR)?0:1; @@ -916,7 +916,7 @@ void handleSave() } char log[LOGSZ +20]; - char stemp[20]; + char stemp[TOPSZ]; byte what = 0; byte restart; String result = ""; @@ -942,12 +942,16 @@ void handleSave() result += F("
Trying to connect device to network
If it fails reconnect to try again"); break; case 2: + strlcpy(stemp, (!strlen(webServer->arg("mt").c_str())) ? MQTT_TOPIC : webServer->arg("mt").c_str(), sizeof(stemp)); + if (strcmp(stemp, sysCfg.mqtt_topic)) { + mqtt_publish_topic_P(2, PSTR("LWT"), (sysCfg.flag.mqtt_offline) ? "Offline" : "", true); // Offline or remove previous retained topic + } + strlcpy(sysCfg.mqtt_topic, stemp, sizeof(sysCfg.mqtt_topic)); strlcpy(sysCfg.mqtt_host, (!strlen(webServer->arg("mh").c_str())) ? MQTT_HOST : webServer->arg("mh").c_str(), sizeof(sysCfg.mqtt_host)); sysCfg.mqtt_port = (!strlen(webServer->arg("ml").c_str())) ? MQTT_PORT : atoi(webServer->arg("ml").c_str()); strlcpy(sysCfg.mqtt_client, (!strlen(webServer->arg("mc").c_str())) ? MQTT_CLIENT_ID : webServer->arg("mc").c_str(), sizeof(sysCfg.mqtt_client)); strlcpy(sysCfg.mqtt_user, (!strlen(webServer->arg("mu").c_str())) ? MQTT_USER : (!strcmp(webServer->arg("mu").c_str(),"0")) ? "" : webServer->arg("mu").c_str(), sizeof(sysCfg.mqtt_user)); strlcpy(sysCfg.mqtt_pwd, (!strlen(webServer->arg("mp").c_str())) ? MQTT_PASS : (!strcmp(webServer->arg("mp").c_str(),"0")) ? "" : webServer->arg("mp").c_str(), sizeof(sysCfg.mqtt_pwd)); - strlcpy(sysCfg.mqtt_topic, (!strlen(webServer->arg("mt").c_str())) ? MQTT_TOPIC : webServer->arg("mt").c_str(), sizeof(sysCfg.mqtt_topic)); snprintf_P(log, sizeof(log), PSTR("HTTP: MQTT Host %s, Port %d, Client %s, User %s, Password %s, Topic %s"), sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.mqtt_client, sysCfg.mqtt_user, sysCfg.mqtt_pwd, sysCfg.mqtt_topic); addLog(LOG_LEVEL_INFO, log); @@ -1340,11 +1344,7 @@ void handleCmnd() } else { message = F("Need user=&password=\n"); } - - webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - webServer->sendHeader("Pragma", "no-cache"); - webServer->sendHeader("Expires", "-1"); - webServer->send(200, "text/plain", message); + webServer->send(200, F("text/plain"), message); } void handleConsole() @@ -1418,11 +1418,7 @@ void handleAjax() } while (counter != logidx); } message += F(""); - - webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - webServer->sendHeader("Pragma", "no-cache"); - webServer->sendHeader("Expires", "-1"); - webServer->send(200, "text/xml", message); + webServer->send(200, F("text/xml"), message); } void handleInfo() @@ -1572,10 +1568,10 @@ void handleNotFound() message += " " + webServer->argName ( i ) + ": " + webServer->arg ( i ) + "\n"; } - webServer->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - webServer->sendHeader("Pragma", "no-cache"); - webServer->sendHeader("Expires", "-1"); - webServer->send(404, "text/plain", message); + 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); } } @@ -1585,8 +1581,8 @@ boolean captivePortal() if ((HTTP_MANAGER == _httpflag) && !isIp(webServer->hostHeader())) { addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Request redirected to captive portal")); - webServer->sendHeader("Location", String("http://") + webServer->client().localIP().toString(), true); - webServer->send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. + 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->client().stop(); // Stop is needed because we sent no content length return true; } diff --git a/sonoff/xdrv_wemohue.ino b/sonoff/xdrv_wemohue.ino index 20c564d30..f80b4434a 100644 --- a/sonoff/xdrv_wemohue.ino +++ b/sonoff/xdrv_wemohue.ino @@ -212,7 +212,10 @@ void pollUDP() packetBuffer[len] = 0; } String request = packetBuffer; + +// addLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet received")); // 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)) { wemo_respondToMSearch(); @@ -354,15 +357,13 @@ void handleUPnPevent() if (request.indexOf("State>0 0) { do_cmnd_power(1, 0); } - webServer->send(200, "text/plain", ""); + webServer->send(200, F("text/plain"), ""); } void handleUPnPservice() { addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Handle WeMo event service")); - - String eventservice_xml = FPSTR(WEMO_EVENTSERVICE_XML); - webServer->send(200, "text/plain", eventservice_xml); + webServer->send_P(200, PSTR("text/plain"), WEMO_EVENTSERVICE_XML); } void handleUPnPsetupWemo() @@ -373,7 +374,7 @@ void handleUPnPsetupWemo() 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); + webServer->send(200, F("text/xml"), setup_xml); } /********************************************************************************************/ @@ -393,7 +394,7 @@ void handleUPnPsetupHue() String description_xml = FPSTR(HUE_DESCRIPTION_XML); description_xml.replace("{x1}", WiFi.localIP().toString()); description_xml.replace("{x2}", hue_UUID()); - webServer->send(200, "text/xml", description_xml); + webServer->send(200, F("text/xml"), description_xml); } void hue_todo(String *path) @@ -445,7 +446,7 @@ void hue_global_cfg(String *path) hue_config_response(&response); response.replace("{id}", *path); response += "}"; - webServer->send(200, "application/json", response); + webServer->send(200, F("application/json"), response); } void hue_auth(String *path) @@ -453,7 +454,7 @@ void hue_auth(String *path) char response[38]; snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%03x\"}}]"), ESP.getChipId()); - webServer->send(200, "application/json", response); + webServer->send(200, F("application/json"), response); } void hue_config(String *path) @@ -463,7 +464,7 @@ void hue_config(String *path) path->remove(0,1); // cut leading / to get hue_config_response(&response); response.replace("{id}", *path); - webServer->send(200, "application/json", response); + webServer->send(200, F("application/json"), response); } void hue_lights(String *path) @@ -506,7 +507,7 @@ void hue_lights(String *path) } } response += "}"; - webServer->send(200, "application/json", response); + webServer->send(200, F("application/json"), response); } else if (path->endsWith("/state")) { // Got ID/state path->remove(0,8); // Remove /lights/ @@ -582,7 +583,7 @@ void hue_lights(String *path) else { response=FPSTR(HUE_ERROR_JSON); } - webServer->send(200, "application/json", response); + webServer->send(200, F("application/json"), response); } else if(path->indexOf("/lights/") >= 0) { // Got /lights/ID path->remove(0,8); // Remove /lights/ @@ -603,9 +604,9 @@ void hue_lights(String *path) response.replace("{s}", "0"); response.replace("{b}", "0"); } - webServer->send(200, "application/json", response); + webServer->send(200, F("application/json"), response); } - else webServer->send(406, "application/json", "{}"); + else webServer->send(406, F("application/json"), "{}"); } void handle_hue_api(String *path)