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)
This commit is contained in:
arendst 2017-02-17 17:18:41 +01:00
parent 381bb4b50c
commit edca508f57
8 changed files with 86 additions and 76 deletions

View File

@ -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```.

Binary file not shown.

View File

@ -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)

View File

@ -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 <ESP8266HTTPClient.h> // MQTT, Ota
#include <ESP8266httpUpdate.h> // Ota
#include <PubSubClient.h> // MQTT
#include <ArduinoJson.h> // WemoHue, IRremote, Domoticz
#ifdef USE_WEBSERVER
#include <ESP8266WebServer.h> // WifiManager, Webserver
#include <DNSServer.h> // WifiManager
@ -154,12 +155,6 @@ enum butt_t {PRESSED, NOT_PRESSED};
#ifdef USE_I2C
#include <Wire.h> // I2C support library
#endif // USE_I2C
#if defined USE_EMULATION || defined USE_IR_REMOTE
#include <ArduinoJson.h>
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));

View File

@ -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
},

View File

@ -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 =
"</div>"
"<div id='f2' name='f2' style='display:none;text-align:center;'><b>Upload started ...</b></div>";
const char HTTP_FORM_CMND[] PROGMEM =
"<br/><textarea readonly id='t1' name='t1' cols='80' wrap='off'></textarea><br/><br/>"
"<br/><textarea readonly id='t1' name='t1' cols='99' wrap='off'></textarea><br/><br/>"
"<form method='post' action='cs'>"
"<input style='width:98%' id='c1' name='c1' length=80 placeholder='Enter command' autofocus><br/>"
"<input style='width:98%' id='c1' name='c1' length='99' placeholder='Enter command' autofocus><br/>"
// "<br/><button type='submit'>Send command</button>"
"</form>";
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("<body>", "<body onload='u()'>");
@ -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("<br/></fieldset>");
#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("<tr><th>Friendly name ");
page += i +1;
page += F("</th><td>"); page += String(sysCfg.friendlyname[i]); page += F("</td></tr>");
page += F("</th><td>"); page += sysCfg.friendlyname[i]; page += F("</td></tr>");
}
page += F("<tr><td>&nbsp;</td></tr>");
// page += F("<tr><th>SSId (RSSI)</th><td>"); page += (sysCfg.sta_active)? sysCfg.sta_ssid2 : sysCfg.sta_ssid1; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)</td></tr>");
@ -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)

View File

@ -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"),

View File

@ -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 {