/*
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)
};
/* union {
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;
};
} scale;*/
};
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 FLORA 1
#define MJ_HT_V1 2
#define LYWSD02 3
#define LYWSD03MMC 4
#define CGG1 5
#define CGD1 6
#define NLIGHT 7
#define MJYD2S 8
#define YEERC 9
#define MHOC401 10
#define MHOC303 11
#define ATC 12
#define MI_SCALE_V1 13
#define 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 ="MISCALEV1";
const char kHM10DeviceType14[] PROGMEM ="MISCALEV2";
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==MJ_HT_V1 || MIBLEsensors[_slot].type==CGG1 || MIBLEsensors[_slot].type==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==LYWSD03MMC || MIBLEsensors[_slot].type==CGD1 || MIBLEsensors[_slot].type==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==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){
MiScaleV1Packet_t *_packetV1 = (MiScaleV1Packet_t*)_buf;
MiScaleV2Packet_t *_packetV2 = (MiScaleV2Packet_t*)_buf;
// Mi Scale V1
if (_type == 0x181d){ // 14-1-1-2
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;
MIBLEsensors[_slot].weight_stabilized = (_packetV1->status & (1 << 5)) ? 1 : 0;
MIBLEsensors[_slot].weight_removed = (_packetV1->status & (1 << 7)) ? 1 : 0;
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 = _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;
bool triggerTele = MIBLEsensors[_slot].weight_stabilized && ! MIBLEsensors[_slot].weight_removed && MIBLEsensors[_slot].weight > 0;
if(HM10.option.directBridgeMode || triggerTele) HM10.mode.shallTriggerTele = 1;
}
}
// Mi Scale V2
else if (_type == 0x181b){ // 17-1-1-2
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;
MIBLEsensors[_slot].has_impedance = (_packetV2->status & (1 << 1)) ? 1 : 0;
MIBLEsensors[_slot].weight_stabilized = (_packetV2->status & (1 << 5)) ? 1 : 0;
MIBLEsensors[_slot].impedance_stabilized = (_packetV2->status & (1 << 1)) ? 1 : 0;
MIBLEsensors[_slot].weight_removed = (_packetV2->status & (1 << 7)) ? 1 : 0;
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;
MIBLEsensors[_slot].impedance = 0;
}
else if (MIBLEsensors[_slot].has_impedance) {
MIBLEsensors[_slot].impedance = _packetV2->impedance;
}
// 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;
bool triggerTele = MIBLEsensors[_slot].weight_stabilized && ! MIBLEsensors[_slot].weight_removed && MIBLEsensors[_slot].weight > 0;
if(HM10.option.directBridgeMode || triggerTele) 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 == LYWSD03MMC || MIBLEsensors[_slot].type == 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 == LYWSD03MMC || MIBLEsensors[_slot].type == 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, _type);
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 == 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 == 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 == 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 FLORA:
HM10_Read_Flora();
break;
case MJ_HT_V1: case CGG1:
HM10_Read_MJ_HT_V1();
break;
case LYWSD02: case MHOC303:
HM10_Read_LYWSD02();
break;
case LYWSD03MMC: case MHOC401:
HM10_Read_LYWSD03();
break;
case 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 == 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 == 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("\"weight_removed\":%u"), MIBLEsensors[i].weight_removed);
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"weight_stabilized\":%u"), MIBLEsensors[i].weight_stabilized);
HM10ShowContinuation(&commaflg);
ResponseAppend_P(PSTR("\"weight_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("\"datetime\":\"%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("\"impedance_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==MI_SCALE_V1 || MIBLEsensors[i].type==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>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