/* xsns_62_MI_HM10.ino - MI-BLE-sensors via HM-10 support for Tasmota Copyright (C) 2021 Christian Baars and Theo Arends This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -------------------------------------------------------------------------------------------- Version yyyymmdd Action Description -------------------------------------------------------------------------------------------- 0.9.6.0 20201127 added - add BLOCK and OPTION command, send BLE scan via MQTT, refactoring, support negative temps --- 0.9.5.0 20201101 added - bugfixes, better advertisement parsing, beacon, HASS-fixes, CGD1 now passive readable --- 0.9.4.1 20200807 added - add ATC, some optimizations and a bit more error handling --- 0.9.4.0 20200807 added - multiple backports from the HM10-driver (NLIGHT,MJYD2S,YEERC,MHOC401,MHOC303), fixing Flora values, adding realtime-bridge, better battery for LYWSD03 and MHOC401 --- 0.9.3.1 20200412 added - clean ups, code shrink, battery bugfix --- 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 --- 0.9.1.0 20200209 added - LYWSD02-support, including setting the time --- 0.9.0.0 20200130 started - initial development by Christian Baars (support LYWSD03 only) forked - from arendst/tasmota - https://github.com/arendst/Tasmota */ #ifdef ESP8266 // ESP8266 only. Use define USE_MI_ESP32 for ESP32 support #ifdef USE_HM10 #define XSNS_62 62 #include #include TasmotaSerial *HM10Serial; #define HM10_BAUDRATE 115200 // default with FW>700 is 115200 #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 64 #pragma pack(1) // byte-aligned structures to read the sensor data struct LYWSD0x_HT_t{ int16_t temp; uint8_t hum; uint16_t volt; }; struct CGD1_HT_t{ uint8_t spare; int16_t temp; uint16_t hum; }; struct Flora_TLMF_t{ int16_t temp; uint8_t spare; uint32_t lux; uint8_t moist; uint16_t fert; uint32_t ID; }; // temperature, lux, moisture, fertility struct mi_beacon_t{ uint16_t frame; uint16_t productID; uint8_t counter; uint8_t MAC[6]; uint8_t spare; uint8_t type; uint8_t ten; uint8_t size; union { struct{ //0d int16_t temp; uint16_t hum; }HT; uint8_t bat; //0a int16_t temp; //04 uint16_t hum; //06 uint32_t lux; //07 uint8_t moist; //08 uint16_t fert; //09 uint32_t NMT; //17 struct{ //01 uint16_t num; uint8_t longPress; }Btn; }; uint8_t padding[12]; }; struct ATCPacket_t{ uint8_t MAC[6]; uint16_t temp; //sadly this is in wrong endianess uint8_t hum; uint8_t batPer; uint16_t batMV; uint8_t frameCnt; }; struct cg_packet_t { uint16_t frameID; uint8_t MAC[6]; uint16_t mode; union { struct { int16_t temp; // -9 - 59 °C uint16_t hum; }; uint8_t bat; }; }; struct MiScaleV1Packet_t { //uint8_t size; // = 14 //uint8_t uid; // = 0x16, 16-bit UUID //uint16_t UUID; // = 0x181D uint8_t status; // bit 0 lbs, 4 jin, 5, stabilized, 7, weight removed uint16_t weight; uint16_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; }; struct MiScaleV2Packet_t { //uint8_t size; // = 17 //uint8_t uid; // = 0x16, 16-bit UUID //uint16_t UUID; // = 0x181B uint8_t weight_unit; uint8_t status; uint16_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; uint16_t impedance; uint16_t weight; }; #pragma pack(0) struct scan_entry_t { uint8_t MAC[6]; uint16_t CID; uint16_t UUID; int32_t RSSI; uint8_t TX; union{ uint16_t SVC; uint8_t svcData[32]; }; }; struct generic_beacon_t { uint8_t MAC[6]; uint32_t time; int32_t RSSI; uint16_t CID; // company identifier uint16_t UUID; // the first, if more than one exists uint16_t SVC; uint16_t TX; bool active = false; }; struct mi_sensor_t{ uint8_t type; //Flora = 1; MI-HT_V1=2; LYWSD02=3; LYWSD03=4; CGG1=5; CGD1=6 uint8_t lastCnt; //device generated counter of the packet uint8_t shallSendMQTT; uint8_t showedUp; uint8_t MAC[6]; union { struct { uint32_t temp:1; uint32_t hum:1; uint32_t tempHum:1; //every hum sensor has temp too, easier to use Tasmota dew point functions uint32_t lux:1; uint32_t moist:1; uint32_t fert:1; uint32_t bat:1; uint32_t NMT:1; uint32_t PIR:1; uint32_t Btn:1; uint32_t scale:1; uint32_t impedance:1; }; uint32_t raw; } feature; union { struct { uint32_t temp:1; uint32_t hum:1; uint32_t tempHum:1; //can be combined from the sensor uint32_t lux:1; uint32_t moist:1; uint32_t fert:1; uint32_t bat:1; uint32_t NMT:1; uint32_t motion:1; uint32_t noMotion:1; uint32_t Btn:1; uint32_t scale:1; }; uint32_t raw; } eventType; int rssi; uint32_t lastTime; uint32_t lux; float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx union { struct { uint8_t moisture; uint16_t fertility; char firmware[6]; // actually only for FLORA but hopefully we can add for more devices }; // Flora struct { float hum; }; // MJ_HT_V1, LYWSD0x struct { uint16_t events; //"alarms" since boot uint32_t NMT; // no motion time in seconds for the MJYD2S }; uint16_t Btn; struct { uint8_t has_impedance; uint8_t impedance_stabilized; uint8_t weight_stabilized; uint8_t weight_removed; char weight_unit[4]; // kg, lbs, jin or empty when unknown float weight; uint16_t impedance; struct { uint16_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; } datetime; }; }; union { uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1) }; }; 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; union { uint32_t time; uint8_t timebuf[4]; }; uint16_t autoScanInterval; struct { uint32_t awaiting:8; uint32_t init:1; uint32_t pending_task:1; uint32_t connected:1; uint32_t subscribed:1; uint32_t autoScan:1; uint32_t shallTriggerTele:1; uint32_t triggeredTele:1; uint32_t activeBeacon:1; uint32_t firstAutodiscoveryDone:1; uint32_t shallShowScanResult:1; uint32_t shallShowBlockList:1; } mode; struct { uint8_t sensor; // points to to the number 0...255 uint8_t beaconScanCounter; // TODO: more to come } state; struct { uint32_t allwaysAggregate:1; // always show all known values of one sensor in brdigemode uint32_t noSummary:1; // no sensor values at TELE-period uint32_t directBridgeMode:1; // send every received BLE-packet as a MQTT-message in real-time uint32_t holdBackFirstAutodiscovery:1; // allows to trigger it later uint32_t showRSSI:1; uint32_t ignoreBogusBattery:1; uint32_t minimalSummary:1; // DEPRECATED!! } option; scan_entry_t rxAdvertisement; char *rxBuffer; } HM10; struct MAC_t { uint8_t buf[6]; }; std::vector MIBLEsensors; std::array MIBLEbeacons; // we support a fixed number std::vector MIBLEscanResult; std::vector MIBLEBlockList; /*********************************************************************************************\ * constants \*********************************************************************************************/ #define D_CMND_HM10 "HM10" const char S_JSON_HM10_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_HM10 "%s\":%d}"; const char S_JSON_HM10_COMMAND_SVALUE[] PROGMEM = "{\"" D_CMND_HM10 "%s%u\":\"%s\"}"; const char S_JSON_HM10_COMMAND[] PROGMEM = "{\"" D_CMND_HM10 "%s%s\"}"; const char kHM10_Commands[] PROGMEM = D_CMND_HM10"|" "Scan|AT|Period|Baud|Time|Auto|Page|Beacon|Block|Option"; const char HTTP_MISCALE_WEIGHT[] PROGMEM = "{s}%s" " Weight" "{m}%*_f %s{e}"; const char HTTP_MISCALE_IMPEDANCE[] PROGMEM = "{s}%s" " Impedance" "{m}%u{e}"; const char HTTP_MISCALE_WEIGHT_REMOVED[] PROGMEM = "{s}%s" " Weight removed" "{m}%s{e}"; const char HTTP_MISCALE_STABILIZED[] PROGMEM = "{s}%s" " Stabilized" "{m}%s{e}"; void (*const HM10_Commands[])(void) PROGMEM = { &CmndHM10Scan, &CmndHM10AT, &CmndHM10Period, &CmndHM10Baud, &CmndHM10Time, &CmndHM10Auto, &CmndHM10Page, &CmndHM10Beacon, &CmndHM10Block, &CmndHM10Option }; #define HM10_FLORA 1 #define HM10_MJ_HT_V1 2 #define HM10_LYWSD02 3 #define HM10_LYWSD03MMC 4 #define HM10_CGG1 5 #define HM10_CGD1 6 #define HM10_NLIGHT 7 #define HM10_MJYD2S 8 #define HM10_YEERC 9 #define HM10_MHOC401 10 #define HM10_MHOC303 11 #define HM10_ATC 12 #define HM10_MI_SCALE_V1 13 #define HM10_MI_SCALE_V2 14 #define HM10_TYPES 14 //count this manually const uint16_t kHM10SlaveID[HM10_TYPES]={ 0x0098, // Flora 0x01aa, // MJ_HT_V1 0x045b, // LYWSD02 0x055b, // LYWSD03 0x0347, // CGG1 0x0576, // CGD1 0x03dd, // NLIGHT 0x07f6, // MJYD2S 0x0153, // yee-rc 0x0387, // MHO-C401 0x06d3, // MHO-C303 0x0a1c, // ATC -> this is a fake ID 0x181d, // Mi Scale V1 0x181b // Mi Scale V2 }; const char kHM10DeviceType1[] PROGMEM = "Flora"; const char kHM10DeviceType2[] PROGMEM = "MJ_HT_V1"; const char kHM10DeviceType3[] PROGMEM = "LYWSD02"; const char kHM10DeviceType4[] PROGMEM = "LYWSD03"; const char kHM10DeviceType5[] PROGMEM = "CGG1"; const char kHM10DeviceType6[] PROGMEM = "CGD1"; const char kHM10DeviceType7[] PROGMEM = "NLIGHT"; const char kHM10DeviceType8[] PROGMEM = "MJYD2S"; const char kHM10DeviceType9[] PROGMEM = "YEERC"; const char kHM10DeviceType10[] PROGMEM ="MHOC401"; const char kHM10DeviceType11[] PROGMEM ="MHOC303"; const char kHM10DeviceType12[] PROGMEM ="ATC"; const char kHM10DeviceType13[] PROGMEM ="MSCALE1"; const char kHM10DeviceType14[] PROGMEM ="MSCALE2"; const char * kHM10DeviceType[] PROGMEM = {kHM10DeviceType1,kHM10DeviceType2,kHM10DeviceType3,kHM10DeviceType4,kHM10DeviceType5,kHM10DeviceType6,kHM10DeviceType7,kHM10DeviceType8,kHM10DeviceType9,kHM10DeviceType10,kHM10DeviceType11,kHM10DeviceType12,kHM10DeviceType13,kHM10DeviceType14}; /*********************************************************************************************\ * enumerations \*********************************************************************************************/ enum HM10_awaitData: uint8_t { none = 0, tempHumLY = 1, TLMF = 2, bat = 3, tempHumCGD1 = 4, discScan = 5, tempHumMJ = 6 }; /*********************************************************************************************\ * Task codes defines \*********************************************************************************************/ #define TASK_HM10_NOTASK 0 // nothing to be done #define TASK_HM10_ROLE1 1 // change role to 1 #define TASK_HM10_IMME1 2 // change imme to 1 #define TASK_HM10_RENEW 3 // device factory setting #define TASK_HM10_RESET 4 // device reset #define TASK_HM10_DISC 5 // device discovery scan: AT+DISA? #define TASK_HM10_CONN 6 // connect to given MAC #define TASK_HM10_VERSION 7 // query FW version #define TASK_HM10_NAME 8 // query device name #define TASK_HM10_FEEDBACK 9 // get device response #define TASK_HM10_DISCONN 10 // disconnect #define TASK_HM10_SUB_L3 11 // subscribe to service handle 37 #define TASK_HM10_SCAN9 13 // longest discovery scan possible #define TASK_HM10_UN_L3 14 // unsubscribe service handle 37 #define TASK_HM10_READ_BT_L3 16 // read from handle 3A -> Battery #define TASK_HM10_SUB_L2 17 // subscribe to service handle 3C #define TASK_HM10_UN_L2 18 // unsubscribe service handle 3C #define TASK_HM10_READ_BT_L2 19 // read from handle 43 -> Battery #define TASK_HM10_TIME_L2 20 // set time of LYWSD02 to system time #define TASK_HM10_SHOW0 21 // set verbositiy to minimum #define TASK_HM10_READ_BF_FL 22 // read battery and firmware from flower care #define TASK_HM10_CALL_TLMF_FL 23 // write 0xa01f to handle 0x33 to init sensor readings #define TASK_HM10_READ_TLMF_FL 24 // read temp,lux,moist and fert from flower care #define TASK_HM10_SUB_HT_CGD1 25 // subscribe to service handle 4b #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_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 /*********************************************************************************************\ * Helper functions \*********************************************************************************************/ void HM10_Launchtask(uint8_t task, uint8_t slot, uint8_t delay){ HM10_TASK_LIST[slot][0] = task; HM10_TASK_LIST[slot][1] = delay; HM10_TASK_LIST[slot+1][0] = TASK_HM10_NOTASK; // the tasks must always be launched in ascending order!! HM10.current_task_delay = HM10_TASK_LIST[0][1]; } void HM10_TaskReplaceInSlot(uint8_t task, uint8_t slot){ HM10.last_command = HM10_TASK_LIST[slot][0]; // save command HM10_TASK_LIST[slot][0] = task; } /** * @brief Remove all colons from null terminated char array * * @param _string Typically representing a MAC-address like AA:BB:CC:DD:EE:FF */ void HM10stripColon(char* _string){ uint32_t _length = strlen(_string); uint32_t _index = 0; while (_index < _length) { char c = _string[_index]; if(c==':'){ memmove(_string+_index,_string+_index+1,_length-_index); } _index++; } _string[_index] = 0; } /** * @brief Convert string that repesents a hexadecimal number to a byte array * * @param _string input string in format: AABBCCDDEEFF or AA:BB:CC:DD:EE:FF, caseinsensitive * @param _mac target byte array must match the correct size (i.e. AA:BB -> uint8_t bytes[2]) */ void HM10HexStringToBytes(char* _string, uint8_t* _byteArray) { HM10stripColon(_string); UpperCase(_string,_string); uint32_t index = 0; uint32_t _end = strlen(_string); memset(_byteArray,0,_end/2); while (index < _end) { char c = _string[index]; uint8_t value = 0; if(c >= '0' && c <= '9') value = (c - '0'); else if (c >= 'A' && c <= 'F') value = (10 + (c - 'A')); _byteArray[(index/2)] += value << (((index + 1) % 2) * 4); index++; } } void HM10_ReverseMAC(uint8_t _mac[]){ uint8_t _reversedMAC[6]; for (uint8_t i=0; i<6; i++){ _reversedMAC[5-i] = _mac[i]; } memcpy(_mac,_reversedMAC, sizeof(_reversedMAC)); } /*********************************************************************************************\ * chained tasks \*********************************************************************************************/ void HM10_Reset(void) { HM10_Launchtask(TASK_HM10_DISCONN,0,1); // disconnect HM10_Launchtask(TASK_HM10_ROLE1,1,1); // set role to 1 HM10_Launchtask(TASK_HM10_IMME1,2,1); // set imme to 1 HM10_Launchtask(TASK_HM10_RESET,3,1); // reset Device HM10_Launchtask(TASK_HM10_VERSION,4,10); // read SW Version HM10_Launchtask(TASK_HM10_SCAN9,5,2); // scan time 9 seconds HM10_Launchtask(TASK_HM10_DISC,6,2); // discovery HM10_Launchtask(TASK_HM10_STATUS_EVENT,7,2); // status } void HM10_Discovery_Scan(void) { // HM10_Launchtask(TASK_HM10_DISCONN,0,1); // disconnect HM10_Launchtask(TASK_HM10_DISC,0,1); // discovery // HM10_Launchtask(TASK_HM10_STATUS_EVENT,2,1); // status } void HM10_Read_LYWSD03(void) { //and MHO-C401 HM10_Launchtask(TASK_HM10_CONN,0,1); // connect HM10_Launchtask(TASK_HM10_FEEDBACK,1,35); // get OK+CONN HM10_Launchtask(TASK_HM10_SUB_L3,2,20); // subscribe HM10_Launchtask(TASK_HM10_UN_L3,3,80); // unsubscribe // HM10_Launchtask(TASK_HM10_READ_BT_L3,4,5); // read Battery HM10_Launchtask(TASK_HM10_DISCONN,4,5); // disconnect } void HM10_Read_LYWSD02(void) { //and MHO-C303 HM10_Launchtask(TASK_HM10_CONN,0,1); // connect HM10_Launchtask(TASK_HM10_FEEDBACK,1,35); // get OK+CONN HM10_Launchtask(TASK_HM10_SUB_L2,2,20); // subscribe HM10_Launchtask(TASK_HM10_UN_L2,3,80); // unsubscribe HM10_Launchtask(TASK_HM10_READ_BT_L2,4,5); // read Battery HM10_Launchtask(TASK_HM10_DISCONN,5,5); // disconnect } void HM10_Time_LYWSD02(void) { HM10_Launchtask(TASK_HM10_DISCONN,0,0); // disconnect HM10_Launchtask(TASK_HM10_CONN,1,5); // connect HM10_Launchtask(TASK_HM10_FEEDBACK,2,35); // get OK+CONN HM10_Launchtask(TASK_HM10_TIME_L2,3,20); // subscribe HM10_Launchtask(TASK_HM10_DISCONN,4,5); // disconnect } void HM10_Read_Flora(void) { HM10_Launchtask(TASK_HM10_DISCONN,0,0); // disconnect HM10_Launchtask(TASK_HM10_CONN,1,1); // connect HM10_Launchtask(TASK_HM10_FEEDBACK,2,5); // get OK+CONN HM10_Launchtask(TASK_HM10_READ_BF_FL,3,20); // read battery HM10_Launchtask(TASK_HM10_CALL_TLMF_FL,4,30); // read TLMF HM10_Launchtask(TASK_HM10_DISCONN,5,10); // disconnect } void HM10_Read_CGD1(void) { HM10_Launchtask(TASK_HM10_CONN,0,1); // connect HM10_Launchtask(TASK_HM10_FEEDBACK,1,35); // get OK+CONN HM10_Launchtask(TASK_HM10_SUB_HT_CGD1,2,20); // subscribe HM10_Launchtask(TASK_HM10_UN_HT_CGD1,3,10); // unsubscribe HM10_Launchtask(TASK_HM10_READ_B_CGD1,4,5); // read Battery 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 * * @param _MAC BLE address of the sensor * @param _type Type number of the sensor * @return uint32_t Known or new slot in the sensors-vector */ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, int _rssi){ DEBUG_SENSOR_LOG(PSTR("%s: will test ID-type: %x"),D_CMND_HM10, _type); bool _success = false; for (uint32_t i=0;ibegin(HM10.serialSpeed)) { AddLog(LOG_LEVEL_DEBUG, PSTR("%s start serial communication fixed to 115200 baud"),D_CMND_HM10); if (HM10Serial->hardwareSerial()) { ClaimSerial(); DEBUG_SENSOR_LOG(PSTR("%s: claim HW"),D_CMND_HM10); } HM10_Reset(); HM10.mode.pending_task = 1; HM10.mode.init = 1; HM10.period = Settings->tele_period; DEBUG_SENSOR_LOG(PSTR("%s_TASK_LIST initialized, now return to main loop"),D_CMND_HM10); //test section for options HM10.option.allwaysAggregate = 1; HM10.option.noSummary = 0; HM10.option.minimalSummary = 0; HM10.option.directBridgeMode = 0; HM10.option.showRSSI = 1; HM10.option.ignoreBogusBattery = 1; // from advertisements HM10.option.holdBackFirstAutodiscovery = 1; HM10.mode.autoScan = 1; HM10.rxBuffer = new char[HM10_MAX_RX_BUF]; } return; } /*********************************************************************************************\ * parse the response \*********************************************************************************************/ void HM10parseMiBeacon(char * _buf, uint32_t _slot){ float _tempFloat; mi_beacon_t _beacon; if (MIBLEsensors[_slot].type==HM10_MJ_HT_V1 || MIBLEsensors[_slot].type==HM10_CGG1 || MIBLEsensors[_slot].type==HM10_YEERC){ memcpy((uint8_t*)&_beacon+1,(uint8_t*)_buf, sizeof(_beacon)-1); // shift by one byte for the MJ_HT_V1 memcpy((uint8_t*)&_beacon.MAC,(uint8_t*)&_beacon.MAC+1,6); // but shift back the MAC } else{ memcpy((void*)&_beacon,(void*)_buf, sizeof(_beacon)); } HM10_ReverseMAC(_beacon.MAC); // if(memcmp(_beacon.MAC,MIBLEsensors[_slot].MAC,sizeof(_beacon.MAC))!=0){ // if (MIBLEsensors[_slot].showedUp>3) return; // probably false alarm from a damaged packet // AddLog(LOG_LEVEL_DEBUG, PSTR("%s: remove garbage sensor"),D_CMND_HM10); // DEBUG_SENSOR_LOG(PSTR("%s i: %x %x %x %x %x %x"),D_CMND_HM10, MIBLEsensors[_slot].MAC[5], MIBLEsensors[_slot].MAC[4],MIBLEsensors[_slot].MAC[3],MIBLEsensors[_slot].MAC[2],MIBLEsensors[_slot].MAC[1],MIBLEsensors[_slot].MAC[0]); // DEBUG_SENSOR_LOG(PSTR("%s n: %x %x %x %x %x %x"),D_CMND_HM10, _beacon.MAC[5], _beacon.MAC[4], _beacon.MAC[3],_beacon.MAC[2],_beacon.MAC[1],_beacon.MAC[0]); // MIBLEsensors.erase(MIBLEsensors.begin()+_slot); // return; // } // if (MIBLEsensors[_slot].showedUp<4) MIBLEsensors[_slot].showedUp++; DEBUG_SENSOR_LOG(PSTR("MiBeacon type:%02x: %02x %02x %02x %02x %02x %02x %02x %02x"),_beacon.type, (uint8_t)_buf[0],(uint8_t)_buf[1],(uint8_t)_buf[2],(uint8_t)_buf[3],(uint8_t)_buf[4],(uint8_t)_buf[5],(uint8_t)_buf[6],(uint8_t)_buf[7]); DEBUG_SENSOR_LOG(PSTR(" type:%02x: %02x %02x %02x %02x %02x %02x %02x %02x"),_beacon.type, (uint8_t)_buf[8],(uint8_t)_buf[9],(uint8_t)_buf[10],(uint8_t)_buf[11],(uint8_t)_buf[12],(uint8_t)_buf[13],(uint8_t)_buf[14],(uint8_t)_buf[15]); // MIBLEsensors[_slot].rssi = _rssi; if(MIBLEsensors[_slot].type==HM10_LYWSD03MMC || MIBLEsensors[_slot].type==HM10_CGD1 || MIBLEsensors[_slot].type==HM10_MHOC401){ DEBUG_SENSOR_LOG(PSTR("LYWSD03 and CGD1 no support for MiBeacon, type %u"),MIBLEsensors[_slot].type); return; } AddLog(LOG_LEVEL_DEBUG, PSTR("%s: %s mibeacon type: %x"),D_CMND_HM10, kHM10DeviceType[MIBLEsensors[_slot].type-1], _beacon.type); DEBUG_SENSOR_LOG(PSTR("%s at slot %u"), kHM10DeviceType[MIBLEsensors[_slot].type-1],_slot); switch(_beacon.type){ case 0x01: MIBLEsensors[_slot].Btn=_beacon.Btn.num + (_beacon.Btn.longPress/2)*6; MIBLEsensors[_slot].eventType.Btn = 1; HM10.mode.shallTriggerTele = 1; DEBUG_SENSOR_LOG(PSTR("Mode 1: U16: %u Button"), MIBLEsensors[_slot].Btn ); break; case 0x04: _tempFloat=(float)(_beacon.temp)/10.0f; if(_tempFloat<60){ MIBLEsensors[_slot].temp=_tempFloat; DEBUG_SENSOR_LOG(PSTR("Mode 4: temp updated")); MIBLEsensors[_slot].eventType.temp = 1; } DEBUG_SENSOR_LOG(PSTR("Mode 4: U16: %u Temp"), _beacon.temp ); break; case 0x06: _tempFloat=(float)(_beacon.hum)/10.0f; if(_tempFloat<101){ MIBLEsensors[_slot].hum=_tempFloat; DEBUG_SENSOR_LOG(PSTR("Mode 6: hum updated")); MIBLEsensors[_slot].eventType.hum = 1; } DEBUG_SENSOR_LOG(PSTR("Mode 6: U16: %u Hum"), _beacon.hum); break; case 0x07: if(MIBLEsensors[_slot].type==HM10_MJYD2S){ MIBLEsensors[_slot].eventType.noMotion = 1; } MIBLEsensors[_slot].lux=_beacon.lux & 0x00ffffff; DEBUG_SENSOR_LOG(PSTR("Mode 7: U24: %u Lux"), _beacon.lux & 0x00ffffff); break; case 0x08: if(_beacon.moist<101){ MIBLEsensors[_slot].moisture=_beacon.moist; DEBUG_SENSOR_LOG(PSTR("Mode 8: moisture updated")); MIBLEsensors[_slot].eventType.moist = 1; } DEBUG_SENSOR_LOG(PSTR("Mode 8: U8: %u Moisture"), _beacon.moist); break; case 0x09: if(_beacon.fert<65535){ MIBLEsensors[_slot].fertility=_beacon.fert; DEBUG_SENSOR_LOG(PSTR("Mode 9: fertility updated")); MIBLEsensors[_slot].eventType.fert = 1; } DEBUG_SENSOR_LOG(PSTR("Mode 9: U16: %u Fertility"), _beacon.fert); break; case 0x0a: if(_beacon.bat<101){ MIBLEsensors[_slot].bat = _beacon.bat; DEBUG_SENSOR_LOG(PSTR("Mode a: bat updated")); MIBLEsensors[_slot].eventType.bat = 1; } DEBUG_SENSOR_LOG(PSTR("Mode a: U8: %u %%"), _beacon.bat); break; case 0x0d: _tempFloat=(float)(_beacon.HT.temp)/10.0f; if(_tempFloat<60){ MIBLEsensors[_slot].temp = _tempFloat; DEBUG_SENSOR_LOG(PSTR("Mode d: temp updated")); } _tempFloat=(float)(_beacon.HT.hum)/10.0f; if(_tempFloat<100){ MIBLEsensors[_slot].hum = _tempFloat; DEBUG_SENSOR_LOG(PSTR("Mode d: hum updated")); } MIBLEsensors[_slot].eventType.tempHum = 1; DEBUG_SENSOR_LOG(PSTR("Mode d: U16: %x Temp U16: %x Hum"), _beacon.HT.temp, _beacon.HT.hum); break; } if(MIBLEsensors[_slot].eventType.raw == 0) return; MIBLEsensors[_slot].shallSendMQTT = 1; if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1; } void HM10parseATC(char * _buf, uint32_t _slot){ ATCPacket_t *_packet = (ATCPacket_t*)_buf; if(memcmp(_packet->MAC,MIBLEsensors.at(_slot).MAC,6)!=0) return; // data corruption MIBLEsensors.at(_slot).temp = (float)(int16_t(__builtin_bswap16(_packet->temp)))/10.0f; MIBLEsensors.at(_slot).hum = (float)_packet->hum; MIBLEsensors.at(_slot).bat = _packet->batPer; MIBLEsensors[_slot].shallSendMQTT = 1; if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1; } void HM10parseCGD1Packet(char * _buf, uint32_t _slot){ // no MiBeacon cg_packet_t *_packet = (cg_packet_t*)_buf; switch (_packet->mode){ case 0x0401: float _tempFloat; _tempFloat=(float)(_packet->temp)/10.0f; if(_tempFloat<60){ MIBLEsensors.at(_slot).temp = _tempFloat; MIBLEsensors[_slot].eventType.temp = 1; DEBUG_SENSOR_LOG(PSTR("CGD1: temp updated")); } _tempFloat=(float)(_packet->hum)/10.0f; if(_tempFloat<100){ MIBLEsensors.at(_slot).hum = _tempFloat; MIBLEsensors[_slot].eventType.hum = 1; DEBUG_SENSOR_LOG(PSTR("CGD1: hum updated")); } DEBUG_SENSOR_LOG(PSTR("CGD1: U16: %x Temp U16: %x Hum"), _packet->temp, _packet->hum); break; case 0x0102: if(_packet->bat<101){ MIBLEsensors.at(_slot).bat = _packet->bat; MIBLEsensors[_slot].eventType.bat = 1; DEBUG_SENSOR_LOG(PSTR("Mode a: bat updated")); } break; default: DEBUG_SENSOR_LOG(PSTR("HM10: unexpected CGD1-packet")); } if(MIBLEsensors[_slot].eventType.raw == 0) return; MIBLEsensors[_slot].shallSendMQTT = 1; if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1; } void HM10ParseMiScalePacket(char * _buf, uint32_t _slot, uint16_t _type){ //void MI32ParseMiScalePacket(const uint8_t * _buf, uint32_t length, const uint8_t *addr, int RSSI, int UUID){ uint8_t impedance_stabilized = 0; uint8_t weight_stabilized = 0; uint8_t weight_removed = 0; if (isnan(MIBLEsensors[_slot].weight)) { //First recieved packet, need to set default values to prevent memory garbage display // For other packets only stable values will be displayed (or bridgeMode will send all packets) MIBLEsensors[_slot].eventType.scale = 1; MIBLEsensors[_slot].has_impedance = 0; MIBLEsensors[_slot].weight_stabilized = 0; MIBLEsensors[_slot].impedance_stabilized = 0; MIBLEsensors[_slot].weight_removed = 0; MIBLEsensors[_slot].weight = 0.0f; MIBLEsensors[_slot].impedance = 0; strcpy(MIBLEsensors[_slot].weight_unit, PSTR("kg")); } // Mi Scale V1 if (_type == 0x181d){ // 14-1-1-2 MiScaleV1Packet_t *_packetV1 = (MiScaleV1Packet_t*)_buf; if ((_slot >= 0) && (_slot < MIBLEsensors.size())){ DEBUG_SENSOR_LOG(PSTR("HM10: %s: at slot %u"), kHM10DeviceType[MIBLEsensors[_slot].type-1],_slot); MIBLEsensors[_slot].eventType.scale = 1; weight_stabilized = (_packetV1->status & (1 << 5)) ? 1 : 0; weight_removed = (_packetV1->status & (1 << 7)) ? 1 : 0; // Set sensor values for every packet in BridgedMode and for every packet with stable weight if (HM10.option.directBridgeMode || (weight_stabilized && !weight_removed)) { MIBLEsensors[_slot].weight_stabilized = weight_stabilized; MIBLEsensors[_slot].weight_removed = weight_removed; if (_packetV1->status & (1 << 0)) { strcpy(MIBLEsensors[_slot].weight_unit, PSTR("lbs")); MIBLEsensors[_slot].weight = (float)_packetV1->weight / 100.0f; } else if (_packetV1->status & (1 << 4)) { strcpy(MIBLEsensors[_slot].weight_unit, PSTR("jin")); MIBLEsensors[_slot].weight = (float)_packetV1->weight / 100.0f; } else { strcpy(MIBLEsensors[_slot].weight_unit, PSTR("kg")); MIBLEsensors[_slot].weight = (float)_packetV1->weight / 200.0f; } if (MIBLEsensors[_slot].weight_removed) { MIBLEsensors[_slot].weight = 0.0f; } // Can be changed to memcpy or smthng else ? MIBLEsensors[_slot].datetime.year = _packetV1->year; MIBLEsensors[_slot].datetime.month = _packetV1->month; MIBLEsensors[_slot].datetime.day = _packetV1->day; MIBLEsensors[_slot].datetime.hour = _packetV1->hour; MIBLEsensors[_slot].datetime.minute = _packetV1->minute; MIBLEsensors[_slot].datetime.second = _packetV1->second; MIBLEsensors[_slot].shallSendMQTT = 1; HM10.mode.shallTriggerTele = 1; } } } // Mi Scale V2 // Scales measure weight at first step, after weight is stable - measure impedance. // So measurement (packet) can contain 'weight' or 'weight + impedance' else if (_type == 0x181b){ // 17-1-1-2 MiScaleV2Packet_t *_packetV2 = (MiScaleV2Packet_t*)_buf; if ((_slot >= 0) && (_slot < MIBLEsensors.size())){ DEBUG_SENSOR_LOG(PSTR("HM10: %s: at slot %u"), kHM10DeviceType[MIBLEsensors[_slot].type-1],_slot); weight_stabilized = (_packetV2->status & (1 << 5)) ? 1 : 0; weight_removed = (_packetV2->status & (1 << 7)) ? 1 : 0; impedance_stabilized = (_packetV2->status & (1 << 1)) ? 1 : 0; //AddLog(LOG_LEVEL_DEBUG, PSTR("%s: MSCALE: WS %u, WR: %u, IS: %u "),D_CMND_HM10,weight_stabilized,weight_removed,impedance_stabilized); // Set sensor values for every packet in BridgedMode and for every packet with stable weight if (HM10.option.directBridgeMode || (weight_stabilized && !weight_removed)) { MIBLEsensors[_slot].has_impedance = (_packetV2->status & (1 << 1)) ? 1 : 0; MIBLEsensors[_slot].weight_stabilized = weight_stabilized; MIBLEsensors[_slot].impedance_stabilized = impedance_stabilized; MIBLEsensors[_slot].weight_removed = weight_removed; if (_packetV2->weight_unit & (1 << 4)) { strcpy(MIBLEsensors[_slot].weight_unit, PSTR("jin")); MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f; } else if (_packetV2->weight_unit == 3) { strcpy(MIBLEsensors[_slot].weight_unit, PSTR("lbs")); MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f; } else if (_packetV2->weight_unit == 2) { strcpy(MIBLEsensors[_slot].weight_unit, PSTR("kg")); MIBLEsensors[_slot].weight = (float)_packetV2->weight / 200.0f; } else { strcpy(MIBLEsensors[_slot].weight_unit, PSTR("")); MIBLEsensors[_slot].weight = (float)_packetV2->weight / 100.0f; } if (MIBLEsensors[_slot].weight_removed) { MIBLEsensors[_slot].weight = 0.0f; } //Set impedance to zero after every stable weight measurement MIBLEsensors[_slot].impedance = 0; // If impedance stable or BridgeMode - set value if (HM10.option.directBridgeMode || impedance_stabilized) { MIBLEsensors[_slot].impedance = MIBLEsensors[_slot].has_impedance ? _packetV2->impedance: 0; } // Can be changed to memcpy or smthng else ? MIBLEsensors[_slot].datetime.year = _packetV2->year; MIBLEsensors[_slot].datetime.month = _packetV2->month; MIBLEsensors[_slot].datetime.day = _packetV2->day; MIBLEsensors[_slot].datetime.hour = _packetV2->hour; MIBLEsensors[_slot].datetime.minute = _packetV2->minute; MIBLEsensors[_slot].datetime.second = _packetV2->second; MIBLEsensors[_slot].shallSendMQTT = 1; //Trigger in all cases - BridgeMode or weight_stabilized HM10.mode.shallTriggerTele = 1; } } } } void HM10ParseResponse(char *buf, uint16_t bufsize) { if (!strncmp(buf,"HMSoft",6)) { //8 const char* _fw = "000"; memcpy((void *)_fw,(void *)(buf+8),3); HM10.firmware = atoi(_fw); DEBUG_SENSOR_LOG(PSTR("%s: Firmware: %d"),D_CMND_HM10, HM10.firmware); } else if (strstr(buf, "LOST")){ HM10.current_task_delay = 0; HM10.mode.connected = false; } else if (strstr(buf, "CONNF")){ HM10.mode.connected = false; HM10.current_task_delay = 0; } else if (strstr(buf, "CONN")){ HM10.current_task_delay = 0; } else { DEBUG_SENSOR_LOG(PSTR("%s: empty response"),D_CMND_HM10); } } void HM10readHT_LY(char *_buf){ // AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t*)_buf,7); if(_buf[0]==0x4f && _buf[1]==0x4b) return; // "OK" if(_buf[0] != 0 && _buf[1] != 0){ LYWSD0x_HT_t *packet = (LYWSD0x_HT_t*)_buf; AddLog(LOG_LEVEL_DEBUG, PSTR("%s: T * 100: %u, H: %u"),D_CMND_HM10,packet->temp,packet->hum); uint32_t _slot = HM10.state.sensor; DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); static float _tempFloat; _tempFloat=(float)(packet->temp)/100.0f; if(_tempFloat<60){ MIBLEsensors[_slot].temp=_tempFloat; HM10.mode.awaiting = none; HM10.current_task_delay = 0; MIBLEsensors[_slot].showedUp=255; // this sensor is real } _tempFloat=(float)packet->hum; if(_tempFloat<100){ MIBLEsensors[_slot].hum = _tempFloat; DEBUG_SENSOR_LOG(PSTR("LYWSD0x: hum updated")); } MIBLEsensors[_slot].eventType.tempHum = 1; if (MIBLEsensors[_slot].type == HM10_LYWSD03MMC || MIBLEsensors[_slot].type == HM10_MHOC401){ MIBLEsensors[_slot].bat = ((float)packet->volt-2100.0f)/12.0f; MIBLEsensors[_slot].eventType.bat = 1; } MIBLEsensors[_slot].shallSendMQTT = 1; if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1; } } void HM10readHT_CGD1(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) return; // "OK" if(_buf[0] == 0){ if(_buf[1]==0 && _buf[2]==0 && _buf[3]==0 && _buf[4]==0) return; CGD1_HT_t *_packet = (CGD1_HT_t*)_buf; AddLog(LOG_LEVEL_DEBUG, PSTR("%s: T * 100: %u, H * 100: %u"),D_CMND_HM10,_packet->temp,_packet->hum); uint32_t _slot = HM10.state.sensor; DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); static float _tempFloat; _tempFloat=(float)(_packet->temp)/100.0f; if(_tempFloat<60){ MIBLEsensors[_slot].temp=_tempFloat; HM10.mode.awaiting = none; HM10.current_task_delay = 0; MIBLEsensors[_slot].showedUp=255; // this sensor is real } _tempFloat=(float)_packet->hum/100.0f; if(_tempFloat<100){ MIBLEsensors[_slot].hum = _tempFloat; DEBUG_SENSOR_LOG(PSTR("CGD1: hum updated")); } MIBLEsensors[_slot].eventType.tempHum = 1; MIBLEsensors[_slot].shallSendMQTT = 1; if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1; } } 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(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[_slot].temp=_tempFloat; HM10.mode.awaiting = none; HM10.current_task_delay = 0; MIBLEsensors[_slot].showedUp=255; // this sensor is real } _tempFloat=(float)_hum/10.0f; if(_tempFloat<100){ MIBLEsensors[_slot].hum = _tempFloat; DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1: hum updated")); } MIBLEsensors[_slot].eventType.tempHum = 1; MIBLEsensors[_slot].shallSendMQTT = 1; if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1; } void HM10readTLMF(char *_buf){ AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t*)_buf,16); Flora_TLMF_t *_packet = (Flora_TLMF_t*)_buf; if(_packet->ID==0xFB003C02){ // this is a magic word ... hopefully independent of FW version AddLog(LOG_LEVEL_DEBUG, PSTR("%s: T * 10: %u, L: %u, M: %u, F: %u"),D_CMND_HM10,_packet->temp,_packet->lux,_packet->moist,_packet->fert); uint32_t _slot = HM10.state.sensor; DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); MIBLEsensors[_slot].showedUp=255; // this sensor is real static float _tempFloat; _tempFloat=(float)(_packet->temp)/10.0f; MIBLEsensors[_slot].temp=_tempFloat; MIBLEsensors[_slot].lux = _packet->lux; MIBLEsensors[_slot].moisture = _packet->moist; MIBLEsensors[_slot].fertility = _packet->fert; MIBLEsensors[_slot].eventType.temp = 1; MIBLEsensors[_slot].eventType.lux = 1; MIBLEsensors[_slot].eventType.moist = 1; MIBLEsensors[_slot].eventType.fert = 1; MIBLEsensors[_slot].shallSendMQTT = 1; if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1; HM10.mode.awaiting = none; HM10.current_task_delay = 0; } } 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) return false; // "OK" uint32_t _slot = HM10.state.sensor; // if(HM10.option.ignoreBogusBattery){ // if (MIBLEsensors[_slot].type == HM10_LYWSD03MMC || MIBLEsensors[_slot].type == HM10_MHOC401) return true; // } if(_buf[0] != 0){ AddLog(LOG_LEVEL_DEBUG,PSTR("%s: Battery: %u"),D_CMND_HM10,_buf[0]); DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); if(_buf[0]<101){ MIBLEsensors[_slot].bat=_buf[0]; MIBLEsensors[_slot].showedUp=255; // this sensor is real MIBLEsensors[_slot].eventType.bat = 1; MIBLEsensors[_slot].shallSendMQTT = 1; if(HM10.option.directBridgeMode) HM10.mode.shallTriggerTele = 1; return true; } } return false; } /*********************************************************************************************\ * beacon functions \*********************************************************************************************/ /** * @brief Handle a generic BLE advertisment in a running scan or to check a beacon * * */ void HM10HandleGenericBeacon(void){ if(HM10.state.beaconScanCounter==0){ //handle beacon for(auto &_beacon : MIBLEbeacons){ if(memcmp(HM10.rxAdvertisement.MAC,_beacon.MAC,6)==0){ _beacon.time = 0; _beacon.RSSI = HM10.rxAdvertisement.RSSI; _beacon.SVC = HM10.rxAdvertisement.SVC; _beacon.CID = HM10.rxAdvertisement.CID; _beacon.UUID = HM10.rxAdvertisement.UUID; _beacon.TX = HM10.rxAdvertisement.TX; return; } } return; } // else handle scan if(MIBLEscanResult.size()>19) { AddLog(LOG_LEVEL_INFO,PSTR("HM10: Scan buffer full")); HM10.state.beaconScanCounter = 1; return; } for(auto _scanResult : MIBLEscanResult){ if(memcmp(HM10.rxAdvertisement.MAC,_scanResult.MAC,6)==0){ // AddLog(LOG_LEVEL_INFO,PSTR("HM10: known device")); return; } } MIBLEscanResult.push_back(HM10.rxAdvertisement); } /** * @brief Add a beacon defined by its MAC-address, if only zeros are given, the beacon will be deactivated * * @param index 1-4 beacons are currently supported * @param data null terminated char array representing a MAC-address in hex */ void HM10addBeacon(uint8_t index, char* data){ auto &_new = MIBLEbeacons[index-1]; //TODO: check HM10HexStringToBytes(data,_new.MAC); char _MAC[18]; ToHex_P(MIBLEbeacons[index-1].MAC,6,_MAC,18,':'); char _empty[6] = {0}; _new.time = 0; if(memcmp(_empty,_new.MAC,6) == 0){ _new.active = false; AddLog(LOG_LEVEL_INFO,PSTR("HM10: beacon%u deactivated"), index); } else{ _new.active = true; HM10.mode.activeBeacon = 1; AddLog(LOG_LEVEL_INFO,PSTR("HM10: beacon added with MAC: %s"), _MAC); } } /** * @brief Present BLE scan in the console, after that deleting the scan data * */ void HM10showScanResults(){ size_t _size = MIBLEscanResult.size(); ResponseAppend_P(PSTR(",\"BLEScan\":{\"Found\":%u,\"Devices\":["), _size); bool add_comma = false; for(auto _scanResult : MIBLEscanResult){ char _MAC[18]; ToHex_P(_scanResult.MAC,6,_MAC,18,':'); ResponseAppend_P(PSTR("%s{\"MAC\":\"%s\",\"CID\":\"0x%04x\",\"SVC\":\"0x%04x\",\"UUID\":\"0x%04x\",\"RSSI\":%d}"), (add_comma)?",":"", _MAC, _scanResult.CID, _scanResult.SVC, _scanResult.UUID, _scanResult.RSSI); add_comma = true; } ResponseAppend_P(PSTR("]}")); MIBLEscanResult.clear(); HM10.mode.shallShowScanResult = 0; } void HM10showBlockList(){ ResponseAppend_P(PSTR(",\"Block\":[")); bool add_comma = false; for(auto _scanResult : MIBLEBlockList){ char _MAC[18]; ToHex_P(_scanResult.buf,6,_MAC,18,':'); ResponseAppend_P(PSTR("%s\"%s\""), (add_comma)?",":"", _MAC); add_comma = true; } ResponseAppend_P(PSTR("]")); HM10.mode.shallShowBlockList = 0; } bool HM10isInBlockList(uint8_t* MAC){ bool isBlocked = false; for(auto &_blockedMAC : MIBLEBlockList){ if(memcmp(_blockedMAC.buf,MAC,6) == 0) isBlocked = true; } return isBlocked; } void HM10removeMIBLEsensor(uint8_t* MAC){ MIBLEsensors.erase( std::remove_if( MIBLEsensors.begin() , MIBLEsensors.end(), [MAC]( mi_sensor_t _sensor )->bool { return (memcmp(_sensor.MAC,MAC,6) == 0); } ), end( MIBLEsensors ) ); } /*********************************************************************************************\ * handle the return value from the HM10 \*********************************************************************************************/ bool HM10SerialHandleFeedback(){ // every 50 milliseconds bool success = false; uint32_t i = 0; uint32_t _targetsize = 64; //set to some save value bool _isPotentialAdv = false; bool _isValidAdv = false; char* _rx = HM10.rxBuffer; // we start always with the buffer uint32_t _nextStep = 2; // we can expect length and AD type at the first two positions anyway uint8_t length_type[64]; // length,AD-type,buffer - buffer should be too large while(HM10Serial->available() && (_targetsize!=i)) { *_rx= HM10Serial->read(); if(i==18){ if(memcmp(HM10.rxBuffer+4,"ISA:",4)==0){ //last 4 bytes of "OK+DISA:" should be safe enough // AddLog(LOG_LEVEL_DEBUG, PSTR("%s packet size: %u"),D_CMND_HM10,HM10.rxBuffer[16]); _targetsize = HM10.rxBuffer[16] + 19; // this is the size byte according to HM-10 docs if(_targetsize>64) _targetsize=64; memcpy(HM10.rxAdvertisement.MAC,HM10.rxBuffer+8,6); HM10_ReverseMAC(HM10.rxAdvertisement.MAC); HM10.rxAdvertisement.RSSI = (256 - HM10.rxBuffer[15]) * -1; length_type[0] = HM10.rxBuffer[17]; //length length_type[1] = HM10.rxBuffer[18]; //AD-type _isPotentialAdv = true; } } if(_isPotentialAdv){ // we will change the pointer now according to the AD-type and data length if(_nextStep>length_type[0]){ if(length_type[1]==0xff){ //we only want the CID from the custom data 0xff ... memcpy((uint8_t*)&HM10.rxAdvertisement.CID,length_type+2,2); // ... and leave the rest unused in the buffer } _nextStep=0; _rx = (char*)&length_type - 1; } else if(_nextStep == 2){ switch(length_type[1]){ //AD type case 0x02: case 0x03: _rx = (char*)&HM10.rxAdvertisement.UUID - 1; break; case 0x0a: if(length_type[0] == 0xd) { if(_targetsize-i == 1)_isValidAdv = true; // expected trailing bytes for a valid packet } _rx = (char*)&HM10.rxAdvertisement.TX - 1; break; case 0x16: _rx = (char*)HM10.rxAdvertisement.svcData - 1; break; } } _nextStep++; } i++; _rx++; success = true; } if(i==0){ if(HM10.mode.shallTriggerTele){ // let us use the spare time for other things HM10.mode.shallTriggerTele=0; HM10triggerTele(); } return success; } switch (HM10.mode.awaiting){ case bat: if (HM10.mode.connected) { if (HM10readBat(HM10.rxBuffer)){ HM10.mode.awaiting = none; HM10.current_task_delay = 0; } } break; case tempHumLY: if (HM10.mode.connected) HM10readHT_LY(HM10.rxBuffer); break; case tempHumCGD1: if (HM10.mode.connected) HM10readHT_CGD1(HM10.rxBuffer); break; case TLMF: if (HM10.mode.connected) HM10readTLMF(HM10.rxBuffer); break; case discScan: if(_isValidAdv) { if(HM10.state.beaconScanCounter!=0 || HM10.mode.activeBeacon){ HM10HandleGenericBeacon(); } uint16_t _type = (uint8_t)HM10.rxAdvertisement.svcData[5]*256 + (uint8_t)HM10.rxAdvertisement.svcData[4]; // AddLog(LOG_LEVEL_DEBUG, PSTR("%04x %02x %04x %04x %04x"),HM10.rxAdvertisement.UUID,HM10.rxAdvertisement.TX,HM10.rxAdvertisement.CID,HM10.rxAdvertisement.SVC, _type); DEBUG_SENSOR_LOG(PSTR("HM10: UUID %04x, TX: %02x, CID: %04x, SVC: %04x"), HM10.rxAdvertisement.UUID,HM10.rxAdvertisement.TX,HM10.rxAdvertisement.CID,HM10.rxAdvertisement.SVC); if(HM10.rxAdvertisement.SVC==0x181a) _type = 0xa1c; else if(HM10.rxAdvertisement.SVC==0xfdcd) _type = 0x0576; else if(HM10.rxAdvertisement.SVC==0x181b || HM10.rxAdvertisement.SVC==0x181d) _type = 0x181b; uint16_t _slot = MIBLEgetSensorSlot(HM10.rxAdvertisement.MAC, _type, HM10.rxAdvertisement.RSSI); if(_slot!=0xff){ if (_type==0xa1c) HM10parseATC((char*)HM10.rxAdvertisement.svcData+2,_slot); else if (_type==0x0576) HM10parseCGD1Packet((char*)HM10.rxAdvertisement.svcData+2,_slot); else if (_type==0x181b) HM10ParseMiScalePacket((char*)HM10.rxAdvertisement.svcData+2,_slot, HM10.rxAdvertisement.SVC); else HM10parseMiBeacon((char*)HM10.rxAdvertisement.svcData+2,_slot); } else{ // AddLogBuffer(LOG_LEVEL_INFO,(uint8_t*)HM10.rxAdvertisement.svcData,16); } } break; case tempHumMJ: if (HM10.mode.connected) HM10readHT_MJ_HT_V1(HM10.rxBuffer); break; case none: if(success) { AddLog(LOG_LEVEL_DEBUG, PSTR("%s: response: %s"),D_CMND_HM10, (char *)HM10.rxBuffer); // for(uint32_t j = 0; jwrite("AT+ROLE1"); break; case TASK_HM10_IMME1: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: set imme to 1"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write("AT+IMME1"); break; case TASK_HM10_DISC: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: start discovery"),D_CMND_HM10); HM10.current_task_delay = 90; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10.mode.awaiting = discScan; HM10Serial->write("AT+DISA?"); break; case TASK_HM10_VERSION: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: read version"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write("AT+VERR?"); break; case TASK_HM10_NAME: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: read name"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write("AT+NAME?"); break; case TASK_HM10_CONN: char _con[20]; sprintf_P(_con,"AT+CON%02x%02x%02x%02x%02x%02x",MIBLEsensors[HM10.state.sensor].MAC[0],MIBLEsensors[HM10.state.sensor].MAC[1],MIBLEsensors[HM10.state.sensor].MAC[2],MIBLEsensors[HM10.state.sensor].MAC[3],MIBLEsensors[HM10.state.sensor].MAC[4],MIBLEsensors[HM10.state.sensor].MAC[5]); AddLog(LOG_LEVEL_DEBUG, PSTR("%s: %s connect %s"),D_CMND_HM10,kHM10DeviceType[MIBLEsensors[HM10.state.sensor].type-1],_con); HM10.current_task_delay = 2; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write(_con); HM10.mode.awaiting = none; HM10.mode.connected = true; break; case TASK_HM10_DISCONN: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: disconnect"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write("AT"); break; case TASK_HM10_RESET: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: Reset Device"),D_CMND_HM10); HM10Serial->write("AT+RESET"); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; break; case TASK_HM10_SUB_L3: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: subscribe"),D_CMND_HM10); HM10.current_task_delay = 25; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); HM10.mode.awaiting = tempHumLY; runningTaskLoop = false; HM10Serial->write("AT+NOTIFY_ON0037"); break; case TASK_HM10_UN_L3: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: un-subscribe"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10.mode.awaiting = none; HM10Serial->write("AT+NOTIFYOFF0037"); break; case TASK_HM10_SUB_L2: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: subscribe"),D_CMND_HM10); HM10.current_task_delay = 85; // set task delay HM10.mode.awaiting = tempHumLY; HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; if(MIBLEsensors[HM10.state.sensor].type == HM10_LYWSD02) HM10Serial->write("AT+NOTIFY_ON003C"); else HM10Serial->write("AT+NOTIFY_ON004B"); //MHO-C303 break; case TASK_HM10_UN_L2: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: un-subscribe"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10.mode.awaiting = none; if(MIBLEsensors[HM10.state.sensor].type == HM10_LYWSD02) HM10Serial->write("AT+NOTIFY_OFF003C"); else HM10Serial->write("AT+NOTIFY_OFF004B"); //MHO-C303 break; case TASK_HM10_TIME_L2: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: set time"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10.time = Rtc.utc_time; HM10Serial->write("AT+SEND_DATAWR002F"); HM10Serial->write(HM10.timebuf,4); HM10Serial->write(Rtc.time_timezone / 60); AddLog(LOG_LEVEL_DEBUG,PSTR("%s Time-string: %x%x%x%x%x"),D_CMND_HM10, HM10.timebuf[0],HM10.timebuf[1],HM10.timebuf[2],HM10.timebuf[3],(Rtc.time_timezone /60)); break; // case TASK_HM10_READ_BT_L3: // AddLog(LOG_LEVEL_DEBUG, PSTR("%s: read handle 003A"),D_CMND_HM10); // HM10.current_task_delay = 2; // set task delay // HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); // runningTaskLoop = false; // HM10Serial->write("AT+READDATA003A?"); // HM10.mode.awaiting = bat; // break; case TASK_HM10_READ_BT_L2: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: read handle 0043"),D_CMND_HM10); HM10.current_task_delay = 2; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; if(MIBLEsensors[HM10.state.sensor].type == HM10_LYWSD02) HM10Serial->write("AT+READDATA0043?"); else HM10Serial->write("AT+READDATA0052?"); //MHO-C303 HM10.mode.awaiting = bat; break; case TASK_HM10_READ_BF_FL: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: read handle 0038"),D_CMND_HM10); HM10.current_task_delay = 2; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write("AT+READDATA0038?"); HM10.mode.awaiting = bat; break; case TASK_HM10_CALL_TLMF_FL: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: write to handle 0033"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_READ_TLMF_FL,i); runningTaskLoop = false; HM10Serial->write("AT+SEND_DATAWR0033"); HM10Serial->write(0xa0); HM10Serial->write(0x1f); HM10.mode.awaiting = none; break; case TASK_HM10_READ_TLMF_FL: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: read handle 0035"),D_CMND_HM10); HM10.current_task_delay = 2; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write("AT+READDATA0035?"); HM10.mode.awaiting = TLMF; break; case TASK_HM10_READ_B_CGD1: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: read handle 0011"),D_CMND_HM10); HM10.current_task_delay = 2; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write("AT+READDATA0011?"); HM10.mode.awaiting = bat; break; case TASK_HM10_SUB_HT_CGD1: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: subscribe 4b"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10.mode.awaiting = tempHumCGD1; HM10Serial->write("AT+NOTIFY_ON004b"); break; case TASK_HM10_UN_HT_CGD1: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: un-subscribe 4b"),D_CMND_HM10); HM10.current_task_delay = 5; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10.mode.awaiting = none; HM10Serial->write("AT+NOTIFYOFF004b"); break; case TASK_HM10_SCAN9: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: scan time to 9"),D_CMND_HM10); HM10.current_task_delay = 2; // set task delay HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i); runningTaskLoop = false; HM10Serial->write("AT+SCAN9"); break; case TASK_HM10_READ_B_MJ: AddLog(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(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; HM10Serial->write("AT+NOTIFY_ON000F"); HM10.mode.awaiting = tempHumMJ; break; case TASK_HM10_FEEDBACK: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: get response"),D_CMND_HM10); HM10SerialHandleFeedback(); HM10.current_task_delay = HM10_TASK_LIST[i+1][1];; // set task delay HM10_TASK_LIST[i][0] = TASK_HM10_DONE; // no feedback for reset runningTaskLoop = false; break; case TASK_HM10_STATUS_EVENT: AddLog(LOG_LEVEL_DEBUG, PSTR("%s: show status"),D_CMND_HM10); HM10StatusInfo(); HM10.current_task_delay = HM10_TASK_LIST[i+1][1];; // set task delay HM10_TASK_LIST[i][0] = TASK_HM10_DONE; // no feedback for reset runningTaskLoop = false; break; case TASK_HM10_DONE: // this entry was already handled // AddLog(LOG_LEVEL_DEBUG, PSTR("%sFound done HM10_TASK"),D_CMND_HM10); // AddLog(LOG_LEVEL_DEBUG, PSTR("%snext slot:%u, i: %u"),D_CMND_HM10, HM10_TASK_LIST[i+1][0],i); if(HM10_TASK_LIST[i+1][0] == TASK_HM10_NOTASK) { // check the next entry and if there is none DEBUG_SENSOR_LOG(PSTR("%s: no Tasks left"),D_CMND_HM10); DEBUG_SENSOR_LOG(PSTR("%s: HM10_TASK_DONE current slot %u"),D_CMND_HM10, i); for (uint8_t j = 0; j < HM10_MAX_TASK_NUMBER+1; j++) { // do a clean-up: DEBUG_SENSOR_LOG(PSTR("%s: HM10_TASK cleanup slot %u"),D_CMND_HM10, j); HM10_TASK_LIST[j][0] = TASK_HM10_NOTASK; // reset all task entries HM10_TASK_LIST[j][1] = 0; // reset all delays } runningTaskLoop = false; // return to main loop HM10.mode.pending_task = 0; // back to main loop control break; } } i++; } } else { HM10.current_task_delay--; // count down every 100 ms } } void HM10StatusInfo() { /* char stemp[20]; snprintf_P(stemp, sizeof(stemp),PSTR("{%s:{\"found\": %u}}"),D_CMND_HM10, MIBLEsensors.size()); AddLog(LOG_LEVEL_INFO, stemp); RulesProcessEvent(stemp); */ Response_P(PSTR("{\"%s\":{\"found\":%u}}"), D_CMND_HM10, MIBLEsensors.size()); XdrvRulesProcess(0); } /** * @brief Main loop of the driver, "high level"-loop * */ 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; } uint32_t _idx = 0; uint32_t _activeBeacons = 0; for (auto &_beacon : MIBLEbeacons){ _idx++; if(_beacon.active == false) continue; _activeBeacons++; _beacon.time++; Response_P(PSTR("{\"Beacon%u\":{\"Time\":%u}}"), _idx, _beacon.time); XdrvRulesProcess(0); } if(_activeBeacons==0) HM10.mode.activeBeacon = 0; if(HM10.state.beaconScanCounter!=0){ HM10.state.beaconScanCounter--; if(HM10.state.beaconScanCounter==0){ HM10.mode.shallShowScanResult = 1; HM10triggerTele(); } } if(HM10.firmware == 0) return; if(HM10.mode.pending_task == 1) return; if(MIBLEsensors.size()==0 && !HM10.mode.autoScan) return; if((HM10.period-_counter)>15 && HM10.mode.autoScan) { if(_counter-_lastDiscovery>HM10.autoScanInterval){ HM10_Discovery_Scan(); HM10.mode.pending_task = 1; _counter+=12; _lastDiscovery = _counter; return; } } if(_counter==0) { if(MIBLEsensors.size() == 0){ _counter++; return; } HM10.state.sensor = _nextSensorSlot; _nextSensorSlot++; HM10.mode.pending_task = 1; switch(MIBLEsensors[HM10.state.sensor].type){ case HM10_FLORA: HM10_Read_Flora(); break; case HM10_MJ_HT_V1: case HM10_CGG1: HM10_Read_MJ_HT_V1(); break; case HM10_LYWSD02: case HM10_MHOC303: HM10_Read_LYWSD02(); break; case HM10_LYWSD03MMC: case HM10_MHOC401: HM10_Read_LYWSD03(); break; case HM10_CGD1: HM10_Read_CGD1(); break; default: HM10.mode.pending_task = 0; } if (HM10.state.sensor==MIBLEsensors.size()-1) { _nextSensorSlot= 0; _counter++; } DEBUG_SENSOR_LOG(PSTR("%s: active sensor now: %u"),D_CMND_HM10, HM10.state.sensor); } else _counter++; if (_counter>HM10.period) { _counter = 0; _lastDiscovery = 0; } } /** * @brief trigger real-time message * */ void HM10triggerTele(void){ HM10.mode.triggeredTele = 1; MqttPublishTeleperiodSensor(); } /*********************************************************************************************\ * Commands \*********************************************************************************************/ void CmndHM10Period() { if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.payload==1) { HM10EverySecond(true); XdrvMailbox.payload = HM10.period; } else { HM10.period = XdrvMailbox.payload; } } else { XdrvMailbox.payload = HM10.period; } ResponseCmndNumber(HM10.period); } void CmndHM10Auto() { if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.payload>0) { HM10.mode.autoScan = 1; HM10.autoScanInterval = XdrvMailbox.payload; } else { HM10.mode.autoScan = 0; HM10.autoScanInterval = 0; } } else { XdrvMailbox.payload = HM10.autoScanInterval; } ResponseCmndNumber(HM10.autoScanInterval); } void CmndHM10Baud() { if (XdrvMailbox.data_len > 0) { HM10.serialSpeed = XdrvMailbox.payload; HM10Serial->begin(HM10.serialSpeed); } else { XdrvMailbox.payload = HM10.serialSpeed; } ResponseCmndNumber(HM10.serialSpeed); } void CmndHM10Time() { if (XdrvMailbox.data_len > 0) { if(MIBLEsensors.size()>XdrvMailbox.payload){ if(MIBLEsensors[XdrvMailbox.payload].type == HM10_LYWSD02){ HM10.state.sensor = XdrvMailbox.payload; HM10_Time_LYWSD02(); } } } ResponseCmndNumber(XdrvMailbox.payload); } void CmndHM10Page() { if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.payload == 0) XdrvMailbox.payload = HM10.perPage; // ignore 0 HM10.perPage = XdrvMailbox.payload; } else XdrvMailbox.payload = HM10.perPage; ResponseCmndNumber(HM10.perPage); } void CmndHM10AT() { HM10Serial->write("AT"); // without an argument this will disconnect if (strlen(XdrvMailbox.data)!=0) { HM10Serial->write("+"); HM10Serial->write(XdrvMailbox.data); // pass everything without checks Response_P(S_JSON_HM10_COMMAND, ":AT+",XdrvMailbox.data); } else Response_P(S_JSON_HM10_COMMAND, ":AT",XdrvMailbox.data); } void CmndHM10Scan() { HM10_Discovery_Scan(); ResponseCmndDone(); } void CmndHM10Beacon() { if (XdrvMailbox.data_len == 0) { switch(XdrvMailbox.index){ case 0: HM10.state.beaconScanCounter = 8; ResponseCmndIdxChar(PSTR("Scanning...")); break; case 1: case 2: case 3: case 4: char _MAC[18]; ResponseCmndIdxChar(ToHex_P(MIBLEbeacons[XdrvMailbox.index-1].MAC, 6, _MAC, 18, ':')); break; } } else { if(XdrvMailbox.data_len == 12 || XdrvMailbox.data_len == 17){ // MAC-string without or with colons switch(XdrvMailbox.index){ case 1: case 2: case 3: case 4: HM10addBeacon(XdrvMailbox.index,XdrvMailbox.data); break; } } ResponseCmndIdxChar(XdrvMailbox.data); } } void CmndHM10Block(void){ if (XdrvMailbox.data_len == 0) { switch (XdrvMailbox.index) { case 0: MIBLEBlockList.clear(); // AddLog(LOG_LEVEL_INFO,PSTR("HM10: size of ilist: %u"), MIBLEBlockList.size()); ResponseCmndIdxChar(PSTR("block list cleared")); break; case 1: ResponseCmndIdxChar(PSTR("show block list")); break; } } else { MAC_t _MACasBytes; HM10HexStringToBytes(XdrvMailbox.data,_MACasBytes.buf); switch (XdrvMailbox.index) { case 0: MIBLEBlockList.erase( std::remove_if( begin( MIBLEBlockList ), end( MIBLEBlockList ), [_MACasBytes]( MAC_t& _entry )->bool { return (memcmp(_entry.buf,_MACasBytes.buf,6) == 0); } ), end( MIBLEBlockList ) ); ResponseCmndIdxChar(PSTR("MAC not blocked anymore")); break; case 1: bool _notYetInList = true; for (auto &_entry : MIBLEBlockList) { if (memcmp(_entry.buf,_MACasBytes.buf,6) == 0){ _notYetInList = false; } } if (_notYetInList) { MIBLEBlockList.push_back(_MACasBytes); ResponseCmndIdxChar(XdrvMailbox.data); HM10removeMIBLEsensor(_MACasBytes.buf); } // AddLog(LOG_LEVEL_INFO,PSTR("HM10: size of ilist: %u"), MIBLEBlockList.size()); break; } } HM10.mode.shallShowBlockList = 1; HM10triggerTele(); } void CmndHM10Option(void){ bool onOff = atoi(XdrvMailbox.data); switch(XdrvMailbox.index) { case 0: HM10.option.allwaysAggregate = onOff; break; case 1: HM10.option.noSummary = onOff; break; case 2: HM10.option.directBridgeMode = onOff; break; } ResponseCmndDone(); } /*********************************************************************************************\ * Presentation \*********************************************************************************************/ const char HTTP_HM10[] PROGMEM = "{s}HM10 FW%u V0960{m}%u%s / %u{e}"; const char HTTP_HM10_MAC[] PROGMEM = "{s}%s %s{m}%s{e}"; const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u%%{e}"; const char HTTP_RSSI[] PROGMEM = "{s}%s " D_RSSI "{m}%d dBm{e}"; const char HTTP_HM10_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%u us/cm{e}"; const char HTTP_HM10_HL[] PROGMEM = "{s}
{m}
{e}"; void HM10ShowContinuation(bool *commaflg) { if (*commaflg) { ResponseAppend_P(PSTR(",")); } else { *commaflg = true; } } void HM10Show(bool json) { if (json) { if(HM10.mode.shallShowScanResult) { return HM10showScanResults(); } else if(HM10.mode.shallShowBlockList) { return HM10showBlockList(); } #ifdef USE_HOME_ASSISTANT bool _noSummarySave = HM10.option.noSummary; bool _minimalSummarySave = HM10.option.minimalSummary; if(hass_mode==2){ if(HM10.option.holdBackFirstAutodiscovery){ if(!HM10.mode.firstAutodiscoveryDone){ HM10.mode.firstAutodiscoveryDone = 1; return; } } HM10.option.noSummary = false; HM10.option.minimalSummary = false; } #endif //USE_HOME_ASSISTANT if(!HM10.mode.triggeredTele){ if(HM10.option.noSummary) return; // no message at TELEPERIOD } for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { if(HM10.mode.triggeredTele && MIBLEsensors[i].eventType.raw == 0) continue; if(HM10.mode.triggeredTele && MIBLEsensors[i].shallSendMQTT==0) continue; bool commaflg = false; ResponseAppend_P(PSTR(",\"%s-%02x%02x%02x\":{"), kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].MAC[3], MIBLEsensors[i].MAC[4], MIBLEsensors[i].MAC[5]); if((!HM10.mode.triggeredTele && !HM10.option.minimalSummary)||HM10.mode.triggeredTele){ bool tempHumSended = false; if(MIBLEsensors[i].feature.tempHum){ if(MIBLEsensors[i].eventType.tempHum || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate){ if (!isnan(MIBLEsensors[i].hum) && !isnan(MIBLEsensors[i].temp) #ifdef USE_HOME_ASSISTANT ||(hass_mode!=-1) #endif //USE_HOME_ASSISTANT ) { HM10ShowContinuation(&commaflg); ResponseAppendTHD(MIBLEsensors[i].temp, MIBLEsensors[i].hum); tempHumSended = true; } } } if(MIBLEsensors[i].feature.temp && !tempHumSended){ if(MIBLEsensors[i].eventType.temp || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate) { if (!isnan(MIBLEsensors[i].temp) #ifdef USE_HOME_ASSISTANT ||(hass_mode!=-1) #endif //USE_HOME_ASSISTANT ) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%*_f"), Settings->flag2.temperature_resolution, &MIBLEsensors[i].temp); } } } if(MIBLEsensors[i].feature.hum && !tempHumSended){ if(MIBLEsensors[i].eventType.hum || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate) { if (!isnan(MIBLEsensors[i].hum) #ifdef USE_HOME_ASSISTANT ||(hass_mode!=-1) #endif //USE_HOME_ASSISTANT ) { char hum[FLOATSZ]; dtostrfd(MIBLEsensors[i].hum, Settings->flag2.humidity_resolution, hum); HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"" D_JSON_HUMIDITY "\":%s"), hum); } } } if (MIBLEsensors[i].feature.lux){ if(MIBLEsensors[i].eventType.lux || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate){ #ifdef USE_HOME_ASSISTANT if ((hass_mode != -1) && (MIBLEsensors[i].lux == 0x0ffffff)) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"" D_JSON_ILLUMINANCE "\":null")); } else #endif //USE_HOME_ASSISTANT if ((MIBLEsensors[i].lux != 0x0ffffff) #ifdef USE_HOME_ASSISTANT || (hass_mode != -1) #endif //USE_HOME_ASSISTANT ) { // this is the error code -> no lux HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"" D_JSON_ILLUMINANCE "\":%u"), MIBLEsensors[i].lux); } } } if (MIBLEsensors[i].feature.moist){ if(MIBLEsensors[i].eventType.moist || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate){ #ifdef USE_HOME_ASSISTANT if ((hass_mode != -1) && (MIBLEsensors[i].moisture == 0xff)) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"" D_JSON_MOISTURE "\":null")); } else #endif //USE_HOME_ASSISTANT if ((MIBLEsensors[i].moisture != 0xff) #ifdef USE_HOME_ASSISTANT || (hass_mode != -1) #endif //USE_HOME_ASSISTANT ) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"" D_JSON_MOISTURE "\":%u"), MIBLEsensors[i].moisture); } } } if (MIBLEsensors[i].feature.fert){ if(MIBLEsensors[i].eventType.fert || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate){ #ifdef USE_HOME_ASSISTANT if ((hass_mode != -1) && (MIBLEsensors[i].fertility == 0xffff)) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Fertility\":null")); } else #endif //USE_HOME_ASSISTANT if ((MIBLEsensors[i].fertility != 0xffff) #ifdef USE_HOME_ASSISTANT || (hass_mode != -1) #endif //USE_HOME_ASSISTANT ) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Fertility\":%u"), MIBLEsensors[i].fertility); } } } if (MIBLEsensors[i].feature.Btn){ if(MIBLEsensors[i].eventType.Btn){ HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Btn\":%u"),MIBLEsensors[i].Btn); } } } // minimal summary if (MIBLEsensors[i].feature.PIR){ if(MIBLEsensors[i].eventType.motion || !HM10.mode.triggeredTele){ if(HM10.mode.triggeredTele) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"PIR\":1")); // only real-time } HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Events\":%u"),MIBLEsensors[i].events); } else if(MIBLEsensors[i].eventType.noMotion && HM10.mode.triggeredTele){ HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"PIR\":0")); } } if (MIBLEsensors[i].type == HM10_FLORA && !HM10.mode.triggeredTele) { if (MIBLEsensors[i].firmware[0] != '\0') { // this is the error code -> no firmware HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Firmware\":\"%s\""), MIBLEsensors[i].firmware); } } if (MIBLEsensors[i].feature.NMT || !HM10.mode.triggeredTele){ if(MIBLEsensors[i].eventType.NMT){ HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"NMT\":%u"), MIBLEsensors[i].NMT); } } if (MIBLEsensors[i].feature.bat){ if(MIBLEsensors[i].eventType.bat || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate){ #ifdef USE_HOME_ASSISTANT if ((hass_mode != -1) && (MIBLEsensors[i].bat == 0x00)) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Battery\":null")); } else #endif //USE_HOME_ASSISTANT if ((MIBLEsensors[i].bat != 0x00) #ifdef USE_HOME_ASSISTANT || (hass_mode != -1) #endif //USE_HOME_ASSISTANT ) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Battery\":%u"), MIBLEsensors[i].bat); } } } //Scale if (MIBLEsensors[i].feature.scale){ if(MIBLEsensors[i].eventType.scale || !HM10.mode.triggeredTele || HM10.option.allwaysAggregate #ifdef USE_HOME_ASSISTANT ||(hass_mode==2) #endif //USE_HOME_ASSISTANT ){ HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"wgh_removed\":%u"), MIBLEsensors[i].weight_removed); HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"wgh_stabilized\":%u"), MIBLEsensors[i].weight_stabilized); HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"wgh_unit\":\"%s\""), MIBLEsensors[i].weight_unit); HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"" D_JSON_WEIGHT "\":%*_f"),Settings->flag2.weight_resolution, &MIBLEsensors[i].weight); HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"dtime\":\"%02u/%02u/%04u %02u:%02u:%02u\"") , MIBLEsensors[i].datetime.day , MIBLEsensors[i].datetime.month , MIBLEsensors[i].datetime.year , MIBLEsensors[i].datetime.hour , MIBLEsensors[i].datetime.minute , MIBLEsensors[i].datetime.second ); } } if (MIBLEsensors[i].feature.impedance){ HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"impedance\":%u"), MIBLEsensors[i].has_impedance ? MIBLEsensors[i].impedance : 0); HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"imp_stabilized\":%u"), MIBLEsensors[i].impedance_stabilized); } if (HM10.option.showRSSI) { HM10ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"RSSI\":%d"), MIBLEsensors[i].rssi); } ResponseJsonEnd(); MIBLEsensors[i].eventType.raw = 0; if(MIBLEsensors[i].shallSendMQTT==1){ MIBLEsensors[i].shallSendMQTT = 0; continue; } } HM10.mode.triggeredTele = 0; // add beacons uint32_t _idx = 0; for (auto _beacon : MIBLEbeacons){ _idx++; if(!_beacon.active) continue; char _MAC[18]; ToHex_P(_beacon.MAC,6,_MAC,18,':'); ResponseAppend_P(PSTR(",\"Beacon%u\":{\"MAC\":\"%s\",\"CID\":\"0x%04x\",\"SVC\":\"0x%04x\"," "\"UUID\":\"0x%04x\",\"Time\":%u,\"RSSI\":%d,\"TX\":%u}"), _idx,_MAC,_beacon.CID,_beacon.SVC,_beacon.UUID,_beacon.time,_beacon.RSSI,_beacon.TX); } #ifdef USE_HOME_ASSISTANT if(hass_mode==2){ HM10.option.noSummary = _noSummarySave; HM10.option.minimalSummary = _minimalSummarySave; } #endif //USE_HOME_ASSISTANT #ifdef USE_WEBSERVER } else { 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, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].lux); } if(MIBLEsensors[i].moisture!=0xff){ WSContentSend_PD(HTTP_SNS_MOISTURE, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].moisture); } if(MIBLEsensors[i].fertility!=0xffff){ WSContentSend_PD(HTTP_HM10_FLORA_DATA, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].fertility); } } if (MIBLEsensors[i].type==HM10_MI_SCALE_V1 || MIBLEsensors[i].type==HM10_MI_SCALE_V2){ if (MIBLEsensors[i].feature.scale){ WSContentSend_PD(HTTP_MISCALE_WEIGHT, kHM10DeviceType[MIBLEsensors[i].type-1], Settings->flag2.weight_resolution, &MIBLEsensors[i].weight, MIBLEsensors[i].weight_unit); WSContentSend_PD(HTTP_MISCALE_WEIGHT_REMOVED, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].weight_removed ? PSTR("yes") : PSTR("no")); WSContentSend_PD(HTTP_MISCALE_STABILIZED, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].weight_stabilized ? PSTR("yes") : PSTR("no")); } if (MIBLEsensors[i].feature.impedance){ WSContentSend_PD(HTTP_MISCALE_IMPEDANCE, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].has_impedance ? MIBLEsensors[i].impedance : 0); } } else if (MIBLEsensors[i].type>HM10_FLORA){ // everything "above" Flora if(!isnan(MIBLEsensors[i].hum) && !isnan(MIBLEsensors[i].temp)){ WSContentSend_THD(kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp, MIBLEsensors[i].hum); } } if(MIBLEsensors[i].bat!=0x00){ WSContentSend_PD(HTTP_BATTERY, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].bat); } WSContentSend_PD(HTTP_RSSI, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].rssi); } _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; //always at the bottom of the page uint32_t _idx=0; if(HM10.mode.activeBeacon){ WSContentSend_PD(HTTP_HM10_HL); char _sbeacon[] = "Beacon1"; for (auto &_beacon : MIBLEbeacons){ _idx++; if(!_beacon.active) continue; WSContentSend_PD(HTTP_HM10_HL); _sbeacon[6] = _idx + 0x30; char _MAC[18]; ToHex_P(_beacon.MAC,6,_MAC,18,':'); WSContentSend_PD(HTTP_HM10_MAC, _sbeacon, D_MAC_ADDRESS, _MAC); WSContentSend_PD(HTTP_RSSI, _sbeacon, _beacon.RSSI); if(_beacon.CID!=0) WSContentSend_PD(PSTR("{s}Beacon%u CID{m}0x%04X{e}"),_idx, _beacon.CID); if(_beacon.SVC!=0) WSContentSend_PD(PSTR("{s}Beacon%u SVC{m}0x%04X{e}"),_idx, _beacon.SVC); if(_beacon.UUID!=0) WSContentSend_PD(PSTR("{s}Beacon%u UUID{m}0x%04X{e}"),_idx, _beacon.UUID); if(_beacon.TX!=0) WSContentSend_PD(PSTR("{s}Beacon%u TX{m}%u{e}"),_idx, _beacon.TX); WSContentSend_PD(PSTR("{s}Beacon%u Time{m}%u seconds{e}"),_idx, _beacon.time); } } #endif // USE_WEBSERVER } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xsns62(uint8_t function) { bool result = false; if (PinUsed(GPIO_HM10_RX) && PinUsed(GPIO_HM10_TX)) { switch (function) { case FUNC_INIT: HM10SerialInit(); // init and start communication break; case FUNC_EVERY_50_MSECOND: HM10SerialHandleFeedback(); // check for device feedback very often break; case FUNC_EVERY_100_MSECOND: if (HM10_TASK_LIST[0][0] != TASK_HM10_NOTASK) { HM10_TaskEvery100ms(); // something has to be done, we'll check in the next step } break; case FUNC_EVERY_SECOND: HM10EverySecond(false); break; case FUNC_COMMAND: result = DecodeCommand(kHM10_Commands, HM10_Commands); break; case FUNC_JSON_APPEND: HM10Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: HM10Show(0); break; #endif // USE_WEBSERVER } } return result; } #endif // USE_HM10 #endif // ESP8266