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)
This commit is contained in:
arendst 2017-02-28 16:01:48 +01:00
parent 3fefb9b931
commit fc3b7e22ab
17 changed files with 450 additions and 128 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.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```.

Binary file not shown.

View File

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

View File

@ -189,3 +189,10 @@ struct SYSCFG {
} sysCfg;
struct RTCMEM {
uint16_t valid;
byte osw_flag;
byte nu1;
unsigned long hlw_kWhtoday;
} rtcMem;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;"
"}"
"</script>"
@ -122,7 +123,7 @@ const char HTTP_HEAD[] PROGMEM =
"</style>"
"</head>"
"<body>"
"<div style='text-align:left;display:inline-block;min-width:260px;'>"
"<div style='text-align:left;display:inline-block;min-width:320px;'>"
"<div style='text-align:center;'><h3>{ha} Module</h3><h2>{h}</h2></div>";
const char HTTP_MSG_RSTRT[] PROGMEM =
"<br/><div style='text-align:center;'>Device will restart in a few seconds</div><br/>";
@ -234,12 +235,18 @@ const char HTTP_FORM_UPG[] PROGMEM =
"<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='99' wrap='off'></textarea><br/><br/>"
"<form method='get' onsubmit='return lc();'>"
"<form method='get' onsubmit='return l(1);'>"
"<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 =
"<br/><div id='t' name='t' style='text-align:center;'></div>";
const char HTTP_SNS_TEMP[] PROGMEM =
"<tr><th>%s Temperature</th><td>%s&deg;%c</td></tr>";
const char HTTP_SNS_HUM[] PROGMEM =
"<tr><th>%s Humidity</th><td>%s%</td></tr>";
const char HTTP_SNS_PRESSURE[] PROGMEM =
"<tr><th>%s Pressure</th><td>%s hPa</td></tr>";
const char HTTP_END[] PROGMEM =
"</div>"
"</body>"
@ -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("</j><l>");
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("<tr><th>Uptime</th><td>"); page += String(uptime); page += F(" Hours</td></tr>");
page += F("<tr><th>Flash write count</th><td>"); page += String(sysCfg.saveFlag); page += F("</td></tr>");
page += F("<tr><th>Boot count</th><td>"); page += String(sysCfg.bootcount); page += F("</td></tr>");
page += F("<tr><th>Reset reason</th><td>"); page += ESP.getResetReason(); page += F("</td></tr>");
page += F("<tr><th>Reset reason</th><td>"); page += getResetReason(); page += F("</td></tr>");
for (byte i = 0; i < Maxdevice; i++) {
page += F("<tr><th>Friendly name ");
page += i +1;

View File

@ -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 =
"<tr><th>BH1750 Illuminance</th><td>%d lx</td></tr>";
String bh1750_webPresent()
{
String page = "";
if (bh1750type) {
uint16_t l = bh1750_readLux();
page += F("<tr><td>Illuminance: </td><td>"); page += String(l); page += F(" lx</td></tr>");
char sensor[80];
snprintf_P(sensor, sizeof(sensor), HTTP_SNS_ILLUMINANCE, bh1750_readLux());
page += sensor;
}
return page;
}

View File

@ -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("&deg;%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("<tr><td>BMP Temperature: </td><td>"); page += itemp; page += iconv; page += F("</td></tr>");
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("<tr><td>BMP Humidity: </td><td>"); page += itemp; page += F("%</td></tr>");
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("<tr><td>BMP Pressure: </td><td>"); page += itemp; page += F(" hPa</td></tr>");
dtostrf(p_bmp, 1, PRESSURE_RESOLUTION &3, stemp);
snprintf_P(sensor, sizeof(sensor), HTTP_SNS_PRESSURE, bmpstype, stemp);
page += sensor;
}
return page;
}

View File

@ -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("&deg;%c"), (TEMP_CONVERSION) ? 'F' : 'C');
char stemp[10], sensor[128];
dtostrf(t, 1, TEMP_RESOLUTION &3, stemp);
page += F("<tr><td>DHT Temperature: </td><td>"); page += stemp; page += sconv; page += F("</td></tr>");
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("<tr><td>DHT Humidity: </td><td>"); page += stemp; page += F("%</td></tr>");
snprintf_P(sensor, sizeof(sensor), HTTP_SNS_HUM, dhtstype, stemp);
page += sensor;
}
return page;
}

View File

@ -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("&deg;%c"), (TEMP_CONVERSION) ? 'F' : 'C');
char stemp[10], sensor[80];
dtostrf(st, 1, TEMP_RESOLUTION &3, stemp);
page += F("<tr><td>DSB Temperature: </td><td>"); page += stemp; page += sconv; page += F("</td></tr>");
snprintf_P(sensor, sizeof(sensor), HTTP_SNS_TEMP, "DS18B20", stemp, (TEMP_CONVERSION) ? 'F' : 'C');
page += sensor;
}
return page;
}

View File

@ -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("&deg;%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("<tr><td>DS"); page += String(i +1); page += F(" Temperature: </td><td>"); page += stemp; page += sconv; page += F("</td></tr>");
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;

View File

@ -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 =
"<tr><th>Voltage</th><td>%d V</td></tr>"
"<tr><th>Current</th><td>%s A</td></tr>"
"<tr><th>Power</th><td>%d W</td></tr>"
"<tr><th>Power Factor</th><td>%s</td></tr>"
"<tr><th>Energy Today</th><td>%s kWh</td></tr>"
"<tr><th>Energy Yesterday</th><td>%s kWh</td></tr>";
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("<tr><td>Voltage: </td><td>"); page += String(pu); page += F(" V</td></tr>");
dtostrf(pi, 1, 3, stemp);
page += F("<tr><td>Current: </td><td>"); page += stemp; page += F(" A</td></tr>");
page += F("<tr><td>Power: </td><td>"); page += String(pw); page += F(" W</td></tr>");
dtostrf(pc, 1, 2, stemp);
page += F("<tr><td>Power Factor: </td><td>"); page += stemp; page += F("</td></tr>");
dtostrf(ped, 1, 3, stemp);
page += F("<tr><td>Energy Today: </td><td>"); page += stemp; page += F(" kWh</td></tr>");
dtostrf((float)sysCfg.hlw_kWhyesterday / 100000000, 1, 3, stemp);
page += F("<tr><td>Energy Yesterday: </td><td>"); page += stemp; page += F(" kWh</td></tr>");
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

View File

@ -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("&deg;%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("<tr><td>HTU Temperature: </td><td>"); page += itemp; page += iconv; page += F("</td></tr>");
dtostrf(h_htu21, 1, HUMIDITY_RESOLUTION &3, itemp);
page += F("<tr><td>HTU Humidity: </td><td>"); page += itemp; page += F("%</td></tr>");
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;
}