diff --git a/tasmota/xsns_62_MI_HM10.ino b/tasmota/xsns_62_MI_HM10.ino index 83252c4f6..581a32096 100644 --- a/tasmota/xsns_62_MI_HM10.ino +++ b/tasmota/xsns_62_MI_HM10.ino @@ -20,6 +20,9 @@ -------------------------------------------------------------------------------------------- Version yyyymmdd Action Description -------------------------------------------------------------------------------------------- + 0.9.3.0 20200322 added - multi page web view, command HM10PAGE, polling for MJ_HT_V1, + more stable readings, internal refactoring + --- 0.9.2.0 20200317 added - MiBeacon-support, add Flora, MJ_HT_V1 and CGD1, add dew point, add AUTO(-scan), RULES-message --- @@ -42,11 +45,12 @@ TasmotaSerial *HM10Serial; #define HM10_MAX_TASK_NUMBER 12 uint8_t HM10_TASK_LIST[HM10_MAX_TASK_NUMBER+1][2]; // first value: kind of task - second value: delay in x * 100ms -#define HM10_MAX_RX_BUF 160 +#define HM10_MAX_RX_BUF 64 struct { uint8_t current_task_delay; // number of 100ms-cycles uint8_t last_command; + uint16_t perPage = 4; uint16_t firmware; uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start uint32_t serialSpeed; @@ -71,24 +75,24 @@ struct { } HM10; #pragma pack(1) // byte-aligned structures to read the sensor data -struct { - uint16_t temp; - uint8_t hum; -} LYWSD0x_HT; -struct { - uint8_t spare; - uint16_t temp; - uint16_t hum; -} CGD1_HT; + struct { + uint16_t temp; + uint8_t hum; + } LYWSD0x_HT; + struct { + uint8_t spare; + uint16_t temp; + uint16_t hum; + } CGD1_HT; + struct { + uint16_t temp; + uint8_t spare; + uint32_t lux; + uint8_t moist; + uint16_t fert; + } Flora_TLMF; // temperature, lux, moisture, fertility -struct { - uint16_t temp; - uint8_t spare; - uint32_t lux; - uint8_t moist; - uint16_t fert; -} Flora_TLMF; // temeprature, lux, moisture, fertility struct mi_beacon_t{ uint16_t frame; @@ -118,7 +122,7 @@ struct mi_sensor_t{ uint8_t type; //Flora = 1; MI-HT_V1=2; LYWSD02=3; LYWSD03=4; CGG1=5; CGD1=6 uint8_t serial[6]; uint8_t showedUp; - float temp; //Flora, MJ_HT_V1, LYWSD0x + float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx union { struct { float moisture; @@ -129,7 +133,7 @@ struct mi_sensor_t{ float hum; }; // MJ_HT_V1, LYWSD0x }; - uint8_t bat; + uint8_t bat; // all sensors }; std::vector MIBLEsensors; @@ -142,7 +146,7 @@ std::vector MIBLEsensors; const char S_JSON_HM10_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_HM10 "%s\":%d}"; const char S_JSON_HM10_COMMAND[] PROGMEM = "{\"" D_CMND_HM10 "%s%s\"}"; -const char kHM10_Commands[] PROGMEM = "Scan|AT|Period|Baud|Time|Auto"; +const char kHM10_Commands[] PROGMEM = "Scan|AT|Period|Baud|Time|Auto|Page"; #define FLORA 1 #define MJ_HT_V1 2 @@ -177,7 +181,8 @@ enum HM10_Commands { // commands useable in console or rules CMND_HM10_PERIOD, // set period like TELE-period in seconds between read-cycles CMND_HM10_BAUD, // serial speed of ESP8266 (<-> HM10), does not change baud rate of HM10 CMND_HM10_TIME, // set LYWSD02-Time from ESP8266-time - CMND_HM10_AUTO // do discovery scans permanently to receive MiBeacons in seconds between read-cycles + CMND_HM10_AUTO, // do discovery scans permanently to receive MiBeacons in seconds between read-cycles + CMND_HM10_PAGE // sensor entries per web page, which will be shown alternated }; enum HM10_awaitData: uint8_t { @@ -186,7 +191,8 @@ enum HM10_awaitData: uint8_t { TLMF = 2, bat = 3, tempHumCGD1 = 4, - discScan = 5 + discScan = 5, + tempHumMJ = 6 }; /*********************************************************************************************\ @@ -222,7 +228,10 @@ enum HM10_awaitData: uint8_t { #define TASK_HM10_UN_HT_CGD1 26 // unsubscribe service handle 4b #define TASK_HM10_READ_B_CGD1 27 // read service handle 11 #define TASK_HM10_DELAY_SUB_CGD1 28 // start reading from subscription delayed -#define TASK_HM10_STATUS_EVENT 29 // process status for RULES +#define TASK_HM10_READ_B_MJ 29 // read service handle 18 +#define TASK_HM10_SUB_HT_MJ 30 // subscribe to service handle 0f + +#define TASK_HM10_STATUS_EVENT 32 // process status for RULES #define TASK_HM10_DONE 99 // used, if there was a task in the slot or just to wait @@ -314,6 +323,14 @@ void HM10_Read_CGD1(void) { HM10_Launchtask(TASK_HM10_DISCONN,5,5); // disconnect } +void HM10_Read_MJ_HT_V1(void) { + HM10_Launchtask(TASK_HM10_CONN,0,1); // connect + HM10_Launchtask(TASK_HM10_FEEDBACK,1,35); // get OK+CONN + HM10_Launchtask(TASK_HM10_READ_B_MJ,2,20); // battery + HM10_Launchtask(TASK_HM10_SUB_HT_MJ,3,10); // temp hum + HM10_Launchtask(TASK_HM10_DISCONN,4,5); // disconnect + } + /** * @brief Return the slot number of a known sensor or return create new sensor slot * @@ -354,17 +371,17 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_serial)[6], uint16_t _type){ memcpy(_newSensor.serial,_serial, sizeof(_serial)); _newSensor.type = _type; _newSensor.showedUp = 1; - _newSensor.temp =-1000.0f; + _newSensor.temp =NAN; _newSensor.bat=0x00; switch (_type) { case 1: - _newSensor.moisture =-1000.0f; - _newSensor.fertility =-1000.0f; + _newSensor.moisture =NAN; + _newSensor.fertility =NAN; _newSensor.lux = 0x00ffffff; break; case 2: case 3: case 4: case 5: case 6: - _newSensor.hum=-1.0f; + _newSensor.hum=NAN; break; default: break; @@ -536,6 +553,7 @@ void HM10ParseResponse(char *buf, uint16_t bufsize) { void HM10readHT_LY(char *_buf){ DEBUG_SENSOR_LOG(PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_HM10,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + if(_buf[0]==0x4f && _buf[1]==0x4b && _buf[2]==0x2b) return; // "OK+" if(_buf[0] != 0 && _buf[1] != 0){ memcpy(&LYWSD0x_HT,(void *)_buf,3); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s: T * 100: %u, H: %u"),D_CMND_HM10,LYWSD0x_HT.temp,LYWSD0x_HT.hum); @@ -559,7 +577,8 @@ void HM10readHT_LY(char *_buf){ } void HM10readHT_CGD1(char *_buf){ - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_HM10,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + DEBUG_SENSOR_LOG(PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_HM10,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + if(_buf[0]==0x4f && _buf[1]==0x4b && _buf[2]==0x2b) return; // "OK+" if(_buf[0] == 0){ if(_buf[1]==0 && _buf[2]==0 && _buf[3]==0 && _buf[4]==0) return; memcpy(&CGD1_HT,(void *)_buf,5); @@ -583,8 +602,35 @@ void HM10readHT_CGD1(char *_buf){ } } +void HM10readHT_MJ_HT_V1(char *_buf){ + DEBUG_SENSOR_LOG(PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_HM10,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + if(_buf[0]!=0x54 && _buf[1]!=0x3d) return; //"T=" + // T=22.7 H=42.2 (response as ASCII) + // 0123456789012 + uint32_t _temp = (atoi(_buf+2) * 10) + atoi(_buf+5); + uint32_t _hum = (atoi(_buf+9) * 10) + atoi(_buf+12); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s: T * 10: %u, H * 10: %u"),D_CMND_HM10,_temp,_hum); + uint32_t _slot = HM10.state.sensor; + + DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); + static float _tempFloat; + _tempFloat=(float)_temp/10.0f; + if(_tempFloat<60){ + MIBLEsensors.at(_slot).temp=_tempFloat; + HM10.mode.awaiting = none; + HM10.current_task_delay = 0; + MIBLEsensors.at(_slot).showedUp=255; // this sensor is real + } + _tempFloat=(float)_hum/10.0f; + if(_tempFloat<100){ + MIBLEsensors.at(_slot).hum = _tempFloat; + DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1: hum updated")); + } +} + void HM10readTLMF(char *_buf){ DEBUG_SENSOR_LOG(PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_HM10,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + if(_buf[0]==0x4f && _buf[1]==0x4b && _buf[2]==0x2b) return; // "OK+" if(_buf[0] != 0 || _buf[1] != 0){ // this will lose 0.0 degree, but it is not possible to measure a successful reading memcpy(&Flora_TLMF,(void *)_buf,10); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s: T * 10: %u, L: %u, M: %u, F: %u"),D_CMND_HM10,Flora_TLMF.temp,Flora_TLMF.lux,Flora_TLMF.moist,Flora_TLMF.fert); @@ -612,6 +658,7 @@ void HM10readTLMF(char *_buf){ bool HM10readBat(char *_buf){ DEBUG_SENSOR_LOG(PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_HM10,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + if(_buf[0]==0x4f && _buf[1]==0x4b && _buf[2]==0x2b) return false; // "OK+" if(_buf[0] != 0){ AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: Battery: %u"),D_CMND_HM10,_buf[0]); uint32_t _slot = HM10.state.sensor; @@ -662,12 +709,15 @@ bool HM10SerialHandleFeedback(){ // every 50 milliseconds break; case TLMF: if (HM10.mode.connected) HM10readTLMF(ret); - break; + break; case discScan: if(success) { HM10ParseResponse(ret,i); } break; + case tempHumMJ: + if (HM10.mode.connected) HM10readHT_MJ_HT_V1(ret); + break; case none: if(success) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s: response: %s"),D_CMND_HM10, (char *)ret); @@ -875,6 +925,23 @@ void HM10_TaskEvery100ms(){ runningTaskLoop = false; HM10Serial->write("AT+SCAN9"); break; + case TASK_HM10_READ_B_MJ: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s: read handle 0x18"),D_CMND_HM10); + HM10.current_task_delay = 2; // set task delay + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10Serial->write("AT+READDATA0018?"); + HM10.mode.awaiting = bat; + break; + case TASK_HM10_SUB_HT_MJ: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s: subscribe to 0x0f"),D_CMND_HM10); + HM10.current_task_delay = 10; // set task delay + HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); + runningTaskLoop = false; + HM10.mode.awaiting = none; + HM10Serial->write("AT+NOTIFY_ON000F"); + HM10.mode.awaiting = tempHumMJ; + break; case TASK_HM10_FEEDBACK: AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s: get response"),D_CMND_HM10); HM10SerialHandleFeedback(); @@ -941,11 +1008,17 @@ void HM10StatusInfo(){ * */ -void HM10EverySecond(){ +void HM10EverySecond(bool restart){ static uint32_t _counter = 0; static uint32_t _nextSensorSlot = 0; static uint32_t _lastDiscovery = 0; + if(restart){ + _counter = 0; + _lastDiscovery = 0; + return; + } + if(HM10.firmware == 0) return; if(HM10.mode.pending_task == 1) return; if(MIBLEsensors.size()==0 && !HM10.mode.autoScan) return; @@ -965,14 +1038,17 @@ void HM10EverySecond(){ _nextSensorSlot++; HM10.mode.pending_task = 1; switch(MIBLEsensors.at(HM10.state.sensor).type){ - case LYWSD03MMC: - HM10_Read_LYWSD03(); + case FLORA: + HM10_Read_Flora(); + break; + case MJ_HT_V1: + HM10_Read_MJ_HT_V1(); break; case LYWSD02: HM10_Read_LYWSD02(); break; - case FLORA: - HM10_Read_Flora(); + case LYWSD03MMC: + HM10_Read_LYWSD03(); break; case CGD1: HM10_Read_CGD1(); @@ -1007,7 +1083,13 @@ bool HM10Cmd(void) { switch (command_code) { case CMND_HM10_PERIOD: if (XdrvMailbox.data_len > 0) { - HM10.period = XdrvMailbox.payload; + if (XdrvMailbox.payload==1) { + HM10EverySecond(true); + XdrvMailbox.payload = HM10.period; + } + else { + HM10.period = XdrvMailbox.payload; + } } else { XdrvMailbox.payload = HM10.period; @@ -1051,6 +1133,14 @@ bool HM10Cmd(void) { } Response_P(S_JSON_HM10_COMMAND_NVALUE, command, XdrvMailbox.payload); break; + case CMND_HM10_PAGE: + if (XdrvMailbox.data_len > 0) { + if (XdrvMailbox.payload == 0) XdrvMailbox.payload = HM10.perPage; // ignore 0 + HM10.perPage = XdrvMailbox.payload; + } + else XdrvMailbox.payload = HM10.perPage; + Response_P(S_JSON_HM10_COMMAND_NVALUE, command, XdrvMailbox.payload); + break; case CMND_HM10_AT: HM10Serial->write("AT"); // without an argument this will disconnect if (strlen(XdrvMailbox.data)!=0) { @@ -1080,7 +1170,7 @@ bool HM10Cmd(void) { * Presentation \*********************************************************************************************/ -const char HTTP_HM10[] PROGMEM = "{s}HM10" " Firmware " "{m}%u{e}"; +const char HTTP_HM10[] PROGMEM = "{s}HM10 V%u{m}%u%s / %u{e}"; const char HTTP_HM10_SERIAL[] PROGMEM = "{s}%s %s{m}%02x:%02x:%02x:%02x:%02x:%02x%{e}"; const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u%%{e}"; const char HTTP_HM10_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%sus/cm{e}"; @@ -1092,18 +1182,17 @@ void HM10Show(bool json) for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { char slave[33]; sprintf_P(slave,"%s-%02x%02x%02x",kHM10SlaveType[MIBLEsensors.at(i).type-1],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]); - char temperature[FLOATSZ]; // all sensors have temperature - dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature); - ResponseAppend_P(PSTR(",\"%s\":{"),slave); - if(MIBLEsensors.at(i).temp!=-1000.0f){ // this is the error code -> no temperature - ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s"), temperature); - } - else { - ResponseAppend_P(PSTR("}")); - continue; - } if (MIBLEsensors.at(i).type==FLORA){ + if(!isnan(MIBLEsensors.at(i).temp)){ // this is the error code -> no temperature + char temperature[FLOATSZ]; // all sensors have temperature + dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature); + ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s"), temperature); + } + else { + ResponseAppend_P(PSTR("}")); + continue; + } char lux[FLOATSZ]; char moisture[FLOATSZ]; char fertility[FLOATSZ]; @@ -1113,23 +1202,16 @@ void HM10Show(bool json) if(MIBLEsensors.at(i).lux!=0x0ffffff){ // this is the error code -> no temperature ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":%s"), lux); } - if(MIBLEsensors.at(i).moisture!=-1000.0f){ // this is the error code -> no moisture + if(!isnan(MIBLEsensors.at(i).moisture)){ // this is the error code -> no moisture ResponseAppend_P(PSTR(",\"" D_JSON_MOISTURE "\":%s"), moisture); } - if(MIBLEsensors.at(i).fertility!=-1000.0f){ // this is the error code -> no fertility + if(!isnan(MIBLEsensors.at(i).fertility)){ // this is the error code -> no fertility ResponseAppend_P(PSTR(",\"Fertility\":%s"), fertility); } } if (MIBLEsensors.at(i).type>FLORA){ - char humidity[FLOATSZ]; - dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity); - if(MIBLEsensors.at(i).hum!=-1.0f){ // this is the error code -> no humidity - ResponseAppend_P(PSTR(",\"" D_JSON_HUMIDITY "\":%s"), humidity); - } - if(MIBLEsensors.at(i).hum!=-1.0f && MIBLEsensors.at(i).temp!=-1000.0f){ // this is the error code -> no humidity nor temp - char dewpoint[FLOATSZ]; - dtostrfd(CalcTempHumToDew(MIBLEsensors.at(i).temp, MIBLEsensors.at(i).hum), Settings.flag2.temperature_resolution, dewpoint); - ResponseAppend_P(PSTR(",\"" D_JSON_DEWPOINT "\":%s"), dewpoint); + if(!isnan(MIBLEsensors.at(i).hum) && !isnan(MIBLEsensors.at(i).temp)){ // this is the error code -> no humidity nor temp + ResponseAppendTHD(MIBLEsensors.at(i).temp, MIBLEsensors.at(i).hum); } } if(MIBLEsensors.at(i).bat!=0x00){ // this is the error code -> no battery @@ -1139,44 +1221,57 @@ void HM10Show(bool json) } #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_HM10, HM10.firmware); - for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { + static uint16_t _page = 0; + static uint16_t _counter = 0; + int32_t i = _page * HM10.perPage; + uint32_t j = i + HM10.perPage; + if (j+1>MIBLEsensors.size()){ + j = MIBLEsensors.size(); + } + char stemp[5] ={0}; + if (MIBLEsensors.size()-(_page*HM10.perPage)>1 && HM10.perPage!=1) { + sprintf_P(stemp,"-%u",j); + } + if (MIBLEsensors.size()==0) i=-1; // only for the GUI + + WSContentSend_PD(HTTP_HM10, HM10.firmware, i+1,stemp,MIBLEsensors.size()); + for (i; i no valid value WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).lux); } - if(MIBLEsensors.at(i).moisture!=-1000.0f){ // this is the error code -> no valid value + if(!isnan(MIBLEsensors.at(i).moisture)){ // this is the error code -> no valid value WSContentSend_PD(HTTP_SNS_MOISTURE, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).moisture); } - if(MIBLEsensors.at(i).fertility!=-1000.0f){ // this is the error code -> no valid value + if(!isnan(MIBLEsensors.at(i).fertility)){ // this is the error code -> no valid value char fertility[FLOATSZ]; dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility); WSContentSend_PD(HTTP_HM10_FLORA_DATA, kHM10SlaveType[MIBLEsensors.at(i).type-1], fertility); } } if (MIBLEsensors.at(i).type>FLORA){ // everything "above" Flora - if(MIBLEsensors.at(i).hum!=-1.0f){ // this is the error code -> no humidity - char humidity[FLOATSZ]; - dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity); - WSContentSend_PD(HTTP_SNS_HUM, kHM10SlaveType[MIBLEsensors.at(i).type-1], humidity); - } - if(MIBLEsensors.at(i).hum!=-1.0f && MIBLEsensors.at(i).temp!=-1000.0f){ // this is the error code -> no humidity nor temp - char dewpoint[FLOATSZ]; - dtostrfd(CalcTempHumToDew(MIBLEsensors.at(i).temp, MIBLEsensors.at(i).hum), Settings.flag2.temperature_resolution, dewpoint); - WSContentSend_PD(HTTP_SNS_DEW, kHM10SlaveType[MIBLEsensors.at(i).type-1], dewpoint, TempUnit()); + if(!isnan(MIBLEsensors.at(i).hum) && !isnan(MIBLEsensors.at(i).temp)){ + WSContentSend_THD(kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).temp, MIBLEsensors.at(i).hum); } } if(MIBLEsensors.at(i).bat!=0x00){ WSContentSend_PD(HTTP_BATTERY, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).bat); } } + _counter++; + if(_counter>3) { + _page++; + _counter=0; + } + if(MIBLEsensors.size()%HM10.perPage==0 && _page==MIBLEsensors.size()/HM10.perPage) _page=0; + if(_page>MIBLEsensors.size()/HM10.perPage) _page=0; #endif // USE_WEBSERVER } } @@ -1203,7 +1298,7 @@ bool Xsns62(uint8_t function) } break; case FUNC_EVERY_SECOND: - HM10EverySecond(); + HM10EverySecond(false); break; case FUNC_COMMAND: result = HM10Cmd();