diff --git a/README.md b/README.md index ee5810873..707214aae 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.21** - See ```sonoff/_releasenotes.ino``` for change information. +Current version is **3.9.22** - 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 d2fb0d14d..fed593281 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 0bcd5e4d5..29f7f4c19 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,14 @@ -/* 3.9.21 20170224 +/* 3.9.22 20170228 + * Update web console + * Fix Status 4 JSON message + * Add Exception info during restart if available + * Add osWatch service to detect loop hangs that might happen during (OTA) upgrades + * Add WiOn support for relay and switch only (#82, #102) + * Allow for user specified relay count up to four in sonoff_template.h (#109) + * Add support for HTU21 compatible I2C sensors SI7013, SI7020 and SI7021 (#118) + * Add NodeMCU or Wemos configuration option (#119) + * + * 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 diff --git a/sonoff/settings.h b/sonoff/settings.h index 55ee022c4..c0aced8e8 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -189,3 +189,10 @@ struct SYSCFG { } sysCfg; +struct RTCMEM { + uint16_t valid; + byte osw_flag; + byte nu1; + unsigned long hlw_kWhtoday; +} rtcMem; + diff --git a/sonoff/settings.ino b/sonoff/settings.ino index 9112638a2..f94567546 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -23,6 +23,85 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/*********************************************************************************************\ + * RTC memory +\*********************************************************************************************/ + +#define RTC_MEM_VALID 0xA55A + +uint32_t _rtcHash = 0; + +uint32_t getRtcHash() +{ + uint32_t hash = 0; + uint8_t *bytes = (uint8_t*)&rtcMem; + + for (uint16_t i = 0; i < sizeof(RTCMEM); i++) hash += bytes[i]*(i+1); + return hash; +} + +void RTC_Save() +{ + if (getRtcHash() != _rtcHash) { + rtcMem.valid = RTC_MEM_VALID; + ESP.rtcUserMemoryWrite(100, (uint32_t*)&rtcMem, sizeof(RTCMEM)); + _rtcHash = getRtcHash(); +#ifdef DEBUG_THEO + addLog_P(LOG_LEVEL_DEBUG, PSTR("Dump: Save")); + RTC_Dump(); +#endif // DEBUG_THEO + } +} + +void RTC_Load() +{ + ESP.rtcUserMemoryRead(100, (uint32_t*)&rtcMem, sizeof(RTCMEM)); +#ifdef DEBUG_THEO + addLog_P(LOG_LEVEL_DEBUG, PSTR("Dump: Load")); + RTC_Dump(); +#endif // DEBUG_THEO + if (rtcMem.valid != RTC_MEM_VALID) { + memset(&rtcMem, 0x00, sizeof(RTCMEM)); + rtcMem.valid = RTC_MEM_VALID; + RTC_Save(); + } + _rtcHash = getRtcHash(); +} + +boolean RTC_Valid() +{ + return (rtcMem.valid == RTC_MEM_VALID); +} + +#ifdef DEBUG_THEO +void RTC_Dump() +{ + #define CFG_COLS 16 + + char log[LOGSZ]; + uint16_t idx, maxrow, row, col; + + uint8_t *buffer = (uint8_t *) &rtcMem; + maxrow = ((sizeof(RTCMEM)+CFG_COLS)/CFG_COLS); + + for (row = 0; row < maxrow; row++) { + idx = row * CFG_COLS; + snprintf_P(log, sizeof(log), PSTR("%04X:"), idx); + for (col = 0; col < CFG_COLS; col++) { + if (!(col%4)) snprintf_P(log, sizeof(log), PSTR("%s "), log); + snprintf_P(log, sizeof(log), PSTR("%s %02X"), log, buffer[idx + col]); + } + snprintf_P(log, sizeof(log), PSTR("%s |"), log); + for (col = 0; col < CFG_COLS; col++) { +// if (!(col%4)) snprintf_P(log, sizeof(log), PSTR("%s "), log); + snprintf_P(log, sizeof(log), PSTR("%s%c"), log, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' '); + } + snprintf_P(log, sizeof(log), PSTR("%s|"), log); + addLog(LOG_LEVEL_INFO, log); + } +} +#endif // DEBUG_THEO + /*********************************************************************************************\ * Config - Flash or Spiffs \*********************************************************************************************/ @@ -131,6 +210,7 @@ void CFG_Save() } _cfgHash = getHash(); } + RTC_Save(); } void CFG_Load() @@ -177,6 +257,8 @@ void CFG_Load() } } _cfgHash = getHash(); + + RTC_Load(); } void CFG_Migrate() diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 1b6fc9c7d..0ac0d0473 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -10,7 +10,7 @@ * ==================================================== */ -#define VERSION 0x03091500 // 3.9.21 +#define VERSION 0x03091600 // 3.9.22 //#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 @@ -45,6 +45,7 @@ enum emul_t {EMUL_NONE, EMUL_WEMO, EMUL_HUE, EMUL_MAX}; #ifndef USE_DS18x20 #define USE_DS18B20 // Default DS18B20 sensor needs no external library #endif +//#define DEBUG_THEO // Add debug code #ifdef BE_MINIMAL //#ifdef USE_MQTT_TLS @@ -80,6 +81,9 @@ enum emul_t {EMUL_NONE, EMUL_WEMO, EMUL_HUE, EMUL_MAX}; #ifdef USE_IR_REMOTE #undef USE_IR_REMOTE // Disable IR driver #endif +#ifdef DEBUG_THEO +#undef DEBUG_THEO // Disable debug code +#endif #endif // BE_MINIMAL #ifndef SWITCH_MODE @@ -497,6 +501,9 @@ void mqtt_connected() mqtt_publish_topic_P(1, PSTR("INFO2"), svalue); } #endif // USE_WEBSERVER + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Started\":\"%s\"}"), + (getResetReason() == "Exception") ? ESP.getResetInfo().c_str() : getResetReason().c_str()); + mqtt_publish_topic_P(1, PSTR("INFO3"), svalue); if (sysCfg.mqtt_enabled && (MQTT_MAX_PACKET_SIZE < (TOPSZ+MESSZ))) { snprintf_P(svalue, sizeof(svalue), PSTR("{\"Warning1\":\"Change MQTT_MAX_PACKET_SIZE in libraries/PubSubClient.h to at least %d\"}"), TOPSZ+MESSZ); mqtt_publish_topic_P(1, PSTR("WARNING1"), svalue); @@ -1167,7 +1174,13 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) else if ((pin[GPIO_IRSEND] < 99) && ir_send_command(type, index, dataBufUc, data_len, payload, svalue, sizeof(svalue))) { // Serviced } -#endif // USE_IR_REMOTE +#endif // USE_IR_REMOTE +#ifdef DEBUG_THEO + else if (!strcmp(type,"EXCEPTION")) { + if (data_len > 0) exception_tst(payload); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Exception\":\"Triggered\"}")); + } +#endif // DEBUG_THEO else { type = NULL; } @@ -1369,7 +1382,7 @@ void publish_status(uint8_t payload) } if ((payload == 0) || (payload == 4)) { - snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusMEM\":{\"ProgramSize\":%d, \"Free\":%d, \"Heap\":%d, \"SpiffsStart\":%d, \"SpiffsSize\":%d, \"FlashSize\":%d, \"ProgramFlashSize\":%d, \"FlashChipMode\",%d}}"), + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusMEM\":{\"ProgramSize\":%d, \"Free\":%d, \"Heap\":%d, \"SpiffsStart\":%d, \"SpiffsSize\":%d, \"FlashSize\":%d, \"ProgramFlashSize\":%d, \"FlashChipMode\":%d}}"), ESP.getSketchSize()/1024, ESP.getFreeSketchSpace()/1024, ESP.getFreeHeap()/1024, ((uint32_t)&_SPIFFS_start - 0x40200000)/1024, (((uint32_t)&_SPIFFS_end - 0x40200000) - ((uint32_t)&_SPIFFS_start - 0x40200000))/1024, ESP.getFlashChipRealSize()/1024, ESP.getFlashChipSize()/1024, ESP.getFlashChipMode()); mqtt_publish_topic_P(option, PSTR("STATUS4"), svalue); @@ -1916,20 +1929,12 @@ void GPIO_init() } Maxdevice = 1; - switch (sysCfg.module) { - case SONOFF_DUAL: - case ELECTRODRAGON: - Maxdevice = 2; - break; - case SONOFF_4CH: - case CH4: - Maxdevice = 4; - break; + if (sysCfg.module == SONOFF_DUAL) { + Maxdevice = 2; + Baudrate = 19200; } - - swt_flg = ((pin[GPIO_SWT1] < 99) || (pin[GPIO_SWT2] < 99) || (pin[GPIO_SWT3] < 99) || (pin[GPIO_SWT4] < 99)); - - if ((sysCfg.module == SONOFF_DUAL) || (sysCfg.module == CH4)) { + else if (sysCfg.module == CH4) { + Maxdevice = 4; Baudrate = 19200; } else if (sysCfg.module == SONOFF_LED) { @@ -1937,9 +1942,13 @@ void GPIO_init() sl_init(); } else { + Maxdevice = 0; for (byte i = 0; i < 4; i++) { + if (pin[GPIO_REL1 +i] < 99) { + pinMode(pin[GPIO_REL1 +i], OUTPUT); + Maxdevice++; + } if (pin[GPIO_KEY1 +i] < 99) pinMode(pin[GPIO_KEY1 +i], INPUT_PULLUP); - if (pin[GPIO_REL1 +i] < 99) pinMode(pin[GPIO_REL1 +i], OUTPUT); } } for (byte i = 0; i < 4; i++) { @@ -1948,6 +1957,7 @@ void GPIO_init() digitalWrite(pin[GPIO_LED1 +i], led_inverted[i]); } if (pin[GPIO_SWT1 +i] < 99) { + swt_flg = 1; pinMode(pin[GPIO_SWT1 +i], INPUT_PULLUP); lastwallswitch[i] = digitalRead(pin[GPIO_SWT1 +i]); // set global now so doesn't change the saved power state on first switch check } @@ -2007,6 +2017,8 @@ void setup() CFG_Load(); CFG_Delta(); + osw_init(); + sysCfg.bootcount++; snprintf_P(log, sizeof(log), PSTR("APP: Bootcount %d"), sysCfg.bootcount); addLog(LOG_LEVEL_DEBUG, log); @@ -2073,6 +2085,8 @@ void setup() void loop() { + osw_loop(); + #ifdef USE_WEBSERVER pollDnsWeb(); #endif // USE_WEBSERVER diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index 598992632..960e00682 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -90,6 +90,8 @@ enum module_t { MOTOR, ELECTRODRAGON, EXS_RELAY, + WION, + NODEMCU, USER_TEST, MAXMODULE }; @@ -283,6 +285,34 @@ const mytmplt modules[MAXMODULE] PROGMEM = { 0, GPIO_USER // GPIO16 Module Pin 4 }, + { "WiOn", // Indoor Tap https://www.amazon.com/gp/product/B00ZYLUBJU/ref=s9_acsd_al_bw_c_x_3_w + GPIO_USER, // GPIO00 Optional sensor (pm clock) + 0, + GPIO_LED1, // GPIO02 Green Led (1 = On, 0 = Off) + 0, 0, 0, 0, 0, 0, 0, 0, 0, + GPIO_USER, // GPIO12 Optional sensor (pm data) + GPIO_KEY1, // GPIO13 Button + 0, + GPIO_REL1, // GPIO15 Relay (0 = Off, 1 = On) + 0 + }, + { "NodeMCU", // NodeMCU and Wemos hardware + GPIO_KEY1, // GPIO00 Button + GPIO_USER, // GPIO01 Serial RXD + GPIO_USER, // GPIO02 + GPIO_USER, // GPIO03 Serial TXD and Optional sensor + GPIO_USER, // GPIO04 Optional sensor + GPIO_USER, // GPIO05 + 0, 0, 0, + GPIO_USER, // GPIO09 + GPIO_USER, // GPIO10 + 0, + GPIO_USER, // GPIO12 + GPIO_USER, // GPIO13 + GPIO_USER, // GPIO14 + GPIO_USER, // GPIO15 + 0 + }, { "User Test", // Sonoff Basic User Test GPIO_KEY1, // GPIO00 Button 0, diff --git a/sonoff/support.ino b/sonoff/support.ino index a13c74447..ecbd50b52 100644 --- a/sonoff/support.ino +++ b/sonoff/support.ino @@ -23,6 +23,127 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/*********************************************************************************************\ + * Watchdog extension (https://github.com/esp8266/Arduino/issues/1532) +\*********************************************************************************************/ + +Ticker tickerOSWatch; + +#define OSWATCH_RESET_TIME 30 + +static unsigned long osw_last_loop; +byte osw_flag = 0; + +void ICACHE_RAM_ATTR osw_osWatch(void) +{ + unsigned long t = millis(); + unsigned long last_run = abs(t - osw_last_loop); + +#ifdef DEBUG_THEO + char log[LOGSZ]; + snprintf_P(log, sizeof(log), PSTR("osWatch: FreeRam %d, rssi %d, last_run %d"), ESP.getFreeHeap(), WIFI_getRSSIasQuality(WiFi.RSSI()), last_run); + addLog(LOG_LEVEL_DEBUG, log); +#endif // DEBUG_THEO + if(last_run >= (OSWATCH_RESET_TIME * 1000)) { + addLog_P(LOG_LEVEL_INFO, PSTR("osWatch: Warning, loop blocked. Restart now")); + rtcMem.osw_flag = 1; + RTC_Save(); +// ESP.restart(); // normal reboot + ESP.reset(); // hard reset + } +} + +void osw_init() +{ + osw_flag = rtcMem.osw_flag; + rtcMem.osw_flag = 0; + osw_last_loop = millis(); + tickerOSWatch.attach_ms(((OSWATCH_RESET_TIME / 3) * 1000), osw_osWatch); +} + +void osw_loop() +{ + osw_last_loop = millis(); +// while(1) delay(1000); // this will trigger the os watch +} + +String getResetReason() +{ + char buff[32]; + if (osw_flag) { + strcpy_P(buff, PSTR("Blocked Loop")); + return String(buff); + } else { + return ESP.getResetReason(); + } +} + +#ifdef DEBUG_THEO +void exception_tst(byte type) +{ +/* +Exception (28): +epc1=0x4000bf64 epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000007 depc=0x00000000 + +ctx: cont +sp: 3fff1f30 end: 3fff2840 offset: 01a0 + +>>>stack>>> +3fff20d0: 202c3573 756f7247 2c302070 646e4920 +3fff20e0: 40236a6e 7954202c 45206570 00454358 +3fff20f0: 00000010 00000007 00000000 3fff2180 +3fff2100: 3fff2190 40107bfc 3fff3e4c 3fff22c0 +3fff2110: 40261934 000000f0 3fff22c0 401004d8 +3fff2120: 40238fcf 00000050 3fff2100 4021fc10 +3fff2130: 3fff32bc 4021680c 3ffeade1 4021ff7d +3fff2140: 3fff2190 3fff2180 0000000c 7fffffff +3fff2150: 00000019 00000000 00000000 3fff21c0 +3fff2160: 3fff23f3 3ffe8e08 00000000 4021ffb4 +3fff2170: 3fff2190 3fff2180 0000000c 40201118 +3fff2180: 3fff21c0 0000003c 3ffef840 00000007 +3fff2190: 00000000 00000000 00000000 40201128 +3fff21a0: 3fff23f3 000000f1 3fff23ec 4020fafb +3fff21b0: 3fff23f3 3fff21c0 3fff21d0 3fff23f6 +3fff21c0: 00000000 3fff23fb 4022321b 00000000 + +Exception 28: LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads +Decoding 14 results +0x40236a6e: ets_vsnprintf at ?? line ? +0x40107bfc: vsnprintf at C:\Data2\Arduino\arduino-1.8.1-esp-2.3.0\portable\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/libc_replacements.c line 387 +0x40261934: bignum_exptmod at ?? line ? +0x401004d8: malloc at C:\Data2\Arduino\arduino-1.8.1-esp-2.3.0\portable\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266\umm_malloc/umm_malloc.c line 1664 +0x40238fcf: wifi_station_get_connect_status at ?? line ? +0x4021fc10: operator new[](unsigned int) at C:\Data2\Arduino\arduino-1.8.1-esp-2.3.0\portable\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/abi.cpp line 57 +0x4021680c: ESP8266WiFiSTAClass::status() at C:\Data2\Arduino\arduino-1.8.1-esp-2.3.0\portable\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WiFi\src/ESP8266WiFiSTA.cpp line 569 +0x4021ff7d: vsnprintf_P(char*, unsigned int, char const*, __va_list_tag) at C:\Data2\Arduino\arduino-1.8.1-esp-2.3.0\portable\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/pgmspace.cpp line 146 +0x4021ffb4: snprintf_P(char*, unsigned int, char const*, ...) at C:\Data2\Arduino\arduino-1.8.1-esp-2.3.0\portable\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/pgmspace.cpp line 146 +0x40201118: atol at C:\Data2\Arduino\arduino-1.8.1-esp-2.3.0\portable\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/core_esp8266_noniso.c line 45 +0x40201128: atoi at C:\Data2\Arduino\arduino-1.8.1-esp-2.3.0\portable\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/core_esp8266_noniso.c line 45 +0x4020fafb: mqttDataCb(char*, unsigned char*, unsigned int) at R:\Arduino\Work-ESP8266\Theo\sonoff\sonoff-4\sonoff/sonoff.ino line 679 (discriminator 1) +0x4022321b: pp_attach at ?? line ? + +00:00:08 MQTT: tele/sonoff/INFO3 = {"Started":"Fatal exception:28 flag:2 (EXCEPTION) epc1:0x4000bf64 epc2:0x00000000 epc3:0x00000000 excvaddr:0x00000007 depc:0x00000000"} +*/ + if (type == 1) { + char svalue[10]; + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), 7); // Exception 28 as number in string (7 in excvaddr) + } +/* +14:50:52 osWatch: FreeRam 25896, rssi 68, last_run 0 +14:51:02 osWatch: FreeRam 25896, rssi 58, last_run 0 +14:51:03 CMND: exception 2 +14:51:12 osWatch: FreeRam 25360, rssi 60, last_run 8771 +14:51:22 osWatch: FreeRam 25360, rssi 62, last_run 18771 +14:51:32 osWatch: FreeRam 25360, rssi 62, last_run 28771 +14:51:42 osWatch: FreeRam 25360, rssi 62, last_run 38771 +14:51:42 osWatch: Warning, loop blocked. Restart now +*/ + if (type == 2) { + while(1) delay(1000); // this will trigger the os watch + } +} +#endif // DEBUG_THEO + /*********************************************************************************************\ * Wifi \*********************************************************************************************/ @@ -703,9 +824,6 @@ void addLog(byte loglevel, const char *line) snprintf_P(mxtime, sizeof(mxtime), PSTR("%02d:%02d:%02d"), rtcTime.Hour, rtcTime.Minute, rtcTime.Second); -#ifdef DEBUG_ESP_PORT - DEBUG_ESP_PORT.printf("%s %s\n", mxtime, line); -#endif // DEBUG_ESP_PORT if (loglevel <= seriallog_level) Serial.printf("%s %s\n", mxtime, line); #ifdef USE_WEBSERVER if (sysCfg.webserver && (loglevel <= sysCfg.weblog_level)) { diff --git a/sonoff/user_config.h b/sonoff/user_config.h index 0581ad6ed..032f72613 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -124,7 +124,7 @@ #define USE_I2C // I2C using library wire (+10k code, 0.2k mem) - Disable by // #define USE_BH1750 // Add I2C code for BH1750 sensor #define USE_BMP // Add I2C code for BMP/BME280 sensor - #define USE_HTU // Add I2C code for HTU21 sensor + #define USE_HTU // Add I2C code for HTU21/SI7013/SI7020/SI7021 sensor #define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0.3k mem) diff --git a/sonoff/webserver.ino b/sonoff/webserver.ino index 83233d952..3eba1f96f 100644 --- a/sonoff/webserver.ino +++ b/sonoff/webserver.ino @@ -70,13 +70,22 @@ const char HTTP_HEAD[] PROGMEM = "};" "x.open('GET','ay'+a,true);" "x.send();" - "lt=setTimeout(la,2000);" + "lt=setTimeout(la,2345);" "}" "var sn=0;" // Scroll position "var id=99;" // Get most of weblog initially - "function l(){" - "var e=document.getElementById('t1');" - "if(e.scrollTop>=sn){" // User scrolled back so no updates + "function l(p){" // Console log and command service + "var c,o,t;" + "clearTimeout(lt);" + "o='';" + "t=document.getElementById('t1');" + "if(p==1){" + "c=document.getElementById('c1');" + "o='&c1='+c.value;" + "c.value='';" + "t.scrollTop=sn;" + "}" + "if(t.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(){" @@ -84,25 +93,17 @@ const char HTTP_HEAD[] PROGMEM = "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=\"\";}" + "if(d.getElementsByTagName('j')[0].childNodes[0].nodeValue==0){t.value='';}" "z=d.getElementsByTagName('l')[0].childNodes;" - "if(z.length>0){e.value+=z[0].nodeValue;}" - "e.scrollTop=99999;" - "sn=e.scrollTop;" + "if(z.length>0){t.value+=z[0].nodeValue;}" + "t.scrollTop=99999;" + "sn=t.scrollTop;" "}" "};" - "x.open('GET','ax?c2='+id,true);" + "x.open('GET','ax?c2='+id+o,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=\"\";" + "lt=setTimeout(l,2345);" "return false;" "}" "" @@ -122,7 +123,7 @@ const char HTTP_HEAD[] PROGMEM = "" "" "" - "
" + "
" "

{ha} Module

{h}

"; const char HTTP_MSG_RSTRT[] PROGMEM = "
Device will restart in a few seconds

"; @@ -234,12 +235,18 @@ const char HTTP_FORM_UPG[] PROGMEM = ""; const char HTTP_FORM_CMND[] PROGMEM = "


" - "
" + "" "
" // "
" "
"; const char HTTP_COUNTER[] PROGMEM = "
"; +const char HTTP_SNS_TEMP[] PROGMEM = + "%s Temperature%s°%c"; +const char HTTP_SNS_HUM[] PROGMEM = + "%s Humidity%s%"; +const char HTTP_SNS_PRESSURE[] PROGMEM = + "%s Pressure%s hPa"; const char HTTP_END[] PROGMEM = "
" "" @@ -1168,7 +1175,7 @@ void handleAjax() { if (httpUser()) return; char svalue[MESSZ], log[LOGSZ]; - byte counter = 99; + byte cflg = 1, counter = 99; if (strlen(webServer->arg("c1").c_str())) { snprintf_P(svalue, sizeof(svalue), webServer->arg("c1").c_str()); @@ -1189,10 +1196,13 @@ void handleAjax() } message += F(""); if (counter != logidx) { - if (counter == 99) counter = logidx; + if (counter == 99) { + counter = logidx; + cflg = 0; + } do { if (Log[counter].length()) { - message += F("\n"); + if (cflg) message += F("\n"); else cflg = 1; message += Log[counter]; } counter++; @@ -1227,7 +1237,7 @@ void handleInfo() page += F("Uptime"); page += String(uptime); page += F(" Hours"); page += F("Flash write count"); page += String(sysCfg.saveFlag); page += F(""); page += F("Boot count"); page += String(sysCfg.bootcount); page += F(""); - page += F("Reset reason"); page += ESP.getResetReason(); page += F(""); + page += F("Reset reason"); page += getResetReason(); page += F(""); for (byte i = 0; i < Maxdevice; i++) { page += F("Friendly name "); page += i +1; diff --git a/sonoff/xsns_bh1750.ino b/sonoff/xsns_bh1750.ino index 603fd9805..93292d60a 100644 --- a/sonoff/xsns_bh1750.ino +++ b/sonoff/xsns_bh1750.ino @@ -67,7 +67,7 @@ boolean bh1750_detect() if (!status) { success = true; bh1750type = 1; - snprintf_P(bh1750stype, sizeof(bh1750stype), PSTR("BH1750")); + strcpy(bh1750stype, "BH1750"); } if (success) { snprintf_P(log, sizeof(log), PSTR("I2C: %s found at address 0x%x"), bh1750stype, bh1750addr); @@ -95,12 +95,16 @@ void bh1750_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) } #ifdef USE_WEBSERVER +const char HTTP_SNS_ILLUMINANCE[] PROGMEM = + "BH1750 Illuminance%d lx"; + String bh1750_webPresent() { String page = ""; if (bh1750type) { - uint16_t l = bh1750_readLux(); - page += F("Illuminance: "); page += String(l); page += F(" lx"); + char sensor[80]; + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_ILLUMINANCE, bh1750_readLux()); + page += sensor; } return page; } diff --git a/sonoff/xsns_bmp.ino b/sonoff/xsns_bmp.ino index 4ac3d7d17..1bc472222 100644 --- a/sonoff/xsns_bmp.ino +++ b/sonoff/xsns_bmp.ino @@ -397,19 +397,19 @@ boolean bmp_detect() bmpaddr--; bmptype = i2c_read8(bmpaddr, BMP_REGISTER_CHIPID); } - snprintf_P(bmpstype, sizeof(bmpstype), PSTR("BMP")); + strcpy_P(bmpstype, PSTR("BMP")); switch (bmptype) { case BMP180_CHIPID: success = bmp180_calibration(); - snprintf_P(bmpstype, sizeof(bmpstype), PSTR("BMP180")); + strcpy_P(bmpstype, PSTR("BMP180")); break; case BMP280_CHIPID: success = bmp280_calibrate(); - snprintf_P(bmpstype, sizeof(bmpstype), PSTR("BMP280")); + strcpy_P(bmpstype, PSTR("BMP280")); break; case BME280_CHIPID: success = bme280_calibrate(); - snprintf_P(bmpstype, sizeof(bmpstype), PSTR("BME280")); + strcpy_P(bmpstype, PSTR("BME280")); } if (success) { snprintf_P(log, sizeof(log), PSTR("I2C: %s found at address 0x%x"), bmpstype, bmpaddr); @@ -454,20 +454,22 @@ String bmp_webPresent() { String page = ""; if (bmptype) { - char itemp[10], iconv[10]; + char stemp[10], sensor[80]; - snprintf_P(iconv, sizeof(iconv), PSTR("°%c"), (TEMP_CONVERSION) ? 'F' : 'C'); double t_bmp = bmp_readTemperature(TEMP_CONVERSION); double p_bmp = bmp_readPressure(); double h_bmp = bmp_readHumidity(); - dtostrf(t_bmp, 1, TEMP_RESOLUTION &3, itemp); - page += F("BMP Temperature: "); page += itemp; page += iconv; page += F(""); + dtostrf(t_bmp, 1, TEMP_RESOLUTION &3, stemp); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_TEMP, bmpstype, stemp, (TEMP_CONVERSION) ? 'F' : 'C'); + page += sensor; if (!strcmp(bmpstype,"BME280")) { - dtostrf(h_bmp, 1, HUMIDITY_RESOLUTION &3, itemp); - page += F("BMP Humidity: "); page += itemp; page += F("%"); + dtostrf(h_bmp, 1, HUMIDITY_RESOLUTION &3, stemp); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_HUM, bmpstype, stemp); + page += sensor; } - dtostrf(p_bmp, 1, PRESSURE_RESOLUTION &3, itemp); - page += F("BMP Pressure: "); page += itemp; page += F(" hPa"); + dtostrf(p_bmp, 1, PRESSURE_RESOLUTION &3, stemp); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_PRESSURE, bmpstype, stemp); + page += sensor; } return page; } diff --git a/sonoff/xsns_dht.ino b/sonoff/xsns_dht.ino index e1b9f9793..5b85e18a1 100644 --- a/sonoff/xsns_dht.ino +++ b/sonoff/xsns_dht.ino @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #define MIN_INTERVAL 2000 uint8_t data[5]; +char dhtstype[7]; uint32_t _lastreadtime, _maxcycles; bool _lastresult; float mt, mh = 0; @@ -170,6 +171,17 @@ void dht_init() pinMode(pin[GPIO_DHT11], INPUT_PULLUP); _lastreadtime = -MIN_INTERVAL; + switch (dht_type) { + case GPIO_DHT11: + strcpy(dhtstype, "DHT11"); + break; + case GPIO_DHT21: + strcpy(dhtstype, "AM2301"); + break; + case GPIO_DHT22: + strcpy(dhtstype, "DHT22"); + } + snprintf_P(log, sizeof(log), PSTR("DHT: Max clock cycles %d"), _maxcycles); addLog(LOG_LEVEL_DEBUG, log); } @@ -186,7 +198,8 @@ void dht_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) if (dht_readTempHum(TEMP_CONVERSION, t, h)) { // Read temperature dtostrf(t, 1, TEMP_RESOLUTION &3, stemp1); dtostrf(h, 1, HUMIDITY_RESOLUTION &3, stemp2); - snprintf_P(svalue, ssvalue, PSTR("%s, \"DHT\":{\"Temperature\":%s, \"Humidity\":%s}"), svalue, stemp1, stemp2); + snprintf_P(svalue, ssvalue, PSTR("%s, \"%s\":{\"Temperature\":%s, \"Humidity\":%s}"), + svalue, dhtstype, stemp1, stemp2); *djson = 1; #ifdef USE_DOMOTICZ domoticz_sensor2(stemp1, stemp2); @@ -197,16 +210,18 @@ void dht_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) #ifdef USE_WEBSERVER String dht_webPresent() { - char stemp[10], sconv[10]; - float t, h; String page = ""; + float t, h; if (dht_readTempHum(TEMP_CONVERSION, t, h)) { // Read temperature as Celsius (the default) - snprintf_P(sconv, sizeof(sconv), PSTR("°%c"), (TEMP_CONVERSION) ? 'F' : 'C'); + char stemp[10], sensor[128]; dtostrf(t, 1, TEMP_RESOLUTION &3, stemp); - page += F("DHT Temperature: "); page += stemp; page += sconv; page += F(""); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_TEMP, dhtstype, stemp, (TEMP_CONVERSION) ? 'F' : 'C'); + page += sensor; dtostrf(h, 1, HUMIDITY_RESOLUTION &3, stemp); - page += F("DHT Humidity: "); page += stemp; page += F("%"); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_HUM, dhtstype, stemp); + page += sensor; + } return page; } diff --git a/sonoff/xsns_ds18b20.ino b/sonoff/xsns_ds18b20.ino index 18375531e..13fbc0053 100644 --- a/sonoff/xsns_ds18b20.ino +++ b/sonoff/xsns_ds18b20.ino @@ -191,14 +191,14 @@ void dsb_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) String dsb_webPresent() { // Needs TelePeriod to refresh data (Do not do it here as it takes too much time) - char stemp[10], sconv[10]; - float st; String page = ""; + float st; if (dsb_readTemp(TEMP_CONVERSION, st)) { // Check if read failed - snprintf_P(sconv, sizeof(sconv), PSTR("°%c"), (TEMP_CONVERSION) ? 'F' : 'C'); + char stemp[10], sensor[80]; dtostrf(st, 1, TEMP_RESOLUTION &3, stemp); - page += F("DSB Temperature: "); page += stemp; page += sconv; page += F(""); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_TEMP, "DS18B20", stemp, (TEMP_CONVERSION) ? 'F' : 'C'); + page += sensor; } return page; } diff --git a/sonoff/xsns_ds18x20.ino b/sonoff/xsns_ds18x20.ino index 22fc92654..77cb2c4d0 100644 --- a/sonoff/xsns_ds18x20.ino +++ b/sonoff/xsns_ds18x20.ino @@ -41,6 +41,7 @@ OneWire *ds = NULL; uint8_t ds18x20_addr[DS18X20_MAX_SENSORS][8]; uint8_t ds18x20_idx[DS18X20_MAX_SENSORS]; uint8_t ds18x20_snsrs = 0; +char dsbstype[8]; void ds18x20_init() { @@ -90,23 +91,6 @@ String ds18x20_address(uint8_t sensor) return String(addrStr); } -String ds18x20_type(uint8_t sensor) -{ - char typeStr[10]; - - switch(ds18x20_addr[ds18x20_idx[sensor]][0]) { - case 0x10: - strcpy(typeStr, "DS18S20"); - break; - case 0x28: - strcpy(typeStr, "DS18B20"); - break; - default: - strcpy(typeStr, "DS18x20"); - } - return String(typeStr); -} - void ds18x20_convert() { ds->reset(); @@ -160,6 +144,19 @@ boolean ds18x20_read(uint8_t sensor, bool S, float &t) * Presentation \*********************************************************************************************/ +void ds18x20_type(uint8_t sensor) +{ + strcpy_P(dsbstype, PSTR("DS18x20")); + switch(ds18x20_addr[ds18x20_idx[sensor]][0]) { + case 0x10: + strcpy_P(dsbstype, PSTR("DS18S20")); + break; + case 0x28: + strcpy_P(dsbstype, PSTR("DS18B20")); + break; + } +} + void ds18x20_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) { char stemp1[10], stemp2[10]; @@ -168,6 +165,7 @@ void ds18x20_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) byte dsxflg = 0; for (byte i = 0; i < ds18x20_sensors(); i++) { if (ds18x20_read(i, TEMP_CONVERSION, t)) { // Check if read failed + ds18x20_type(i); dtostrf(t, 1, TEMP_RESOLUTION &3, stemp2); if (!dsxflg) { snprintf_P(svalue, ssvalue, PSTR("%s, \"DS18x20\":{"), svalue); @@ -176,7 +174,7 @@ void ds18x20_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) } dsxflg++; snprintf_P(svalue, ssvalue, PSTR("%s%s\"DS%d\":{\"Type\":\"%s\", \"Address\":\"%s\", \"Temperature\":%s}"), - svalue, stemp1, i +1, ds18x20_type(i).c_str(), ds18x20_address(i).c_str(), stemp2); + svalue, stemp1, i +1, dsbstype, ds18x20_address(i).c_str(), stemp2); strcpy(stemp1, ", "); #ifdef USE_DOMOTICZ if (dsxflg == 1) domoticz_sensor1(stemp2); @@ -189,15 +187,17 @@ void ds18x20_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) #ifdef USE_WEBSERVER String ds18x20_webPresent() { - char stemp[10], sconv[10]; - float t; String page = ""; + char stemp[10], stemp2[16], sensor[80]; + float t; - snprintf_P(sconv, sizeof(sconv), PSTR("°%c"), (TEMP_CONVERSION) ? 'F' : 'C'); for (byte i = 0; i < ds18x20_sensors(); i++) { if (ds18x20_read(i, TEMP_CONVERSION, t)) { // Check if read failed + ds18x20_type(i); dtostrf(t, 1, TEMP_RESOLUTION &3, stemp); - page += F("DS"); page += String(i +1); page += F(" Temperature: "); page += stemp; page += sconv; page += F(""); + snprintf_P(stemp2, sizeof(stemp2), PSTR("%s-%d"), dsbstype, i +1); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_TEMP, stemp2, stemp, (TEMP_CONVERSION) ? 'F' : 'C'); + page += sensor; } } return page; diff --git a/sonoff/xsns_hlw8012.ino b/sonoff/xsns_hlw8012.ino index 3ed5e24c3..95a851f19 100644 --- a/sonoff/xsns_hlw8012.ino +++ b/sonoff/xsns_hlw8012.ino @@ -102,11 +102,13 @@ void hlw_200mS() hlw_EDcntr = 0; hlw_temp = (HLW_PREF * sysCfg.hlw_pcal) / hlw_len; hlw_kWhtoday += (hlw_temp * 100) / 36; + rtcMem.hlw_kWhtoday = hlw_kWhtoday; } if (rtcTime.Valid) { if (rtc_loctime() == rtc_midnight()) { sysCfg.hlw_kWhyesterday = hlw_kWhtoday; hlw_kWhtoday = 0; + rtcMem.hlw_kWhtoday = hlw_kWhtoday; hlw_mkwh_state = 3; } if ((rtcTime.Hour == sysCfg.hlw_mkwhs) && (hlw_mkwh_state == 3)) { @@ -114,6 +116,7 @@ void hlw_200mS() } if (hlw_startup && (rtcTime.DayOfYear == sysCfg.hlw_kWhdoy)) { hlw_kWhtoday = sysCfg.hlw_kWhtoday; + rtcMem.hlw_kWhtoday = hlw_kWhtoday; hlw_startup = 0; } } @@ -236,7 +239,7 @@ void hlw_init() hlw_Ecntr = 0; hlw_EDcntr = 0; - hlw_kWhtoday = 0; + hlw_kWhtoday = (RTC_Valid()) ? rtcMem.hlw_kWhtoday : 0; hlw_SELflag = 0; // Voltage; @@ -540,18 +543,16 @@ void hlw_mqttStat(byte option, char* svalue, uint16_t ssvalue) void hlw_mqttPresent() { - char svalue[MESSZ], stime[21]; + char svalue[MESSZ]; - snprintf_P(stime, sizeof(stime), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Time\":\"%04d-%02d-%02dT%02d:%02d:%02d\", "), rtcTime.Year, rtcTime.Month, rtcTime.Day, rtcTime.Hour, rtcTime.Minute, rtcTime.Second); - snprintf_P(svalue, sizeof(svalue), PSTR("{\"Time\":\"%s\", "), stime); hlw_mqttStat(1, svalue, sizeof(svalue)); // snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/ENERGY"), PUB_PREFIX2, sysCfg.mqtt_topic); // mqtt_publish(stopic, svalue); mqtt_publish_topic_P(1, PSTR("ENERGY"), svalue); - } void hlw_mqttStatus(char* svalue, uint16_t ssvalue) @@ -562,24 +563,29 @@ void hlw_mqttStatus(char* svalue, uint16_t ssvalue) } #ifdef USE_WEBSERVER +const char HTTP_ENERGY_SNS[] PROGMEM = + "Voltage%d V" + "Current%s A" + "Power%d W" + "Power Factor%s" + "Energy Today%s kWh" + "Energy Yesterday%s kWh"; + String hlw_webPresent() { - char stemp[10]; + String page = ""; + char stemp[10], stemp2[10], stemp3[10], stemp4[10], sensor[300]; float ped, pi, pc; uint16_t pe, pw, pu; - String page = ""; hlw_readEnergy(0, ped, pe, pw, pu, pi, pc); - page += F("Voltage: "); page += String(pu); page += F(" V"); + dtostrf(pi, 1, 3, stemp); - page += F("Current: "); page += stemp; page += F(" A"); - page += F("Power: "); page += String(pw); page += F(" W"); - dtostrf(pc, 1, 2, stemp); - page += F("Power Factor: "); page += stemp; page += F(""); - dtostrf(ped, 1, 3, stemp); - page += F("Energy Today: "); page += stemp; page += F(" kWh"); - dtostrf((float)sysCfg.hlw_kWhyesterday / 100000000, 1, 3, stemp); - page += F("Energy Yesterday: "); page += stemp; page += F(" kWh"); + dtostrf(pc, 1, 2, stemp2); + dtostrf(ped, 1, 3, stemp3); + dtostrf((float)sysCfg.hlw_kWhyesterday / 100000000, 1, 3, stemp4); + snprintf_P(sensor, sizeof(sensor), HTTP_ENERGY_SNS, pu, stemp, pw, stemp2, stemp3, stemp4); + page += sensor; return page; } #endif // USE_WEBSERVER diff --git a/sonoff/xsns_htu21.ino b/sonoff/xsns_htu21.ino index 64ea0cde4..da57baa9e 100644 --- a/sonoff/xsns_htu21.ino +++ b/sonoff/xsns_htu21.ino @@ -33,6 +33,9 @@ #define HTU21_ADDR 0x40 +#define SI7013_CHIPID 0x0D +#define SI7020_CHIPID 0x14 +#define SI7021_CHIPID 0x15 #define HTU21_CHIPID 0x32 #define HTU21_READTEMP 0xE3 @@ -48,17 +51,15 @@ #define HTU21_HEATER_ON 0x04 #define HTU21_HEATER_OFF 0xFB -#define HTU21_RES_RH12_T14 0x00 // Default +#define HTU21_RES_RH12_T14 0x00 // Default #define HTU21_RES_RH8_T12 0x01 #define HTU21_RES_RH10_T13 0x80 #define HTU21_RES_RH11_T11 0x81 -#define HTU21_MAX_HUM 16 // 16ms max time -#define HTU21_MAX_TEMP 50 // 50ms max time - #define HTU21_CRC8_POLYNOM 0x13100 uint8_t htuaddr, htutype = 0; +uint8_t delayT, delayH = 50; char htustype[7]; uint8_t check_crc8(uint16_t data) @@ -149,7 +150,7 @@ float htu21_readHumidity(void) Wire.beginTransmission(HTU21_ADDR); Wire.write(HTU21_READHUM); if(Wire.endTransmission() != 0) return 0.0; // In case of error - delay(HTU21_MAX_HUM); // HTU21 time at max resolution + delay(delayH); // Sensor time at max resolution Wire.requestFrom(HTU21_ADDR, 3); if(3 <= Wire.available()) @@ -163,8 +164,8 @@ float htu21_readHumidity(void) sensorval ^= 0x02; // clear status bits humidity = 0.001907 * (float)sensorval - 6; - if(humidity>100) return 100.0; - if(humidity<0) return 0.01; + if(humidity > 100) return 100.0; + if(humidity < 0) return 0.01; return humidity; } @@ -178,7 +179,7 @@ float htu21_readTemperature(bool S) Wire.beginTransmission(HTU21_ADDR); Wire.write(HTU21_READTEMP); if(Wire.endTransmission() != 0) return 0.0; // In case of error - delay(HTU21_MAX_TEMP); // HTU21 time at max resolution + delay(delayT); // Sensor time at max resolution Wire.requestFrom(HTU21_ADDR, 3); if(3 == Wire.available()) @@ -210,11 +211,32 @@ uint8_t htu_detect() htuaddr = HTU21_ADDR; htutype = htu21_readDeviceID(); - snprintf_P(htustype, sizeof(htustype), PSTR("HTU")); + success = htu21_init(); switch (htutype) { case HTU21_CHIPID: - success = htu21_init(); - snprintf_P(htustype, sizeof(htustype), PSTR("HTU21")); + strcpy_P(htustype, PSTR("HTU21")); + delayT=50; + delayH=16; + break; + case SI7013_CHIPID: + strcpy_P(htustype, PSTR("SI7013")); + delayT=12; + delayH=23; + break; + case SI7020_CHIPID: + strcpy_P(htustype, PSTR("SI7020")); + delayT=12; + delayH=23; + break; + case SI7021_CHIPID: + strcpy_P(htustype, PSTR("SI7021")); + delayT=12; + delayH=23; + break; + default: + strcpy_P(htustype, PSTR("T/RH?")); + delayT=50; + delayH=23; } if (success) { snprintf_P(log, sizeof(log), PSTR("I2C: %s found at address 0x%x"), htustype, htuaddr); @@ -240,7 +262,8 @@ void htu_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) h = htu21_compensatedHumidity(h, t); dtostrf(t, 1, TEMP_RESOLUTION &3, stemp1); dtostrf(h, 1, HUMIDITY_RESOLUTION &3, stemp2); - snprintf_P(svalue, ssvalue, PSTR("%s, \"%s\":{\"Temperature\":%s, \"Humidity\":%s}"), svalue, htustype, stemp1, stemp2); + snprintf_P(svalue, ssvalue, PSTR("%s, \"%s\":{\"Temperature\":%s, \"Humidity\":%s}"), + svalue, htustype, stemp1, stemp2); *djson = 1; #ifdef USE_DOMOTICZ domoticz_sensor2(stemp1, stemp2); @@ -252,16 +275,17 @@ String htu_webPresent() { String page = ""; if (htutype) { - char itemp[10], iconv[10]; + char stemp[10], sensor[128]; - snprintf_P(iconv, sizeof(iconv), PSTR("°%c"), (TEMP_CONVERSION) ? 'F' : 'C'); float t_htu21 = htu21_readTemperature(TEMP_CONVERSION); float h_htu21 = htu21_readHumidity(); h_htu21 = htu21_compensatedHumidity(h_htu21, t_htu21); - dtostrf(t_htu21, 1, TEMP_RESOLUTION &3, itemp); - page += F("HTU Temperature: "); page += itemp; page += iconv; page += F(""); - dtostrf(h_htu21, 1, HUMIDITY_RESOLUTION &3, itemp); - page += F("HTU Humidity: "); page += itemp; page += F("%"); + dtostrf(t_htu21, 1, TEMP_RESOLUTION &3, stemp); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_TEMP, htustype, stemp, (TEMP_CONVERSION) ? 'F' : 'C'); + page += sensor; + dtostrf(h_htu21, 1, HUMIDITY_RESOLUTION &3, stemp); + snprintf_P(sensor, sizeof(sensor), HTTP_SNS_HUM, htustype, stemp); + page += sensor; } return page; }