diff --git a/lib/libesp32/berry/default/be_modtab.c b/lib/libesp32/berry/default/be_modtab.c index 05a882028..989cad873 100644 --- a/lib/libesp32/berry/default/be_modtab.c +++ b/lib/libesp32/berry/default/be_modtab.c @@ -48,6 +48,11 @@ be_extern_native_module(lv_extra); be_extern_native_module(lv_tasmota); #endif // USE_LVGL +#if defined(USE_MI_ESP32) && !defined(USE_BLE_ESP32) +extern void be_load_MI32_class(bvm *vm); +extern void be_load_BLE_class(bvm *vm); +#endif //USE_MI_ESP32 + /* user-defined modules declare start */ /* user-defined modules declare end */ @@ -229,5 +234,9 @@ BERRY_API void be_load_custom_libs(bvm *vm) be_load_lv_wifi_arcs_icon_class(vm); be_load_lv_clock_icon_class(vm); #endif // USE_LVGL +#if defined(USE_MI_ESP32) && !defined(USE_BLE_ESP32) + be_load_MI32_class(vm); + be_load_BLE_class(vm); +#endif //USE_MI_ESP32 } #endif diff --git a/lib/libesp32/berry_tasmota/src/be_MI32_lib.c b/lib/libesp32/berry_tasmota/src/be_MI32_lib.c new file mode 100644 index 000000000..4714f21a9 --- /dev/null +++ b/lib/libesp32/berry_tasmota/src/be_MI32_lib.c @@ -0,0 +1,72 @@ +/******************************************************************** + * Tasmota lib + * + * To use: `import MI32` + *******************************************************************/ +#include "be_constobj.h" + +#ifdef USE_MI_ESP32 +extern int be_MI32_devices(bvm *vm); +extern int be_MI32_set_bat(bvm *vm); +extern int be_MI32_get_name(bvm *vm); +extern int be_MI32_get_MAC(bvm *vm); +extern int be_MI32_set_hum(bvm *vm); +extern int be_MI32_set_temp(bvm *vm); + +/******************************************************************** +** Solidified class: MI32 +********************************************************************/ +be_local_class(MI32, + 0, + NULL, + be_nested_map(6, + ( (struct bmapnode*) &(const bmapnode[]) { + { be_nested_key("devices", -1593144448, 7, -1), be_const_func(be_MI32_devices) }, + { be_nested_key("set_bat", -1558299945, 7, -1), be_const_func(be_MI32_set_bat) }, + { be_nested_key("set_hum", 964296026, 7, 4), be_const_func(be_MI32_set_hum) }, + { be_nested_key("get_MAC", 2091521771, 7, -1), be_const_func(be_MI32_get_MAC) }, + { be_nested_key("set_temp", 1952131250, 8, -1), be_const_func(be_MI32_set_temp) }, + { be_nested_key("get_name", 1616902907, 8, 3), be_const_func(be_MI32_get_name) }, + })), + (be_nested_const_str("MI32", -220693882, 4)) +); +/*******************************************************************/ + +void be_load_MI32_class(bvm *vm) { + be_pushntvclass(vm, &be_class_MI32); + be_setglobal(vm, "MI32"); + be_pop(vm, 1); +} + +extern int be_BLE_reg_conn_cb(bvm *vm); +extern int be_BLE_reg_adv_cb(bvm *vm); +extern int be_BLE_set_MAC(bvm *vm); +extern int be_BLE_set_characteristic(bvm *vm); +extern int be_BLE_run(bvm *vm); +extern int be_BLE_set_service(bvm *vm); +/******************************************************************** +** Solidified class: BLE +********************************************************************/ +be_local_class(BLE, + 0, + NULL, + be_nested_map(6, + ( (struct bmapnode*) &(const bmapnode[]) { + { be_nested_key("conn_cb", 1381122945, 7, -1), be_const_func(be_BLE_reg_conn_cb) }, + { be_nested_key("set_svc", 752734654, 7, -1), be_const_func(be_BLE_set_service) }, + { be_nested_key("run", 718098122, 3, -1), be_const_func(be_BLE_run) }, + { be_nested_key("set_chr", 102133743, 7, 0), be_const_func(be_BLE_set_characteristic) }, + { be_nested_key("adv_cb", 1957890034, 6, 1), be_const_func(be_BLE_reg_adv_cb) }, + { be_nested_key("set_MAC", 1617581015, 7, -1), be_const_func(be_BLE_set_MAC) }, + })), + (be_nested_const_str("BLE", -361123990, 3)) +); +/*******************************************************************/ + +void be_load_BLE_class(bvm *vm) { + be_pushntvclass(vm, &be_class_BLE); + be_setglobal(vm, "BLE"); + be_pop(vm, 1); +} + +#endif //USE_MI_ESP32 diff --git a/tasmota/tasmota_configurations_ESP32.h b/tasmota/tasmota_configurations_ESP32.h index bc6620370..20600a4d2 100644 --- a/tasmota/tasmota_configurations_ESP32.h +++ b/tasmota/tasmota_configurations_ESP32.h @@ -184,6 +184,12 @@ #define USE_BLE_ESP32 // Enable new BLE driver #define USE_EQ3_ESP32 #define USE_MI_ESP32 // (ESP32 only) Add support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) +#ifdef USE_MI_ESP32 + #if(USE_MI_HOMEKIT != 1) //only for the .c-file + #undef USE_MI_HOMEKIT + #endif //USE_MI_HOMEKIT + #define USE_MI_EXT_GUI //enable dashboard style GUI +#endif //USE_MI_ESP32 #endif // FIRMWARE_BLUETOOTH /*********************************************************************************************\ diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index e55fd7203..d3dcd988f 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -2582,7 +2582,7 @@ void HandleUploadDone(void) { WSContentStop(); } -#ifdef USE_BLE_ESP32 +#if defined(USE_BLE_ESP32) || defined(USE_MI_ESP32) // declare the fn int ExtStopBLE(); #endif @@ -3599,4 +3599,4 @@ bool Xdrv01(uint8_t function) } return result; } -#endif // USE_WEBSERVER \ No newline at end of file +#endif // USE_WEBSERVER diff --git a/tasmota/xdrv_52_3_berry_MI32.ino b/tasmota/xdrv_52_3_berry_MI32.ino new file mode 100644 index 000000000..a2f03fe21 --- /dev/null +++ b/tasmota/xdrv_52_3_berry_MI32.ino @@ -0,0 +1,234 @@ +/* + xdrv_52_3_berry_MI32.ino - Berry scripting language, native functions + + Copyright (C) 2021 Christian Baars & Stephan Hadinger, Berry language by Guan Wenliang https://github.com/Skiars/berry + + 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 . +*/ + + +#ifdef USE_BERRY + +#include + +#if defined(USE_MI_ESP32) && !defined(USE_BLE_ESP32) + +/*********************************************************************************************\ + * Native functions mapped to Berry functions + * + * +\*********************************************************************************************/ +extern "C" { + +/******************************************************************** +** MI32 - sensor specific functions +********************************************************************/ + + extern uint32_t MI32numberOfDevices(); + extern const char * MI32getDeviceName(uint32_t slot); + extern void MI32setBatteryForSlot(uint32_t slot, uint8_t value); + extern void MI32setHumidityForSlot(uint32_t slot, float value); + extern void MI32setTemperatureForSlot(uint32_t slot, float value); + extern uint8_t * MI32getDeviceMAC(uint32_t slot); + + int be_MI32_devices(bvm *vm); + int be_MI32_devices(bvm *vm) { + uint32_t devices = MI32numberOfDevices(); + be_pushint(vm, devices); + be_return(vm); + } + + int be_MI32_set_bat(bvm *vm); + int be_MI32_set_bat(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 3 && be_isint(vm, 2) && be_isint(vm, 3)) { + uint32_t slot = be_toint(vm, 2); + int32_t bat_val = be_toint(vm, 3); + MI32setBatteryForSlot(slot,bat_val); + be_return(vm); // Return + } + be_raise(vm, kTypeError, nullptr); + } + + int be_MI32_get_name(bvm *vm); + int be_MI32_get_name(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 2 && be_isint(vm, 2)) { + uint32_t slot = be_toint(vm, 2); + const char * name = MI32getDeviceName(slot); + be_pushstring(vm,name); + be_return(vm); // Return + } + be_raise(vm, kTypeError, nullptr); + } + + int be_MI32_get_MAC(bvm *vm); + int be_MI32_get_MAC(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 2 && be_isint(vm, 2)) { + uint32_t slot = be_toint(vm, 2); + uint8_t *buffer = MI32getDeviceMAC(slot); + size_t len = 6; + if(buffer != NULL) { + be_pushbytes(vm,buffer,len); + be_return(vm); // Return + } + } + be_raise(vm, kTypeError, nullptr); + } + + int be_MI32_set_hum(bvm *vm); + int be_MI32_set_hum(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 3 && be_isint(vm, 2) && be_isreal(vm, 3)) { + uint32_t slot = be_toint(vm, 2); + float hum_val = be_toreal(vm, 3); + MI32setHumidityForSlot(slot,hum_val); + be_return(vm); // Return + } + be_raise(vm, kTypeError, nullptr); + } + + int be_MI32_set_temp(bvm *vm); + int be_MI32_set_temp(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 3 && be_isint(vm, 2) && be_isreal(vm, 3)) { + uint32_t slot = be_toint(vm, 2); + float temp_val = be_toreal(vm, 3); + MI32setTemperatureForSlot(slot,temp_val); + be_return(vm); // Return + } + be_raise(vm, kTypeError, nullptr); + } + + +/******************************************************************** +** BLE - generic BLE functions +********************************************************************/ + extern void MI32setBerryAdvCB(void* function, uint8_t *buffer); + extern void MI32setBerryConnCB(void* function, uint8_t *buffer); + extern bool MI32runBerryConnection(uint8_t operation); + extern bool MI32setBerryCtxSvc(const char *Svc); + extern bool MI32setBerryCtxChr(const char *Chr); + extern bool MI32setBerryCtxMAC(uint8_t *MAC, uint8_t type); + + + int be_BLE_reg_conn_cb(bvm *vm); + int be_BLE_reg_conn_cb(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 3 && be_iscomptr(vm, 2)) { + void* cb = be_tocomptr(vm, 2); + size_t len; + uint8_t * buf = (uint8_t*)be_tobytes(vm, 3, &len); + MI32setBerryConnCB(cb,buf); + be_return(vm); + } + be_raise(vm, kTypeError, nullptr); + } + + int be_BLE_reg_adv_cb(bvm *vm); + int be_BLE_reg_adv_cb(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 3 && be_iscomptr(vm, 2)) { + void* cb = be_tocomptr(vm, 2); + size_t len; + uint8_t * buf = (uint8_t*)be_tobytes(vm, 3, &len); + MI32setBerryAdvCB(cb,buf); + be_return(vm); // Return + } + else if(argc == 2 && be_isint(vm, 2)){ + if(be_toint(vm, 2) == 0){ + MI32setBerryAdvCB(NULL,NULL); + be_return(vm); // Return + } + } + be_raise(vm, kTypeError, nullptr); + } + + int be_BLE_set_MAC(bvm *vm); + int be_BLE_set_MAC(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc > 1 && be_isbytes(vm, 2)) { + size_t len = 6; + uint8_t type = 0; + if(argc == 3 && be_isint(vm, 3)){ + type = be_toint(vm,3); + } + if (MI32setBerryCtxMAC((uint8_t*)be_tobytes(vm, 2, &len),type)) be_return(vm); + } + be_raise(vm, kTypeError, nullptr); + } + + int be_BLE_set_service(bvm *vm); + int be_BLE_set_service(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 2 && be_isstring(vm, 2)) { + if (MI32setBerryCtxSvc(be_tostring(vm, 2))) be_return(vm); + } + be_raise(vm, kTypeError, nullptr); + } + + int be_BLE_set_characteristic(bvm *vm); + int be_BLE_set_characteristic(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 2 && be_isstring(vm, 2)) { + if (MI32setBerryCtxChr(be_tostring(vm, 2))) be_return(vm); + } + be_raise(vm, kTypeError, nullptr); + } + + int be_BLE_run(bvm *vm); + int be_BLE_run(bvm *vm){ + int32_t argc = be_top(vm); // Get the number of arguments + if (argc == 2 && be_isint(vm, 2)) { + if (MI32runBerryConnection(be_toint(vm, 2))) be_return(vm); + } + be_raise(vm, kTypeError, nullptr); + } +} //extern "C" + + +#endif // USE_MI_ESP32 + +#endif // USE_BERRY + +/* +BLE.set_svc +BLE.set_chr + +BLE.set_MAC +BLE.run(op) +be_BLE_op: +1 read +2 write +3 subscribe +4 unsubscribe +5 disconnect + +11 read once, then disconnect +12 write once, then disconnect +13 subscribe once, then disconnect +14 unsubscribe once, then disconnect + +BLE.conn_cb(cb,buffer) +BLE.adv_cb(cb,buffer) + +MI32.devices() +MI32.get_name(slot) +MI32.get_MAC(slot) +MI32.set_bat(slot,int) +MI32.set_hum(slot,float) +MI32.set_temp(slot,float) + +*/ diff --git a/tasmota/xsns_62_esp32_mi.h b/tasmota/xsns_62_esp32_mi.h new file mode 100644 index 000000000..7aa705ebd --- /dev/null +++ b/tasmota/xsns_62_esp32_mi.h @@ -0,0 +1,506 @@ +/* + xsns_62_esp32_mi.h - MI-BLE-sensors via ESP32 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 . + +*/ +#ifdef USE_MI_ESP32 +/*********************************************************************************************\ + * structs and types +\*********************************************************************************************/ +#pragma pack(1) // byte-aligned structures to read the sensor data + +struct frame_crtl_t{ + uint16_t reserved1:1; + uint16_t reserved2:1; + uint16_t reserved3:1; + uint16_t isEncrypted:1; + uint16_t includesMAC:1; + uint16_t includesCapability:1; + uint16_t includesObj:1; + uint16_t MESH:1; + uint16_t registered:1; + uint16_t solicited:1; + uint16_t AuthMode:2; + uint16_t version:4; +}; + +struct mi_payload_t{ + 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 + uint8_t leak; //14 + uint32_t NMT; //17 + uint8_t door; //19 + struct{ //01 + uint8_t num; + uint8_t value; + uint8_t type; + }Btn; + }; + uint8_t padding[12]; //for decryption +}; + +struct mi_beacon_t{ + frame_crtl_t frame; + uint16_t productID; + uint8_t counter; + uint8_t MAC[6]; + uint8_t capability; + mi_payload_t payload; +}; + + +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 encPacket_t{ + // the packet is longer, but this part is enough to decrypt + uint16_t PID; + uint8_t frameCnt; + uint8_t MAC[6]; + uint8_t payload[16]; // only a pointer to the address, size is variable +}; + +struct berryAdvPacket_t{ + uint8_t MAC[6]; + uint8_t addressType; + uint16_t svcUUID; + uint8_t RSSI; + uint8_t length; // length of svcData + uint8_t svcData[40]; // only a pointer to the address, size is variable + // the last array contains manufacturer data if present, if svcData is not present + // format: svcData[0] = length, svcData[1...length] = payload +}; + + +union mi_bindKey_t{ + struct{ + uint8_t key[16]; + uint8_t MAC[6]; + }; + uint8_t buf[22]; +}; + +struct ATCPacket_t{ //and PVVX + uint8_t MAC[6]; + union { + struct{ + uint16_t temp; //sadly this is in wrong endianess + uint8_t hum; + uint8_t batPer; + uint16_t batMV; + uint8_t frameCnt; + } A; //ATC + struct{ + int16_t temp; + uint16_t hum; // x 0.01 % + uint16_t batMV; + uint8_t batPer; + uint8_t frameCnt; + struct { + uint8_t reed:1; + uint8_t TRGval:1; + uint8_t TRGcrtl:1; + uint8_t tempTrig:1; + uint8_t humTrig:1; + uint8_t spare:3; + }; + }P; //PVVX + }; +}; + +#pragma pack(0) + + +struct MI32connectionContextBerry_t{ + NimBLEUUID serviceUUID; + NimBLEUUID charUUID; + uint8_t * MAC; + uint8_t * buffer; + uint8_t operation; + uint8_t addrType; + int error; + bool oneOp; +}; + +struct { + // uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start + TaskHandle_t ScanTask = nullptr; + TaskHandle_t ConnTask = nullptr; + MI32connectionContextBerry_t *conCtx = nullptr; + union { + struct { + uint32_t init:1; + uint32_t connected:1; + uint32_t autoScan:1; + uint32_t canScan:1; + uint32_t runningScan:1; + + uint32_t canConnect:1; + uint32_t willConnect:1; + uint32_t readingDone:1; + + uint32_t shallTriggerTele:1; + uint32_t triggeredTele:1; + uint32_t shallClearResults:1; // BLE scan results + uint32_t shallShowStatusInfo:1; // react to amount of found sensors via RULES + uint32_t didGetConfig:1; + uint32_t didStartHAP:1; + uint32_t triggerBerryAdvCB:1; + uint32_t triggerBerryConnCB:1; + }; + uint32_t all = 0; + } mode; + struct { + uint8_t sensor; // points to to the number 0...255 + } 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 showRSSI:1; + uint32_t ignoreBogusBattery:1; + uint32_t minimalSummary:1; // DEPRECATED!! + } option; +#ifdef USE_MI_EXT_GUI + uint32_t widgetSlot; +#ifdef USE_ENERGY_SENSOR + uint8_t *energy_history; +#endif //USE_ENERGY_SENSOR +#endif //USE_MI_EXT_GUI + +#ifdef USE_MI_HOMEKIT + void *outlet_hap_service[4]; //arbitrary chosen + int8_t HKconnectedControllers = 0; //should never be < 0 + uint8_t HKinfoMsg = 0; + char hk_setup_code[11]; +#endif //USE_MI_HOMEKIT + void *beConnCB; + void *beAdvCB; + uint8_t *beAdvBuf; + uint8_t infoMsg = 0; +} MI32; + +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 MAC[6]; + uint8_t *key; + uint32_t lastTimeSeen; + union { + struct { + uint32_t needsKey:1; + uint32_t hasWrongKey:1; + 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 motion:1; + uint32_t Btn:1; + uint32_t door:1; + uint32_t leak: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 door:1; + uint32_t leak:1; + }; + uint32_t raw; + } eventType; + + int RSSI; + uint32_t lastTime; + uint32_t lux; + uint8_t *lux_history; + float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx + uint8_t *temp_history; + 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; + uint8_t *hum_history; + }; // MJ_HT_V1, LYWSD0x + struct { + uint16_t events; //"alarms" since boot + uint32_t NMT; // no motion time in seconds for the MJYD2S + }; + struct { + uint16_t Btn; + uint8_t leak; + }; + uint8_t door; + }; + union { + uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1) + }; +#ifdef USE_MI_HOMEKIT + //HAP handles + void *temp_hap_service; + void *hum_hap_service; + void *light_hap_service; + void *motion_hap_service; + void *door_sensor_hap_service; + void *button_hap_service[6]; + void *bat_hap_service; + void *leak_hap_service; +#endif //USE_MI_HOMEKIT +}; + +/*********************************************************************************************\ + * constants +\*********************************************************************************************/ + +#define D_CMND_MI32 "MI32" + +const char kMI32_Commands[] PROGMEM = D_CMND_MI32 "|Key|"/*Time|Battery|Unit|Beacon|*/"Cfg|Option"; + +void (*const MI32_Commands[])(void) PROGMEM = {&CmndMi32Key, /*&CmndMi32Time, &CmndMi32Battery, &CmndMi32Unit, &CmndMi32Beacon,*/ &CmndMi32Cfg, &CmndMi32Option }; + +#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 MCCGQ02 13 +#define SJWS01L 14 +#define PVVX 15 +#define YLKG08 16 + +#define MI32_TYPES 16 //count this manually + +const uint16_t kMI32DeviceID[MI32_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 + 0x098b, // MCCGQ02 + 0x0863, // SJWS01L + 0x944a, // PVVX -> this is a fake ID + 0x03b6 // YLKG08 and YLKG07 - version w/wo mains + }; + +const char kMI32DeviceType1[] PROGMEM = "Flora"; +const char kMI32DeviceType2[] PROGMEM = "MJ_HT_V1"; +const char kMI32DeviceType3[] PROGMEM = "LYWSD02"; +const char kMI32DeviceType4[] PROGMEM = "LYWSD03"; +const char kMI32DeviceType5[] PROGMEM = "CGG1"; +const char kMI32DeviceType6[] PROGMEM = "CGD1"; +const char kMI32DeviceType7[] PROGMEM = "NLIGHT"; +const char kMI32DeviceType8[] PROGMEM = "MJYD2S"; +const char kMI32DeviceType9[] PROGMEM = "YEERC"; +const char kMI32DeviceType10[] PROGMEM ="MHOC401"; +const char kMI32DeviceType11[] PROGMEM ="MHOC303"; +const char kMI32DeviceType12[] PROGMEM ="ATC"; +const char kMI32DeviceType13[] PROGMEM ="MCCGQ02"; +const char kMI32DeviceType14[] PROGMEM ="SJWS01L"; +const char kMI32DeviceType15[] PROGMEM ="PVVX"; +const char kMI32DeviceType16[] PROGMEM ="YLKG08"; +const char * kMI32DeviceType[] PROGMEM = {kMI32DeviceType1,kMI32DeviceType2,kMI32DeviceType3,kMI32DeviceType4, + kMI32DeviceType5,kMI32DeviceType6,kMI32DeviceType7,kMI32DeviceType8, + kMI32DeviceType9,kMI32DeviceType10,kMI32DeviceType11,kMI32DeviceType12, + kMI32DeviceType13,kMI32DeviceType14,kMI32DeviceType15,kMI32DeviceType16}; + +const char kMI32_ConnErrorMsg[] PROGMEM = "no Error|could not connect|got no service|got no characteristic|can not read|can not notify|can not write|did not write|notify time out"; + +const char kMI32_BLEInfoMsg[] PROGMEM = "Scan ended|Got Notification|Did connect|Did disconnect|Start scanning"; + +const char kMI32_HKInfoMsg[] PROGMEM = "HAP core started|HAP core did not start!!|HAP controller disconnected|HAP controller connected|HAP outlet added"; +/*********************************************************************************************\ + * enumerations +\*********************************************************************************************/ + +enum MI32_Commands { // commands useable in console or rules + CMND_MI32_KEY, // add bind key to a mac for packet decryption + CMND_MI32_CFG, // save config file as JSON with all sensors w/o keys to mi32cfg + CMND_MI32_OPTION // change driver options at runtime + }; + +enum MI32_TASK { + MI32_TASK_SCAN = 0, + MI32_TASK_CONN = 1, +}; + +enum MI32_ConnErrorMsg { + MI32_CONN_NO_ERROR = 0, + MI32_CONN_NO_CONNECT, + MI32_CONN_NO_SERVICE, + MI32_CONN_NO_CHARACTERISTIC, + MI32_CONN_CAN_NOT_READ, + MI32_CONN_CAN_NOT_NOTIFY, + MI32_CONN_CAN_NOT_WRITE, + MI32_CONN_DID_NOT_WRITE, + MI32_CONN_NOTIFY_TIMEOUT +}; + +enum MI32_BLEInfoMsg { + MI32_SCAN_ENDED = 1, + MI32_GOT_NOTIFICATION, + MI32_DID_CONNECT, + MI32_DID_DISCONNECT, + MI32_START_SCANNING +}; + +enum MI32_HKInfoMsg { + MI32_HAP_DID_START = 1, + MI32_HAP_DID_NOT_START, + MI32_HAP_CONTROLLER_DISCONNECTED, + MI32_HAP_CONTROLLER_CONNECTED, + MI32_HAP_OUTLET_ADDED +}; + +/*********************************************************************************************\ + * extended web gui +\*********************************************************************************************/ + +#ifdef USE_WEBSERVER +#ifdef USE_MI_EXT_GUI +const char HTTP_BTN_MENU_MI32[] PROGMEM = "

"; + +const char HTTP_MI32_SCRIPT_1[] PROGMEM = + "function setUp(){setInterval(countUp,1000); setInterval(update,100);}" + "function countUp(){let ti=document.querySelectorAll('.Ti');" + "for(const el of ti){var t=parseInt(el.innerText);el.innerText=t+1;}}" + "function update(){" //source, value + "var xr=new XMLHttpRequest();" + "xr.onreadystatechange=function(){" + "if(xr.readyState==4&&xr.status==200){" + "var r = xr.response;" // new widget + "if(r.length>2000){return;};if(r.length==0){return;}" + "var d = document.createElement('div');" + "d.innerHTML = r.trim();" + "var old = eb(d.firstChild.id);" + "old.parentNode.replaceChild(d.firstChild,old);" + "};" + "};" + "xr.open('GET','/m32?wi=1',true);" + "xr.send();" + "};" + ; + +const char HTTP_MI32_STYLE[] PROGMEM = + ""; + +const char HTTP_MI32_STYLE_SVG[] PROGMEM = + "" + "" + "" + ; + +const char HTTP_MI32_PARENT_START[] PROGMEM = + "
" + "

MI32 Bridge

" + "Observing %u devices
" + "Uptime: %u seconds
" +#ifdef USE_MI_HOMEKIT + "HomeKit setup code: %s
" + "HAP controller connections: %d
" +#else + "HomeKit not enabled%s
" +#endif //USE_MI_HOMEKIT + "Free Heap: %u kB" + "
"; + +const char HTTP_MI32_WIDGET[] PROGMEM = + "
MAC:%s RSSI:%d %s
" + "‌%s" + "

%s" + "" + "" + "" + "" + "

"; + +const char HTTP_MI32_GRAPH[] PROGMEM = + "" + "" + "" + ""; + //rgb(185, 124, 124) - red, rgb(185, 124, 124) - blue, rgb(242, 240, 176) - yellow + +#ifdef USE_MI_ESP32_ENERGY +const char HTTP_MI32_POWER_WIDGET[] PROGMEM = + "
" + "

Energy" + "

" + "

" D_VOLTAGE ": %.1f " D_UNIT_VOLT "

" + "

" D_CURRENT ": %.3f " D_UNIT_AMPERE "

"; +#endif //USE_MI_ESP32_ENERGY + +#endif //USE_MI_EXT_GUI +#endif // USE_WEBSERVER + +#endif //USE_MI_ESP32 diff --git a/tasmota/xsns_62_esp32_mi.ino b/tasmota/xsns_62_esp32_mi.ino index ecc9d228c..c3c8bc0a7 100644 --- a/tasmota/xsns_62_esp32_mi.ino +++ b/tasmota/xsns_62_esp32_mi.ino @@ -22,22 +22,12 @@ -------------------------------------------------------------------------------------------- Version yyyymmdd Action Description -------------------------------------------------------------------------------------------- + 0.9.5.0 20211016 changed - major rewrite, added mi32cfg (file and command), Homekit-Bridge, + extended GUI, + removed BLOCK, PERIOD, TIME, UNIT, BATTERY and PAGE -> replaced via Berry-Support + ------- 0.9.1.7 20201116 changed - small bugfixes, add BLOCK and OPTION command, send BLE scan via MQTT ------- - 0.9.1.6 20201022 changed - Beacon support, RSSI at TELEPERIOD, refactoring - ------- - 0.9.1.5 20201021 changed - HASS related ('null', hold back discovery), number of found sensors for RULES - ------- - 0.9.1.4 20201020 changed - use BearSSL for decryption, revert to old TELEPERIOD-cycle as default - ------- - 0.9.1.3 20200926 changed - Improve HA discovery, make key+MAC case insensitive - ------- - 0.9.1.3 20200916 changed - add ATC (custom FW for LYWSD03MMC), API adaption for NimBLE-Arduino 1.0.2 - ------- - 0.9.1.2 20200802 changed - add MHO-C303 - ------- - 0.9.1.1 20200715 changed - add MHO-C401, refactoring - ------- 0.9.1.0 20200712 changed - add lights and yeerc, add pure passive mode with decryption, lots of refactoring ------- @@ -53,334 +43,35 @@ #ifdef USE_MI_ESP32 +#ifdef USE_ENERGY_SENSOR +// #define USE_MI_ESP32_ENERGY //perpare for some GUI extensions +#endif + #define XSNS_62 62 -#define USE_MI_DECRYPTION #include #include -#ifdef USE_MI_DECRYPTION + #include -#endif //USE_MI_DECRYPTION + +#include "xsns_62_esp32_mi.h" + +#ifdef USE_MI_HOMEKIT +extern "C" void mi_homekit_main(void); +extern "C" void mi_homekit_update_value(void* handle, float value, uint32_t type); +extern "C" void mi_homekit_stop(); +void MI32getSetupCodeFromMAC(char* code); +#endif //USE_MI_HOMEKIT + void MI32scanEndedCB(NimBLEScanResults results); void MI32notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); - -struct { - uint16_t perPage = 4; - uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start - union { - struct { - uint32_t init:1; - uint32_t connected:1; - uint32_t autoScan:1; - uint32_t canScan:1; - uint32_t runningScan:1; - uint32_t canConnect:1; - uint32_t willConnect:1; - uint32_t readingDone:1; - uint32_t shallSetTime:1; - uint32_t willSetTime:1; - uint32_t shallReadBatt:1; - uint32_t willReadBatt:1; - uint32_t shallSetUnit:1; - uint32_t willSetUnit:1; - uint32_t shallTriggerTele:1; - uint32_t triggeredTele:1; - uint32_t shallClearResults:1; // BLE scan results - uint32_t shallShowStatusInfo:1; // react to amount of found sensors via RULES - uint32_t firstAutodiscoveryDone:1; - uint32_t activeBeacon:1; - uint32_t shallShowScanResult:1; - uint32_t shallShowBlockList:1; - }; - uint32_t all = 0; - } mode; - struct { - uint8_t sensor; // points to to the number 0...255 - uint8_t beaconScanCounter; // countdown timer in seconds - } 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; -} MI32; - -#pragma pack(1) // byte-aligned structures to read the sensor data - - struct { - int16_t temp; - uint8_t hum; - uint16_t volt; // LYWSD03 only - } LYWSD0x_HT; - struct { - uint8_t spare; - int16_t temp; - uint16_t hum; - } CGD1_HT; - struct { - int16_t temp; - uint8_t spare; - uint32_t lux; - uint8_t moist; - uint16_t fert; - } Flora_TLMF; // 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 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 encPacket_t{ - // the packet is longer, but this part is enough to decrypt - uint16_t PID; - uint8_t frameCnt; - uint8_t MAC[6]; - uint8_t payload[16]; // only a pointer to the address, size is variable -}; - -union mi_bindKey_t{ - struct{ - uint8_t key[16]; - uint8_t MAC[6]; - }; - uint8_t buf[22]; -}; - -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; -}; - -#pragma pack(0) - -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 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 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 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; - }; - union { - uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1) - }; -}; - -struct scan_entry_t { - uint8_t MAC[6]; - uint16_t CID; - uint16_t SVC; - uint16_t UUID; - int32_t RSSI; -}; - -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; - bool active = false; -}; - -struct MAC_t { - uint8_t buf[6]; -}; +void MI32AddKey(mi_bindKey_t keyMAC); std::vector MIBLEsensors; -std::vector MIBLEbindKeys; -std::array MIBLEbeacons; // we support a fixed number -std::vector MIBLEscanResult; -std::vector MIBLEBlockList; static BLEScan* MI32Scan; -/*********************************************************************************************\ - * constants -\*********************************************************************************************/ - -#define D_CMND_MI32 "MI32" - -const char kMI32_Commands[] PROGMEM = D_CMND_MI32 "|" -#ifdef USE_MI_DECRYPTION - "Key|" -#endif // USE_MI_DECRYPTION - "Period|Time|Page|Battery|Unit|Beacon|Block|Option"; - -void (*const MI32_Commands[])(void) PROGMEM = { -#ifdef USE_MI_DECRYPTION - &CmndMi32Key, -#endif // USE_MI_DECRYPTION - &CmndMi32Period, &CmndMi32Time, &CmndMi32Page, &CmndMi32Battery, &CmndMi32Unit, &CmndMi32Beacon, &CmndMi32Block, &CmndMi32Option }; - -#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 MI32_TYPES 12 //count this manually - -const uint16_t kMI32DeviceID[MI32_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 - }; - -const char kMI32DeviceType1[] PROGMEM = "Flora"; -const char kMI32DeviceType2[] PROGMEM = "MJ_HT_V1"; -const char kMI32DeviceType3[] PROGMEM = "LYWSD02"; -const char kMI32DeviceType4[] PROGMEM = "LYWSD03"; -const char kMI32DeviceType5[] PROGMEM = "CGG1"; -const char kMI32DeviceType6[] PROGMEM = "CGD1"; -const char kMI32DeviceType7[] PROGMEM = "NLIGHT"; -const char kMI32DeviceType8[] PROGMEM = "MJYD2S"; -const char kMI32DeviceType9[] PROGMEM = "YEERC"; -const char kMI32DeviceType10[] PROGMEM ="MHOC401"; -const char kMI32DeviceType11[] PROGMEM ="MHOC303"; -const char kMI32DeviceType12[] PROGMEM ="ATC"; -const char * kMI32DeviceType[] PROGMEM = {kMI32DeviceType1,kMI32DeviceType2,kMI32DeviceType3,kMI32DeviceType4,kMI32DeviceType5,kMI32DeviceType6,kMI32DeviceType7,kMI32DeviceType8,kMI32DeviceType9,kMI32DeviceType10,kMI32DeviceType11,kMI32DeviceType12}; - -/*********************************************************************************************\ - * enumerations -\*********************************************************************************************/ - -enum MI32_Commands { // commands useable in console or rules - CMND_MI32_PERIOD, // set period like TELE-period in seconds between read-cycles - CMND_MI32_TIME, // set LYWSD02-Time from ESP8266-time - CMND_MI32_PAGE, // sensor entries per web page, which will be shown alternated - CMND_MI32_BATTERY, // read all battery levels - CMND_MI32_UNIT, // toggles the displayed unit between C/F (LYWSD02) - CMND_MI32_KEY, // add bind key to a mac for packet decryption - CMND_MI32_BEACON, // add up to 4 beacons defined by their MAC addresses - CMND_MI32_BLOCK, // block BLE sensors defined by their MAC addresses - CMND_MI32_OPTION // change driver options at runtime - }; - -enum MI32_TASK { - MI32_TASK_SCAN = 0, - MI32_TASK_CONN = 1, - MI32_TASK_TIME = 2, - MI32_TASK_BATT = 3, - MI32_TASK_UNIT = 4, -}; - -enum MI32_BEACON_CMND { - MI32_BEACON_ON = 0, - MI32_BEACON_OFF = 1, - MI32_BEACON_DEL = 2, -}; /*********************************************************************************************\ * Classes @@ -388,14 +79,15 @@ enum MI32_BEACON_CMND { class MI32SensorCallback : public NimBLEClientCallbacks { void onConnect(NimBLEClient* pclient) { - AddLog(LOG_LEVEL_DEBUG,PSTR("connected %s"), kMI32DeviceType[(MIBLEsensors[MI32.state.sensor].type)-1]); + // AddLog(LOG_LEVEL_DEBUG,PSTR("connected %s"), kMI32DeviceType[(MIBLEsensors[MI32.conCtx->slot].type)-1]); + MI32.infoMsg = MI32_DID_CONNECT; MI32.mode.willConnect = 0; MI32.mode.connected = 1; } void onDisconnect(NimBLEClient* pclient) { MI32.mode.connected = 0; - MI32.mode.willReadBatt = 0; - AddLog(LOG_LEVEL_DEBUG,PSTR("disconnected %s"), kMI32DeviceType[(MIBLEsensors[MI32.state.sensor].type)-1]); + MI32.infoMsg = MI32_DID_DISCONNECT; + //AddLog(LOG_LEVEL_DEBUG,PSTR("disconnected")); } bool onConnParamsUpdateRequest(NimBLEClient* MI32Client, const ble_gap_upd_params* params) { if(params->itvl_min < 24) { /** 1.25ms units */ @@ -413,46 +105,63 @@ class MI32SensorCallback : public NimBLEClientCallbacks { class MI32AdvCallbacks: public NimBLEAdvertisedDeviceCallbacks { void onResult(NimBLEAdvertisedDevice* advertisedDevice) { - // AddLog(LOG_LEVEL_DEBUG,PSTR("Advertised Device: %s Buffer: %u"),advertisedDevice->getAddress().toString().c_str(),advertisedDevice->getServiceData(0).length()); + static bool _mutex = false; + if(_mutex) return; + _mutex = true; + int RSSI = advertisedDevice->getRSSI(); uint8_t addr[6]; memcpy(addr,advertisedDevice->getAddress().getNative(),6); MI32_ReverseMAC(addr); - if (advertisedDevice->getServiceDataCount() == 0) { - // AddLog(LOG_LEVEL_DEBUG,PSTR("No Xiaomi Device: %s Buffer: %u"),advertisedDevice->getAddress().toString().c_str(),advertisedDevice->getServiceData(0).length()); - if(MI32.state.beaconScanCounter==0 && !MI32.mode.activeBeacon){ - MI32Scan->erase(advertisedDevice->getAddress()); - return; - } - else{ - MI32HandleGenericBeacon(advertisedDevice->getPayload(), advertisedDevice->getPayloadLength(), RSSI, addr); - return; - } + size_t ServiceDataLength = 0; + + if (advertisedDevice->getServiceDataCount() == 0) { + if(MI32.beAdvCB != nullptr && MI32.mode.triggerBerryAdvCB == 0){ + berryAdvPacket_t *_packet = (berryAdvPacket_t *)MI32.beAdvBuf; + memcpy(_packet->MAC,addr,6); + _packet->addressType = advertisedDevice->getAddressType(); + _packet->svcUUID = 0; + _packet->RSSI = (uint8_t)RSSI; + _packet->length = ServiceDataLength; + _packet->svcData[0] = 0; //guarantee it is zero!! + if(advertisedDevice->haveManufacturerData()){ + std::string _md = advertisedDevice->getManufacturerData(); + _packet->svcData[0] = _md.size(); + memcpy((_packet->svcData)+ServiceDataLength+1,_md.data(), _md.size()); + } + MI32.mode.triggerBerryAdvCB = 1; + } + _mutex = false; + return; } uint16_t UUID = advertisedDevice->getServiceDataUUID(0).getNative()->u16.value; - // AddLog(LOG_LEVEL_DEBUG,PSTR("UUID: %x"),UUID); - size_t ServiceDataLength = advertisedDevice->getServiceData(0).length(); + ServiceDataLength = advertisedDevice->getServiceData(0).length(); + if(MI32.beAdvCB != nullptr && MI32.mode.triggerBerryAdvCB == 0){ + berryAdvPacket_t *_packet = (berryAdvPacket_t *)MI32.beAdvBuf; + memcpy(_packet->MAC,addr,6); + _packet->addressType = advertisedDevice->getAddressType(); + _packet->svcUUID = UUID; + _packet->RSSI = (uint8_t)RSSI; + _packet->length = ServiceDataLength; + memcpy(_packet->svcData,advertisedDevice->getServiceData(0).data(),ServiceDataLength); + MI32.mode.triggerBerryAdvCB = 1; + } + if(UUID==0xfe95) { - if(MI32isInBlockList(addr) == true) return; MI32ParseResponse((char*)advertisedDevice->getServiceData(0).data(),ServiceDataLength, addr, RSSI); } else if(UUID==0xfdcd) { - if(MI32isInBlockList(addr) == true) return; MI32parseCGD1Packet((char*)advertisedDevice->getServiceData(0).data(),ServiceDataLength, addr, RSSI); } - else if(UUID==0x181a) { //ATC - if(MI32isInBlockList(addr) == true) return; + else if(UUID==0x181a) { //ATC and PVVX MI32ParseATCPacket((char*)advertisedDevice->getServiceData(0).data(),ServiceDataLength, addr, RSSI); } - else { - if(MI32.state.beaconScanCounter!=0 || MI32.mode.activeBeacon){ - MI32HandleGenericBeacon(advertisedDevice->getPayload(), advertisedDevice->getPayloadLength(), RSSI, addr); - } - // AddLog(LOG_LEVEL_DEBUG,PSTR("No Xiaomi Device: %x: %s Buffer: %u"), UUID, advertisedDevice->getAddress().toString().c_str(),advertisedDevice->getServiceData(0).length()); - MI32Scan->erase(advertisedDevice->getAddress()); - } + // else { + // MI32Scan->erase(advertisedDevice->getAddress()); + // } + _mutex = false; }; }; @@ -466,21 +175,16 @@ static NimBLEClient* MI32Client; \*********************************************************************************************/ void MI32scanEndedCB(NimBLEScanResults results){ - AddLog(LOG_LEVEL_DEBUG,PSTR("Scan ended")); + MI32.infoMsg = MI32_SCAN_ENDED; MI32.mode.runningScan = 0; } void MI32notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){ - AddLog(LOG_LEVEL_DEBUG,PSTR("Notified length: %u"),length); - switch(MIBLEsensors[MI32.state.sensor].type){ - case LYWSD03MMC: case LYWSD02: case MHOC401: - MI32readHT_LY((char*)pData); - MI32.mode.readingDone = 1; - break; - default: - MI32.mode.readingDone = 1; - break; - } + MI32.infoMsg = MI32_GOT_NOTIFICATION; + MI32.conCtx->buffer[0] = (uint8_t)length; + memcpy(MI32.conCtx->buffer + 1, pData, length); + MI32.mode.triggerBerryConnCB = 1; + MI32.mode.readingDone = 1; } /*********************************************************************************************\ * Helper functions @@ -542,97 +246,126 @@ void MI32_ReverseMAC(uint8_t _mac[]){ memcpy(_mac,_reversedMAC, sizeof(_reversedMAC)); } -#ifdef USE_MI_DECRYPTION -void MI32AddKey(char* payload){ - mi_bindKey_t keyMAC; - MI32HexStringToBytes(payload,keyMAC.buf); - bool unknownKey = true; - for(uint32_t i=0; iMAC[i]; - } - memcpy((uint8_t*)&nonce+6,(uint8_t*)&packet->PID,2); - nonce[8] = packet->frameCnt; - memcpy((uint8_t*)&nonce+9,(uint8_t*)&_buf[_bufSize-9],3); - // AddLog(LOG_LEVEL_DEBUG,PSTR("nonceCnt1 and 2: %02x %02x %02x"),nonce[9],nonce[10],nonce[11]); - memcpy((uint8_t*)&tag,(uint8_t*)&_buf[_bufSize-6],4); - // AddLog(LOG_LEVEL_DEBUG,PSTR("tag: %02x %02x %02x %02x"),tag[0],tag[1],tag[2],tag[3]); - - MI32_ReverseMAC(packet->MAC); - uint8_t _bindkey[16] = {0x0}; - bool foundNoKey = true; - AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Search key for MAC: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); - for(uint32_t i=0; iMAC,MIBLEbindKeys[i].MAC,sizeof(packet->MAC))==0){ - memcpy(_bindkey,MIBLEbindKeys[i].key,sizeof(_bindkey)); - AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Decryption Key found")); - foundNoKey = false; - break; - } - } - if(foundNoKey){ - AddLog(LOG_LEVEL_DEBUG,PSTR("M32: No Key found !!")); + if(MIBLEsensors[_slot].key == nullptr){ + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: No Key found !!")); return -2; } + // uint8_t _testBuf[] = {0x58,0x30,0xb6,0x03,0x36,0x8b,0x98,0xc5,0x41,0x24,0xf8,0x8b,0xb8,0xf2,0x66,0x13,0x51,0x00,0x00,0x00,0xd6}; + // uint8_t _testKey[] = {0xb8,0x53,0x07,0x51,0x58,0x48,0x8d,0x3d,0x3c,0x97,0x7c,0xa3,0x9a,0x5b,0x5e,0xa9}; + // _beacon = (mi_beacon_t *)_testBuf; + // _bufSize = sizeof(_testBuf); + // dataLen = _bufSize - 11 ; // _bufsize - frame - type - frame.counter - MAC + + + uint32_t _version = (uint32_t)_beacon->frame.version; + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: encrypted msg from %s with version:%u"),kMI32DeviceType[MIBLEsensors[_slot].type-1],_version); + + if(_version == 5){ + if(_beacon->frame.includesMAC){ + for (uint32_t i = 0; i<6; i++){ + nonce[i] = _beacon->MAC[i]; + } + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: has MAC")); + memcpy(_payload,(uint8_t*)&_beacon->capability, dataLen); //special packet + dataLen -= 7; + } + else{ + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: has no MAC")); + for (uint32_t i = 0; i<6; i++){ + nonce[i] = MIBLEsensors[_slot].MAC[5-i]; + } + dataLen = _bufSize -5 ; + memcpy(_payload,_beacon->MAC, dataLen); //special packet + dataLen -= 7; + // AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*) _payload, dataLen); + } + // nonce: device MAC, device type, frame cnt, ext. cnt + memcpy((uint8_t*)&nonce+6,(uint8_t*)&_beacon->productID,2); + nonce[8] = _beacon->counter; + memcpy((uint8_t*)&nonce+9,(uint8_t*)&_payload[dataLen],3); + // memcpy((uint8_t*)&tag,(uint8_t*)&_payload[dataLen-4],4); + memcpy((uint8_t*)&tag,(uint8_t*)&_buf[_bufSize-4],4); + } + else if(_version == 3){ + // nonce: frame_ctrl, device type, ext. cnt, frame cnt, device MAC(only first 5 bytes) + memcpy(_payload,(uint8_t*)&_beacon->capability, dataLen); //special packet + nonceLen = 13; + memcpy((uint8_t*)&nonce,(uint8_t*)&_beacon->frame,2); + memcpy((uint8_t*)&nonce+2,(uint8_t*)&_beacon->productID,2); + nonce[4] = _beacon->counter; + memcpy((uint8_t*)&nonce+5,(uint8_t*)&_buf[_bufSize-4],3); + for (uint32_t i = 0; i<5; i++){ + nonce[i+8] = _beacon->MAC[i]; + } + tag[0] = _buf[_bufSize-1]; + // AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*) nonce, 13); + dataLen -= 4; + } + else{ + AddLog(LOG_LEVEL_DEBUG,PSTR("M32: unexpected decryption version:%u"),_version); // should never happen + } + br_aes_small_ctrcbc_keys keyCtx; - br_aes_small_ctrcbc_init(&keyCtx, _bindkey, sizeof(_bindkey)); + br_aes_small_ctrcbc_init(&keyCtx, MIBLEsensors[_slot].key, 16); + // br_aes_small_ctrcbc_init(&keyCtx, _testKey, 16); br_ccm_context ctx; br_ccm_init(&ctx, &keyCtx.vtable); - br_ccm_reset(&ctx, nonce, sizeof(nonce), sizeof(authData), data_len, sizeof(tag)); + br_ccm_reset(&ctx, nonce, nonceLen, sizeof(authData), dataLen, sizeof(tag)); br_ccm_aad_inject(&ctx, authData, sizeof(authData)); br_ccm_flip(&ctx); - - - memcpy(payload,packet->payload,data_len); //we want to be sure about 4-byte alignement - br_ccm_run(&ctx, 0, payload, data_len); - memcpy((uint8_t*)packet->payload+1,payload,data_len); //back to the packet + br_ccm_run(&ctx, 0, _payload, dataLen); ret = br_ccm_check_tag(&ctx, &tag); - AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Err:%i, Decrypted : %02x %02x %02x %02x %02x "), ret, packet->payload[1],packet->payload[2],packet->payload[3],packet->payload[4],packet->payload[5]); - return ret-1; + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: decrypted in %.2f mSec"),enctime); + // AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*) _payload, dataLen); + return ret; } -#endif // USE_MI_DECRYPTION /*********************************************************************************************\ * common functions \*********************************************************************************************/ - /** * @brief Return the slot number of a known sensor or return create new sensor slot * @@ -641,7 +374,6 @@ int MI32_decryptPacket(char *_buf, uint16_t _bufSize, uint32_t _type){ * @return uint32_t Known or new slot in the sensors-vector */ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter){ - DEBUG_SENSOR_LOG(PSTR("%s: will test ID-type: %x"),D_CMND_MI32, _type); bool _success = false; for (uint32_t i=0;i100.0f) value=100.0f; //clamp it for now + history[_hour] = (((uint8_t)(value/5.0f))+1) + 0b10000000; //lux + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: history lux: %u in hour:%u"),history[_hour], _hour); + break; +#ifdef USE_MI_ESP32_ENERGY + case 100: // energy + if(value == 0.0f) value = 1.0f; + uint8_t _watt = ((uint8_t)(MI32ln(value))*2) + 0b10000000; //watt + history[_hour] = _watt; + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: history energy: %u for value:%u"),history[_hour], value); //still playing with the mapping + break; +#endif //USE_MI_ESP32_ENERGY + } +} + +/** + * @brief Returns a value betwenn 0-21 for use as a data point in the history graph of the extended web UI + * + * @param history - pointer to uint8_t[23] + * @param hour - hour of datapoint + * @return uint8_t - value for the y-axis, should be between 0-21 + */ +uint8_t MI32fetchHistory(uint8_t *history, uint32_t hour){ + if(hour>23) { + return 0;} //should never happen + if(bitRead(history[hour],7) == 0) { + return 0; //invalidated data + } + return (history[hour]) - 0b10000000; +} + +/** + * @brief Invalidates the history data of the following hour by setting MSB to 0, should be called at FUNC_JSON_APPEND + * + */ +void Mi32invalidateOldHistory(){ + uint32_t _hour = (LocalTime()%SECS_PER_DAY)/SECS_PER_HOUR; + static uint32_t _lastInvalidatedHour = 99; + if (_lastInvalidatedHour == _hour){ + return; + } + uint32_t _nextHour = (_hour>22)?0:_hour+1; + for(auto _sensor:MIBLEsensors){ + if(_sensor.feature.temp == 1){ + bitClear(_sensor.temp_history[_nextHour],7); + } + if(_sensor.feature.hum == 1){ + bitClear(_sensor.hum_history[_nextHour],7); + } + if(_sensor.feature.lux == 1){ + bitClear(_sensor.lux_history[_nextHour],7); + } + } + _lastInvalidatedHour = _hour; +} + +#endif //USE_MI_EXT_GUI /*********************************************************************************************\ * init NimBLE \*********************************************************************************************/ void MI32PreInit(void) { - MIBLEsensors.reserve(10); - MIBLEbindKeys.reserve(10); - MIBLEscanResult.reserve(20); + MI32.mode.init = false; //test section for options @@ -758,7 +620,13 @@ void MI32PreInit(void) { MI32.option.directBridgeMode = 0; MI32.option.showRSSI = 1; MI32.option.ignoreBogusBattery = 1; // from advertisements - MI32.option.holdBackFirstAutodiscovery = 1; + + MI32loadCfg(); + if(MIBLEsensors.size()>0){ + MI32.mode.didGetConfig = 1; + } + + MI32.beAdvCB = nullptr; AddLog(LOG_LEVEL_INFO,PSTR("M32: pre-init")); } @@ -775,52 +643,370 @@ void MI32Init(void) { } } + if(MI32.mode.didGetConfig){ + MI32.mode.didStartHAP = 0; + #ifdef USE_MI_HOMEKIT + MI32getSetupCodeFromMAC(MI32.hk_setup_code); + AddLog(LOG_LEVEL_INFO,PSTR("M32: Init HAP core")); + mi_homekit_main(); + #else + MI32.mode.didStartHAP = 1; + #endif //USE_MI_HOMEKIT + } + if (!MI32.mode.init) { - NimBLEDevice::init(""); + NimBLEDevice::init("MI32"); AddLog(LOG_LEVEL_INFO,PSTR("M32: Init BLE device")); - MI32.mode.canScan = 1; MI32.mode.init = 1; - MI32.period = Settings->tele_period; MI32StartScanTask(); // Let's get started !! } +#ifdef USE_MI_EXT_GUI +#ifdef USE_MI_ESP32_ENERGY + MI32.energy_history = (uint8_t*) calloc(24,1); +#endif //USE_MI_ESP32_ENERGY +#endif //USE_MI_EXT_GUI return; } +/*********************************************************************************************\ + * Berry section - partly used by HomeKit too +\*********************************************************************************************/ +extern "C" { + + bool MI32runBerryConnection(uint8_t operation){ + if(MI32.conCtx != nullptr){ + MI32.conCtx->operation = operation%100; + AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Berry connection op: %d, addrType: %d"),MI32.conCtx->operation, MI32.conCtx->addrType); + MI32StartConnectionTask(); + return true; + } + return false; + } + + void MI32setBerryConnCB(void* function, uint8_t *buffer){ + if(MI32.conCtx == nullptr){ + MI32.conCtx = new MI32connectionContextBerry_t; + } + MI32.conCtx->buffer = buffer; + MI32.beConnCB = function; + AddLog(LOG_LEVEL_INFO,PSTR("M32: Connection Ctx created")); + } + + bool MI32setBerryCtxSvc(const char *Svc){ + if(MI32.conCtx != nullptr){ + MI32.conCtx->serviceUUID = NimBLEUUID(Svc); + AddLog(LOG_LEVEL_INFO,PSTR("M32: SVC: %s"),MI32.conCtx->serviceUUID.toString().c_str()); + // AddLog(LOG_LEVEL_INFO,PSTR("M32: SVC: %s"),Svc); + return true; + } + return false; + } + + bool MI32setBerryCtxChr(const char *Chr){ + if(MI32.conCtx != nullptr){ + MI32.conCtx->charUUID = NimBLEUUID(Chr); + AddLog(LOG_LEVEL_INFO,PSTR("M32: CHR: %s"),MI32.conCtx->charUUID.toString().c_str()); + // AddLog(LOG_LEVEL_INFO,PSTR("M32: CHR: %s"),Chr); + return true; + } + return false; + } + + bool MI32setBerryCtxMAC(uint8_t *MAC, uint8_t type){ + if(MI32.conCtx != nullptr){ + MI32.conCtx->MAC = MAC; + if(type<4) MI32.conCtx->addrType = type; + else MI32.conCtx->addrType = 0; + return true; + } + return false; + } + + void MI32setBerryAdvCB(void* function, uint8_t *buffer){ + MI32.beAdvCB = function; + MI32.beAdvBuf = buffer; + } + + void MI32setBatteryForSlot(uint32_t slot, uint8_t value){ + if(slot>MIBLEsensors.size()-1) return; + if(MIBLEsensors[slot].feature.bat){ + MIBLEsensors[slot].bat = value; + } + } + + void MI32setHumidityForSlot(uint32_t slot, float value){ + if(slot>MIBLEsensors.size()-1) return; + if(MIBLEsensors[slot].feature.hum){ + MIBLEsensors[slot].hum = value; + } + } + + void MI32setTemperatureForSlot(uint32_t slot, float value){ + if(slot>MIBLEsensors.size()-1) return; + if(MIBLEsensors[slot].feature.temp){ + MIBLEsensors[slot].temp = value; + } + } + + uint32_t MI32numberOfDevices(){ + return MIBLEsensors.size(); + } + + uint8_t * MI32getDeviceMAC(uint32_t slot){ + if(slot>MIBLEsensors.size()-1) return NULL; + return MIBLEsensors[slot].MAC; + } + + const char * MI32getDeviceName(uint32_t slot){ + if(slot>MIBLEsensors.size()-1) return ""; + return kMI32DeviceType[MIBLEsensors[slot].type-1]; + } + +} //extern "C" +/*********************************************************************************************\ + * Homekit section +\*********************************************************************************************/ +#ifdef USE_MI_HOMEKIT +extern "C" { + + const char * MI32getSetupCode(){ + return (const char*)MI32.hk_setup_code; + } + + uint32_t MI32numOfRelays(){ + if(TasmotaGlobal.devices_present>0) MI32.HKinfoMsg = MI32_HAP_OUTLET_ADDED; + return TasmotaGlobal.devices_present; + } + + void MI32setRelayFromHK(uint32_t relay, bool onOff){ + ExecuteCommandPower(relay, onOff, SRC_IGNORE); + } + + uint32_t MI32getDeviceType(uint32_t slot){ + return MIBLEsensors[slot].type; + } + +/** + * @brief Get at least a bit of the status of the HAP core, i.e. to reduce the activy of the driver while doing the pairing + * + * @param event + */ + void MI32passHapEvent(uint32_t event){ + switch(event){ + case 5: //HAP_EVENT_PAIRING_STARTED + MI32suspendScanTask(); + default: + vTaskResume(MI32.ScanTask); + } + if(event==4){ + MI32.HKinfoMsg = MI32_HAP_CONTROLLER_DISCONNECTED; + MI32.HKconnectedControllers--; + } + if(event==3){ + MI32.HKinfoMsg = MI32_HAP_CONTROLLER_CONNECTED; + MI32.HKconnectedControllers++; + } + } + + void MI32didStartHAP(bool HAPdidStart){ + if(HAPdidStart) { + MI32.mode.didStartHAP = 1; + MI32.HKinfoMsg = MI32_HAP_DID_START; + } + else{ + MI32.HKinfoMsg = MI32_HAP_DID_NOT_START; + } + } + +/** + * @brief Simply store the writeable HAP characteristics as void pointers in the "main" driver for updates of the values + * + * @param slot - sensor slot in MIBLEsensors + * @param type - sensors type, except for the buttons this is equal to the mibeacon types + * @param handle - a void ponter to a characteristic + */ + void MI32saveHAPhandles(uint32_t slot, uint32_t type, void* handle){ + // AddLog(LOG_LEVEL_INFO,PSTR("M32: pass ptr to hap service, type:%u"), type); + switch(type){ + case 1000: case 1001: case 1002: case 1003: case 1004: case 1005: + MIBLEsensors[slot].button_hap_service[type-1000] = handle; + break; + case 0x04: + MIBLEsensors[slot].temp_hap_service = handle; + break; + case 0x06: + MIBLEsensors[slot].hum_hap_service = handle; + break; + case 0x0a: + MIBLEsensors[slot].bat_hap_service = handle; + break; + case 0x07: + MIBLEsensors[slot].light_hap_service = handle; + break; + case 0x0f: + MIBLEsensors[slot].motion_hap_service = handle; + break; + case 0x14: + MIBLEsensors[slot].leak_hap_service = handle; + break; + case 0x19: + MIBLEsensors[slot].door_sensor_hap_service = handle; + break; + case 0xf0: + if(slot>3) break; //support only 4 for now + MI32.outlet_hap_service[slot] = handle; + break; + } + } +} + +/** + * @brief Creates a simplified setup code from the Wifi MAC for HomeKit by converting every ascii-converted byte to 1, if it not 2-9 + * Example: AABBCC1234f2 + * -> 111-11-234 + * This is no security feature, only for convenience + * * @param setupcode + */ + void MI32getSetupCodeFromMAC(char *setupcode){ + uint8_t _mac[6]; + char _macStr[13] = { 0 }; + WiFi.macAddress(_mac); + ToHex_P(_mac,6,_macStr,13); + AddLog(LOG_LEVEL_INFO,PSTR("M32: Wifi MAC: %s"), _macStr); + for(int i = 0; i<10; i++){ + if(_macStr[i]>'9' || _macStr[i]<'1') setupcode[i]='1'; + else setupcode[i] = _macStr[i]; + } + setupcode[3] = '-'; + setupcode[6] = '-'; + setupcode[10] = 0; + AddLog(LOG_LEVEL_INFO,PSTR("M32: HK setup code: %s"), setupcode); + return; + } + +#endif //USE_MI_HOMEKIT +/*********************************************************************************************\ + * Config section +\*********************************************************************************************/ + +void MI32loadCfg(){ + if (TfsFileExists("/mi32cfg")){ + MIBLEsensors.reserve(10); + const size_t _buf_size = 2048; + char * _filebuf = (char*)calloc(_buf_size,1); + AddLog(LOG_LEVEL_INFO,PSTR("M32: found config file")); + if(TfsLoadFile("/mi32cfg",(uint8_t*)_filebuf,_buf_size)){ + AddLog(LOG_LEVEL_INFO,PSTR("M32: %s"),_filebuf); + JsonParser parser(_filebuf); + JsonParserToken root = parser.getRoot(); + if (!root) {AddLog(LOG_LEVEL_INFO,PSTR("M32: invalid root "));} + JsonParserArray arr = root.getArray(); + if (!arr) {AddLog(LOG_LEVEL_INFO,PSTR("M32: invalid array object"));; } + bool _error; + int32_t _numberOfDevices; + for (auto _dev : arr) { + AddLog(LOG_LEVEL_INFO,PSTR("M32: found device in config file")); + JsonParserObject _device = _dev.getObject(); + uint8_t _mac[6]; + JsonParserToken _val = _device[PSTR("MAC")]; + _error = true; + if (_val) { + char *_macStr = (char *)_val.getStr(); + AddLog(LOG_LEVEL_INFO,PSTR("M32: found MAC: %s"), _macStr); + if(strlen(_macStr)!=12){ + AddLog(LOG_LEVEL_INFO,PSTR("M32: wrong MAC length: %u"), strlen(_macStr)); + break; + } + MI32HexStringToBytes(_macStr,_mac); + _val = _device[PSTR("PID")]; + if(_val){ + uint8_t _pid[2]; + char *_pidStr = (char *)_val.getStr(); + AddLog(LOG_LEVEL_INFO,PSTR("M32: found PID: %s"), _pidStr); + if(strlen(_pidStr)!=4){ + AddLog(LOG_LEVEL_INFO,PSTR("M32: wrong PID length: %u"), strlen(_pidStr)); + break; + } + MI32HexStringToBytes(_pidStr,_pid); + uint16_t _pid16 = _pid[0]*256 + _pid[1]; + _numberOfDevices = MIBLEgetSensorSlot(_mac,_pid16,0); + _error = false; + } + } + _val = _device[PSTR("key")]; + if (_val) { + mi_bindKey_t _keyMAC; + uint8_t *_key = (uint8_t*) malloc(16); + char *_keyStr = (char *)_val.getStr(); + if(strlen(_keyStr)==0){ + continue; + } + if(strlen(_keyStr)!=32){ + _error = true; + break; + } + MI32HexStringToBytes(_keyStr,_key); + MIBLEsensors[_numberOfDevices].key = _key; + } + } + if(!_error){ + AddLog(LOG_LEVEL_INFO,PSTR("M32: added %u devices from config file"), _numberOfDevices + 1); + } + } + free(_filebuf); + } +} + +void MI32saveConfig(){ + const size_t _buf_size = 2048; + char * _filebuf = (char*) malloc(_buf_size); + _filebuf[0] = '['; + uint32_t _pos = 1; + for(auto _sensor: MIBLEsensors){ + char _MAC[13]; + ToHex_P(_sensor.MAC,6,_MAC,13); + char _key[33]; + _key[0] = 0; + if(_sensor.key != nullptr){ + ToHex_P(_sensor.key,16,_key,33); + } + uint32_t _inc = snprintf_P(_filebuf+_pos,200,PSTR("{\"MAC\":\"%s\",\"PID\":\"%04x\",\"key\":\"%s\"},"),_MAC,kMI32DeviceID[_sensor.type - 1],_key); + _pos += _inc; + } + _filebuf[_pos-1] = ']'; + _filebuf[_pos] = '\0'; + if (_pos>2){ + AddLog(LOG_LEVEL_INFO,PSTR("M32: %s"), _filebuf); + if (TfsSaveFile("/mi32cfg",(uint8_t*)_filebuf,_pos+1)) { + AddLog(LOG_LEVEL_INFO,PSTR("M32: %u bytes written to config"), _pos+1); + } + } + else{ + AddLog(LOG_LEVEL_ERROR,PSTR("M32: nothing written to config")); + } + free(_filebuf); +} + /*********************************************************************************************\ * Task section \*********************************************************************************************/ +void MI32suspendScanTask(void){ + if (MI32.ScanTask != nullptr) vTaskSuspend(MI32.ScanTask); +} + void MI32StartTask(uint32_t task){ switch(task){ case MI32_TASK_SCAN: - if (MI32.mode.canScan == 0 || MI32.mode.willConnect == 1) return; + if (MI32.mode.willConnect == 1) return; if (MI32.mode.runningScan == 1 || MI32.mode.connected == 1) return; MI32StartScanTask(); break; case MI32_TASK_CONN: if (MI32.mode.canConnect == 0 || MI32.mode.willConnect == 1 ) return; if (MI32.mode.connected == 1) return; - MI32StartSensorTask(); - break; - case MI32_TASK_TIME: - if (MI32.mode.shallSetTime == 0) return; - MI32StartTimeTask(); - break; - case MI32_TASK_BATT: - if (MI32.mode.willReadBatt == 1) return; - switch(MIBLEsensors[MI32.state.sensor].type) { - case LYWSD03MMC: case MHOC401: // the "original" battery value is crap ... - MI32.mode.willReadBatt = 1; - MI32StartSensorTask(); // ... but the part of the temp/hum-message is good! - break; - default: - MI32StartBatteryTask(); - } - break; - case MI32_TASK_UNIT: - if (MI32.mode.shallSetUnit == 0) return; - MI32StartUnitTask(); + MI32StartConnectionTask(); break; default: break; @@ -828,49 +1014,27 @@ void MI32StartTask(uint32_t task){ } bool MI32ConnectActiveSensor(){ // only use inside a task !! - MI32.mode.connected = 0; - - NimBLEAddress _address = NimBLEAddress(MIBLEsensors[MI32.state.sensor].MAC); + NimBLEAddress _address = NimBLEAddress(MI32.conCtx->MAC, MI32.conCtx->addrType); + MI32Client = nullptr; if(NimBLEDevice::getClientListSize()) { - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: found any clients in the list"),D_CMND_MI32); MI32Client = NimBLEDevice::getClientByPeerAddress(_address); - if(MI32Client){ - // Should be impossible - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: got connected client"),D_CMND_MI32); - } - else { - // Should be the norm after the first iteration - MI32Client = NimBLEDevice::getDisconnectedClient(); - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: got disconnected client"),D_CMND_MI32); - } } - - if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { - MI32.mode.willConnect = 0; - DEBUG_SENSOR_LOG(PSTR("%s: max connection already reached"),D_CMND_MI32); - return false; - } - if(!MI32Client) { - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: will create client"),D_CMND_MI32); - MI32Client = NimBLEDevice::createClient(); + if (!MI32Client){ + MI32Client = NimBLEDevice::createClient(_address); MI32Client->setClientCallbacks(&MI32SensorCB , false); - MI32Client->setConnectionParams(12,12,0,48); - MI32Client->setConnectTimeout(30); - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: did create new client"),D_CMND_MI32); } - vTaskDelay(300/ portTICK_PERIOD_MS); - if (!MI32Client->connect(_address,false)) { + if (!MI32Client->connect(false)) { MI32.mode.willConnect = 0; - // NimBLEDevice::deleteClient(MI32Client); - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: did not connect client"),D_CMND_MI32); + NimBLEDevice::deleteClient(MI32Client); + // AddLog(LOG_LEVEL_ERROR,PSTR("M32: did not connect client")); return false; } return true; - // } } void MI32StartScanTask(){ if (MI32.mode.connected) return; + if(MI32.ScanTask!=nullptr) vTaskDelete(MI32.ScanTask); MI32.mode.runningScan = 1; xTaskCreatePinnedToCore( MI32ScanTask, /* Function to implement the task */ @@ -878,447 +1042,287 @@ void MI32StartScanTask(){ 2048, /* Stack size in words */ NULL, /* Task input parameter */ 0, /* Priority of the task */ - NULL, /* Task handle. */ + &MI32.ScanTask, /* Task handle. */ 0); /* Core where the task should run */ - AddLog(LOG_LEVEL_DEBUG,PSTR("%s: Start scanning"),D_CMND_MI32); } void MI32ScanTask(void *pvParameters){ + if(MI32.mode.didGetConfig){ + vTaskDelay(5000/ portTICK_PERIOD_MS); + } if (MI32Scan == nullptr) MI32Scan = NimBLEDevice::getScan(); - // DEBUG_SENSOR_LOG(PSTR("%s: Scan Cache Length: %u"),D_CMND_MI32, MI32Scan->getResults().getCount()); + MI32Scan->setInterval(70); MI32Scan->setWindow(50); MI32Scan->setAdvertisedDeviceCallbacks(&MI32ScanCallbacks,true); MI32Scan->setActiveScan(false); + MI32Scan->setMaxResults(0); MI32Scan->start(0, MI32scanEndedCB, true); // never stop scanning, will pause automatically while connecting - + MI32.infoMsg = MI32_START_SCANNING; + uint32_t timer = 0; for(;;){ - if(MI32.mode.shallClearResults){ - MI32Scan->clearResults(); - MI32.mode.shallClearResults=0; - } vTaskDelay(10000/ portTICK_PERIOD_MS); } vTaskDelete( NULL ); } -void MI32StartSensorTask(){ - MI32.mode.willConnect = 1; - switch(MIBLEsensors[MI32.state.sensor].type){ - case LYWSD03MMC: case MHOC401: - break; - default: - MI32.mode.willConnect = 0; - return; - } +bool MI32StartConnectionTask(){ + if(MI32.conCtx == nullptr) return false; + if(MI32.conCtx->buffer == nullptr) return false; + MI32.mode.willConnect = 1; + MI32Scan->stop(); + MI32suspendScanTask(); xTaskCreatePinnedToCore( - MI32SensorTask, /* Function to implement the task */ - "MI32SensorTask", /* Name of the task */ + MI32ConnectionTask, /* Function to implement the task */ + "MI32ConnectionTask", /* Name of the task */ 4096, /* Stack size in words */ NULL, /* Task input parameter */ - 15, /* Priority of the task */ - NULL, /* Task handle. */ + 2, /* Priority of the task */ + &MI32.ConnTask, /* Task handle. */ 0); /* Core where the task should run */ - AddLog(LOG_LEVEL_DEBUG,PSTR("%s: Start sensor connections"),D_CMND_MI32); - AddLog(LOG_LEVEL_DEBUG,PSTR("%s: with sensor: %u"),D_CMND_MI32, MI32.state.sensor); + return true; } -void MI32SensorTask(void *pvParameters){ +void MI32ConnectionTask(void *pvParameters){ + MI32.mode.connected = 0; + MI32.conCtx->error = MI32_CONN_NO_ERROR; if (MI32ConnectActiveSensor()){ + MI32.mode.readingDone = 0; uint32_t timer = 0; while (MI32.mode.connected == 0){ if (timer>1000){ MI32Client->disconnect(); - // NimBLEDevice::deleteClient(MI32Client); + NimBLEDevice::deleteClient(MI32Client); MI32.mode.willConnect = 0; - MI32.mode.willReadBatt = 0; //could be a "battery task" for LYWSD03MMC or MHO-C401 + MI32.mode.triggerBerryConnCB = 1; + MI32.conCtx->error = MI32_CONN_NO_CONNECT; // not connected + MI32StartTask(MI32_TASK_SCAN); vTaskDelay(100/ portTICK_PERIOD_MS); vTaskDelete( NULL ); } timer++; vTaskDelay(10/ portTICK_PERIOD_MS); } + NimBLERemoteService* pSvc = nullptr; + NimBLERemoteCharacteristic* pChr = nullptr; - timer = 150; - switch(MIBLEsensors[MI32.state.sensor].type){ - case LYWSD03MMC: case MHOC401: - MI32.mode.readingDone = 0; - if(MI32connectLYWSD03forNotification()) timer=0; - break; - default: - break; + pSvc = MI32Client->getService(MI32.conCtx->serviceUUID); + if(pSvc) { + pChr = pSvc->getCharacteristic(MI32.conCtx->charUUID); } + else{ + MI32.conCtx->error = MI32_CONN_NO_SERVICE; + } + if (pChr){ + switch(MI32.conCtx->operation){ + case 11: + if(pChr->canRead()) { + std::string _val = pChr->readValue(); + MI32.conCtx->buffer[0] = (uint8_t)_val.size(); + const char *_c_val = _val.c_str(); + memcpy( MI32.conCtx->buffer + 1,_c_val,MI32.conCtx->buffer[0]); + } + else{ + MI32.conCtx->error = MI32_CONN_CAN_NOT_READ; + } + break; + case 13: + if(pChr->canNotify()) { + if(pChr->subscribe(true,MI32notifyCB,false)) AddLog(LOG_LEVEL_DEBUG,PSTR("M32: subscribe")); + } + else{ + MI32.conCtx->error = MI32_CONN_CAN_NOT_NOTIFY; + } + break; + case 12: + if(pChr->canWrite()) { + uint8_t len = MI32.conCtx->buffer[0]; + if(pChr->writeValue(MI32.conCtx->buffer + 1,len,true)) { // true is important ! + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: write op done")); + } + else{ + MI32.conCtx->error = MI32_CONN_DID_NOT_WRITE; + } + } + else{ + MI32.conCtx->error = MI32_CONN_CAN_NOT_WRITE; + } + MI32.mode.readingDone = 1; + break; + default: + break; + } + } + else{ + MI32.conCtx->error = MI32_CONN_NO_CHARACTERISTIC; + } + timer = 0; - while (!MI32.mode.readingDone){ - if (timer>150){ + while (timer<150){ + if (MI32.mode.readingDone){ break; } + else{ + MI32.conCtx->error = MI32_CONN_NOTIFY_TIMEOUT; //did not read on notify - timeout + } timer++; vTaskDelay(100/ portTICK_PERIOD_MS); - } - MI32Client->disconnect(); - DEBUG_SENSOR_LOG(PSTR("%s: requested disconnect"),D_CMND_MI32); + } - - MI32.mode.connected = 0; - vTaskDelete( NULL ); -} - -bool MI32connectLYWSD03forNotification(){ - NimBLERemoteService* pSvc = nullptr; - NimBLERemoteCharacteristic* pChr = nullptr; - static BLEUUID serviceUUID(0xebe0ccb0,0x7a0a,0x4b0c,0x8a1a6ff2997da3a6); - static BLEUUID charUUID(0xebe0ccc1,0x7a0a,0x4b0c,0x8a1a6ff2997da3a6); - pSvc = MI32Client->getService(serviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(charUUID); + MI32Client->disconnect(); + DEBUG_SENSOR_LOG(PSTR("M32: requested disconnect")); } - if (pChr){ - if(pChr->canNotify()) { - if(pChr->subscribe(true,MI32notifyCB,false)) { - return true; - } - } + else{ + MI32.conCtx->error = MI32_CONN_NO_CONNECT; // not connected } - return false; -} - -void MI32StartTimeTask(){ - MI32.mode.willConnect = 1; - xTaskCreatePinnedToCore( - MI32TimeTask, /* Function to implement the task */ - "MI32TimeTask", /* Name of the task */ - 4096, /* Stack size in words */ - NULL, /* Task input parameter */ - 15, /* Priority of the task */ - NULL, /* Task handle. */ - 0); /* Core where the task should run */ - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: Start time set"),D_CMND_MI32); - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: with sensor: %u"),D_CMND_MI32, MI32.state.sensor); -} - -void MI32TimeTask(void *pvParameters){ - if (MIBLEsensors[MI32.state.sensor].type != LYWSD02 && MIBLEsensors[MI32.state.sensor].type != MHOC303) { - MI32.mode.shallSetTime = 0; - vTaskDelete( NULL ); - } - if(MI32ConnectActiveSensor()){ - uint32_t timer = 0; - while (MI32.mode.connected == 0){ - if (timer>1000){ - break; - } - timer++; - vTaskDelay(10/ portTICK_PERIOD_MS); - } - - NimBLERemoteService* pSvc = nullptr; - NimBLERemoteCharacteristic* pChr = nullptr; - static BLEUUID serviceUUID(0xEBE0CCB0,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); - static BLEUUID charUUID(0xEBE0CCB7,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); - pSvc = MI32Client->getService(serviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(charUUID); - - } - if (pChr){ - if(pChr->canWrite()) { - union { - uint8_t buf[5]; - uint32_t time; - } _utc; - _utc.time = Rtc.utc_time; - _utc.buf[4] = Rtc.time_timezone / 60; - - if(!pChr->writeValue(_utc.buf,sizeof(_utc.buf),true)) { // true is important ! - MI32.mode.willConnect = 0; - MI32Client->disconnect(); - } - else { - MI32.mode.shallSetTime = 0; - MI32.mode.willSetTime = 0; - } - } - } - MI32Client->disconnect(); - } - MI32.mode.connected = 0; - MI32.mode.canScan = 1; + MI32.mode.triggerBerryConnCB = 1; + MI32StartTask(MI32_TASK_SCAN); vTaskDelete( NULL ); } -void MI32StartUnitTask(){ - MI32.mode.willConnect = 1; - xTaskCreatePinnedToCore( - MI32UnitTask, /* Function to implement the task */ - "MI32UnitTask", /* Name of the task */ - 4096, /* Stack size in words */ - NULL, /* Task input parameter */ - 15, /* Priority of the task */ - NULL, /* Task handle. */ - 0); /* Core where the task should run */ - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: Start unit set"),D_CMND_MI32); - // AddLog(LOG_LEVEL_DEBUG,PSTR("%s: with sensor: %u"),D_CMND_MI32, MI32.state.sensor); -} - -void MI32UnitTask(void *pvParameters){ - if (MIBLEsensors[MI32.state.sensor].type != LYWSD02 && MIBLEsensors[MI32.state.sensor].type != MHOC303) { - MI32.mode.shallSetUnit = 0; - vTaskDelete( NULL ); - } - - if(MI32ConnectActiveSensor()){ - uint32_t timer = 0; - while (MI32.mode.connected == 0){ - if (timer>1000){ - break; - } - timer++; - vTaskDelay(10/ portTICK_PERIOD_MS); - } - - NimBLERemoteService* pSvc = nullptr; - NimBLERemoteCharacteristic* pChr = nullptr; - static BLEUUID serviceUUID(0xEBE0CCB0,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); - static BLEUUID charUUID(0xEBE0CCBE,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); - pSvc = MI32Client->getService(serviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(charUUID); - } - - if(pChr->canRead()){ - uint8_t curUnit; - const char *buf = pChr->readValue().c_str(); - if( buf[0] != 0 && buf[0]<101 ){ - curUnit = buf[0]; - } - - if(pChr->canWrite()) { - curUnit = curUnit == 0x01?0xFF:0x01; // C/F - - if(!pChr->writeValue(&curUnit,sizeof(curUnit),true)) { // true is important ! - MI32.mode.willConnect = 0; - MI32Client->disconnect(); - } - else { - MI32.mode.shallSetUnit = 0; - MI32.mode.willSetUnit = 0; - } - } - } - MI32Client->disconnect(); - } - - MI32.mode.connected = 0; - MI32.mode.canScan = 1; - vTaskDelete( NULL ); -} - -void MI32StartBatteryTask(){ - if (MI32.mode.connected) return; - MI32.mode.willReadBatt = 1; - MI32.mode.willConnect = 1; - MI32.mode.canScan = 0; - - switch (MIBLEsensors[MI32.state.sensor].type){ - case LYWSD03MMC: case MJ_HT_V1: case CGG1: case NLIGHT: case MJYD2S: case YEERC: case MHOC401: - MI32.mode.willConnect = 0; - MI32.mode.willReadBatt = 0; - return; - } - - xTaskCreatePinnedToCore( - MI32BatteryTask, /* Function to implement the task */ - "MI32BatteryTask", /* Name of the task */ - 4096, /* Stack size in words */ - NULL, /* Task input parameter */ - 15, /* Priority of the task */ - NULL, /* Task handle. */ - 0); /* Core where the task should run */ -} - -void MI32BatteryTask(void *pvParameters){ - // all reported battery values are probably crap, but we allow the reading on demand - - MI32.mode.connected = 0; - if(MI32ConnectActiveSensor()){ - uint32_t timer = 0; - while (MI32.mode.connected == 0){ - if (timer>1000){ - break; - } - timer++; - vTaskDelay(30/ portTICK_PERIOD_MS); - } - - switch(MIBLEsensors[MI32.state.sensor].type){ - case FLORA: case LYWSD02: case CGD1: - MI32batteryRead(MIBLEsensors[MI32.state.sensor].type); - break; - } - MI32Client->disconnect(); - } - MI32.mode.willReadBatt = 0; - MI32.mode.connected = 0; - vTaskDelete( NULL ); -} - -void MI32batteryRead(uint32_t _type){ - uint32_t timer = 0; - while (!MI32.mode.connected){ - if (timer>1000){ - break; - } - timer++; - vTaskDelay(10/ portTICK_PERIOD_MS); - } - DEBUG_SENSOR_LOG(PSTR("%s connected for battery"),kMI32DeviceType[MIBLEsensors[MI32.state.sensor].type-1] ); - NimBLERemoteService* pSvc = nullptr; - NimBLERemoteCharacteristic* pChr = nullptr; - - switch(_type){ - case FLORA: - { - static BLEUUID _serviceUUID(0x00001204,0x0000,0x1000,0x800000805f9b34fb); - static BLEUUID _charUUID(0x00001a02,0x0000,0x1000,0x800000805f9b34fb); - pSvc = MI32Client->getService(_serviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(_charUUID); - } - } - break; - case LYWSD02: - { - static BLEUUID _serviceUUID(0xEBE0CCB0,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); - static BLEUUID _charUUID(0xEBE0CCC4,0x7A0A,0x4B0C,0x8A1A6FF2997DA3A6); - pSvc = MI32Client->getService(_serviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(_charUUID); - } - } - break; - case CGD1: - { - static BLEUUID _serviceUUID((uint16_t)0x180F); - static BLEUUID _charUUID((uint16_t)0x2A19); - pSvc = MI32Client->getService(_serviceUUID); - if(pSvc) { - pChr = pSvc->getCharacteristic(_charUUID); - } - } - break; - } - - if (pChr){ - DEBUG_SENSOR_LOG(PSTR("%s: got %s char %s"),D_CMND_MI32, kMI32DeviceType[MIBLEsensors[MI32.state.sensor].type-1], pChr->getUUID().toString().c_str()); - if(pChr->canRead()) { - const char *buf = pChr->readValue().c_str(); - MI32readBat((char*)buf); - } - } - MI32.mode.readingDone = 1; -} - /*********************************************************************************************\ * parse the response from advertisements \*********************************************************************************************/ void MI32parseMiBeacon(char * _buf, uint32_t _slot, uint16_t _bufSize){ + float _tempFloat; - mi_beacon_t _beacon; + mi_beacon_t* _beacon = (mi_beacon_t*)_buf; + mi_payload_t _payload; - 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 DANGER!!! - memcpy((uint8_t*)&_beacon.MAC,(uint8_t*)&_beacon.MAC+1,6); // but shift back the MAC - _beacon.counter = _buf[4]; // restore the counter - } - else{ - memcpy((char *)&_beacon, _buf, _bufSize); - } + MIBLEsensors[_slot].lastCnt = _beacon->counter; - MIBLEsensors[_slot].lastCnt = _beacon.counter; -#ifdef USE_MI_DECRYPTION - int decryptRet = 0; - switch(MIBLEsensors[_slot].type){ - case LYWSD03MMC: case MHOC401: - if (_beacon.frame == 0x5858){ - decryptRet = MI32_decryptPacket((char*)&_beacon.productID,_bufSize, LYWSD03MMC); //start with PID - // AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*)&_beacon.productID,_bufSize); - } - else return; // 0x3058 holds no data, TODO: check for unpaired devices, that need connections - break; - case MJYD2S: - AddLog(LOG_LEVEL_DEBUG,PSTR("MJYD2S: %x"),_beacon.frame); - if (_beacon.frame == 0x5948){ // Now let's build/recreate a special MiBeacon - memmove((uint8_t*)&_beacon.MAC+6,(uint8_t*)&_beacon.MAC, _bufSize); // shift payload by the size of the MAC = 6 bytes - memcpy((uint8_t*)&_beacon.MAC,MIBLEsensors[_slot].MAC,6); // now insert the real MAC from our internal vector - _bufSize+=6; // the packet has grown - MI32_ReverseMAC(_beacon.MAC); // payload MAC is always reversed - AddLog(LOG_LEVEL_DEBUG,PSTR("MJYD2S: special packet")); - } - if (_beacon.frame != 0x5910){ - decryptRet = MI32_decryptPacket((char*)&_beacon.productID,_bufSize,MJYD2S); //start with PID - } - break; +#ifdef USE_MI_EXT_GUI + bitSet(MI32.widgetSlot,_slot); +#endif //USE_MI_EXT_GUI +if(_beacon->frame.includesObj == 0){ + return; //nothing to parse +} + +int decryptRet = 0; +if(_beacon->frame.isEncrypted){ + decryptRet = MI32_decryptPacket(_buf,_bufSize, (uint8_t*)&_payload,_slot); } -if(decryptRet!=0){ +else{ + uint32_t _offset = (_beacon->frame.includesCapability)?0:1; + uint32_t _payloadSize = (_beacon->frame.includesCapability)?_beacon->payload.size:_beacon->payload.ten; + if(_beacon->frame.includesMAC && _beacon->frame.includesObj) { + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: offset %u, size: %u"),_offset,_payloadSize); + memcpy((uint8_t*)&_payload,(uint8_t*)(&_beacon->payload)-_offset, _payloadSize + 3); + // AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*)&_payload,_payloadSize + 3); + } + } +if(decryptRet<0){ AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Decryption failed with error: %d"),decryptRet); + MIBLEsensors[_slot].feature.hasWrongKey = 1; return; } -#endif //USE_MI_DECRYPTION +// if (_beacon->frame.solicited){ +// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: sensor unbonded: %s"),kMI32DeviceType[MIBLEsensors[_slot].type-1]); +// } +// if (_beacon->frame.registered){ +// AddLog(LOG_LEVEL_DEBUG,PSTR("M32: registered: %s"),kMI32DeviceType[MIBLEsensors[_slot].type-1]); +// } - if(MIBLEsensors[_slot].type==6){ - DEBUG_SENSOR_LOG(PSTR("CGD1 no support for MiBeacon, type %u"),MIBLEsensors[_slot].type); - return; - } - AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u with payload type: %02x"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot,_beacon.type); - switch(_beacon.type){ + // AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u with payload type: %02x"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot,_payload.type); + MIBLEsensors[_slot].lastTime = millis(); + switch(_payload.type){ case 0x01: - MIBLEsensors[_slot].Btn=_beacon.Btn.num + (_beacon.Btn.longPress/2)*6; + if(_payload.Btn.type == 4){ //knob dimmer + if(_payload.Btn.num == 0){ + if(_payload.Btn.value<128){ + AddLog(LOG_LEVEL_DEBUG,PSTR("Rotate right: %u"),_payload.Btn.value); + } + else{ + AddLog(LOG_LEVEL_DEBUG,PSTR("Rotate left: %u"),256 - _payload.Btn.value); + } + } + else if(_payload.Btn.num<128){ + AddLog(LOG_LEVEL_DEBUG,PSTR("Rotate right: %u"),_payload.Btn.num); + } + else{ + AddLog(LOG_LEVEL_DEBUG,PSTR("Rotate left: %u"),256 - _payload.Btn.num); + } + return; //TODO: implement MQTT later + } + MIBLEsensors[_slot].Btn=_payload.Btn.num + (_payload.Btn.type/2)*6; MIBLEsensors[_slot].eventType.Btn = 1; MI32.mode.shallTriggerTele = 1; +#ifdef USE_MI_HOMEKIT + { + // {uint32_t _button = _payload.Btn.num + (_payload.Btn.type/2)*6; + uint32_t _singleLong = 0; + if(MIBLEsensors[_slot].Btn>5){ + MIBLEsensors[_slot].Btn = MIBLEsensors[_slot].Btn - 6; + _singleLong = 2; + } + if(MIBLEsensors[_slot].Btn>5) break; // + if((void**)MIBLEsensors[_slot].button_hap_service[MIBLEsensors[_slot].Btn] != nullptr){ + // AddLog(LOG_LEVEL_DEBUG,PSTR("Send Button %u: SingleLong:%u, pointer: %x"), MIBLEsensors[_slot].Btn,_singleLong,MIBLEsensors[_slot].button_hap_service[MIBLEsensors[_slot].Btn] ); + mi_homekit_update_value(MIBLEsensors[_slot].button_hap_service[MIBLEsensors[_slot].Btn], (float)_singleLong, 0x01); + } + } +#endif //USE_MI_HOMEKIT // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 1: U16: %u Button"), MIBLEsensors[_slot].Btn ); break; case 0x04: - _tempFloat=(float)(_beacon.temp)/10.0f; + _tempFloat=(float)(_payload.temp)/10.0f; if(_tempFloat<60){ MIBLEsensors[_slot].temp=_tempFloat; MIBLEsensors[_slot].eventType.temp = 1; DEBUG_SENSOR_LOG(PSTR("Mode 4: temp updated")); } - // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 4: U16: %u Temp"), _beacon.temp ); +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].temp_hap_service, _tempFloat, 0x04); +#endif //USE_MI_HOMEKIT +#ifdef USE_MI_EXT_GUI + MI32addHistory(MIBLEsensors[_slot].temp_history, _tempFloat, 0); +#endif //USE_MI_EXT_GUI + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 4: U16: %u Temp"), _payload.temp ); break; case 0x06: - _tempFloat=(float)(_beacon.hum)/10.0f; + _tempFloat=(float)(_payload.hum)/10.0f; if(_tempFloat<101){ MIBLEsensors[_slot].hum=_tempFloat; MIBLEsensors[_slot].eventType.hum = 1; DEBUG_SENSOR_LOG(PSTR("Mode 6: hum updated")); } - // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 6: U16: %u Hum"), _beacon.hum); +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].hum_hap_service, _tempFloat,0x06); +#endif //USE_MI_HOMEKIT +#ifdef USE_MI_EXT_GUI + MI32addHistory(MIBLEsensors[_slot].hum_history, _tempFloat, 1); +#endif //USE_MI_EXT_GUI + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 6: U16: %u Hum"), _payload.hum); break; case 0x07: - MIBLEsensors[_slot].lux=_beacon.lux & 0x00ffffff; + MIBLEsensors[_slot].lux=_payload.lux & 0x00ffffff; if(MIBLEsensors[_slot].type==MJYD2S){ MIBLEsensors[_slot].eventType.noMotion = 1; } MIBLEsensors[_slot].eventType.lux = 1; - // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 7: U24: %u Lux"), _beacon.lux & 0x00ffffff); +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].light_hap_service, (float)MIBLEsensors[_slot].lux,0x07); +#endif //USE_MI_HOMEKIT +#ifdef USE_MI_EXT_GUI + MI32addHistory(MIBLEsensors[_slot].lux_history, (float)MIBLEsensors[_slot].lux, 2); +#endif //USE_MI_EXT_GUI + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 7: U24: %u Lux"), _payload.lux & 0x00ffffff); break; case 0x08: - MIBLEsensors[_slot].moisture=_beacon.moist; + MIBLEsensors[_slot].moisture=_payload.moist; MIBLEsensors[_slot].eventType.moist = 1; DEBUG_SENSOR_LOG(PSTR("Mode 8: moisture updated")); - // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 8: U8: %u Moisture"), _beacon.moist); + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 8: U8: %u Moisture"), _payload.moist); break; case 0x09: - MIBLEsensors[_slot].fertility=_beacon.fert; + MIBLEsensors[_slot].fertility=_payload.fert; MIBLEsensors[_slot].eventType.fert = 1; DEBUG_SENSOR_LOG(PSTR("Mode 9: fertility updated")); - // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 9: U16: %u Fertility"), _beacon.fert); + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 9: U16: %u Fertility"), _payload.fert); break; case 0x0a: if(MI32.option.ignoreBogusBattery){ @@ -1326,56 +1330,85 @@ if(decryptRet!=0){ break; } } - if(_beacon.bat<101){ - MIBLEsensors[_slot].bat = _beacon.bat; + if(_payload.bat<101){ + MIBLEsensors[_slot].bat = _payload.bat; MIBLEsensors[_slot].eventType.bat = 1; DEBUG_SENSOR_LOG(PSTR("Mode a: bat updated")); - } - // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode a: U8: %u %%"), _beacon.bat); +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].bat_hap_service, (float)_payload.bat,0xa); +#endif //USE_MI_HOMEKIT + } + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode a: U8: %u %%"), _payload.bat); break; case 0x0d: - _tempFloat=(float)(_beacon.HT.temp)/10.0f; + _tempFloat=(float)(_payload.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; + _tempFloat=(float)(_payload.HT.hum)/10.0f; if(_tempFloat<100){ MIBLEsensors[_slot].hum = _tempFloat; DEBUG_SENSOR_LOG(PSTR("Mode d: hum updated")); } MIBLEsensors[_slot].eventType.tempHum = 1; - // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode d: U16: %x Temp U16: %x Hum"), _beacon.HT.temp, _beacon.HT.hum); + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode d: U16: %x Temp U16: %x Hum"), _payload.HT.temp, _payload.HT.hum); break; -#ifdef USE_MI_DECRYPTION + case 0x0f: - if (_beacon.ten!=0) break; + if (_payload.ten!=0) break; MIBLEsensors[_slot].eventType.motion = 1; - MIBLEsensors[_slot].lastTime = millis(); MIBLEsensors[_slot].events++; - MIBLEsensors[_slot].lux = _beacon.lux; + MIBLEsensors[_slot].lux = _payload.lux & 0x00ffffff; MIBLEsensors[_slot].eventType.lux = 1; MIBLEsensors[_slot].NMT = 0; MI32.mode.shallTriggerTele = 1; - // AddLog(LOG_LEVEL_DEBUG,PSTR("PIR: primary"),MIBLEsensors[_slot].lux ); +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].motion_hap_service, (float)1,0x0f); + mi_homekit_update_value(MIBLEsensors[_slot].light_hap_service, (float)_payload.lux,0x07); +#endif //USE_MI_HOMEKIT +#ifdef USE_MI_EXT_GUI + MI32addHistory(MIBLEsensors[_slot].lux_history, (float)MIBLEsensors[_slot].lux, 2); +#endif //USE_MI_EXT_GUI + // AddLog(LOG_LEVEL_DEBUG,PSTR("motion: primary"),MIBLEsensors[_slot].lux ); break; + case 0x14: + MIBLEsensors[_slot].leak = _payload.leak; + MIBLEsensors[_slot].eventType.leak = 1; + if(_payload.leak>0) MI32.mode.shallTriggerTele = 1; +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].leak_hap_service, (float)_payload.leak,0x14); +#endif //USE_MI_HOMEKIT + break; case 0x17: - MIBLEsensors[_slot].NMT = _beacon.NMT; + MIBLEsensors[_slot].NMT = _payload.NMT; MIBLEsensors[_slot].eventType.NMT = 1; MI32.mode.shallTriggerTele = 1; - // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 17: NMT: %u seconds"), _beacon.NMT); + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 17: NMT: %u seconds"), _payload.NMT); break; -#endif //USE_MI_DECRYPTION + case 0x19: + MIBLEsensors[_slot].door = _payload.door; + MIBLEsensors[_slot].eventType.door = 1; + MIBLEsensors[_slot].events++; + MI32.mode.shallTriggerTele = 1; +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].door_sensor_hap_service, (float)_payload.door,0x19); +#endif //USE_MI_HOMEKIT + // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 19: %u"), _payload.door); + break; + default: if (MIBLEsensors[_slot].type==NLIGHT){ - MIBLEsensors[_slot].eventType.motion = 1; //PIR + MIBLEsensors[_slot].eventType.motion = 1; //motion MIBLEsensors[_slot].events++; MIBLEsensors[_slot].NMT = 0; - MIBLEsensors[_slot].lastTime = millis(); MI32.mode.shallTriggerTele = 1; - // AddLog(LOG_LEVEL_DEBUG,PSTR("PIR: primary"),MIBLEsensors[_slot].lux ); +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].motion_hap_service, (float)1,0x0f); +#endif //USE_MI_HOMEKIT } else{ + //unknown payload AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*)_buf,_bufSize); } break; @@ -1387,18 +1420,41 @@ if(decryptRet!=0){ void MI32ParseATCPacket(char * _buf, uint32_t length, uint8_t addr[6], int RSSI){ ATCPacket_t *_packet = (ATCPacket_t*)_buf; - uint32_t _slot = MIBLEgetSensorSlot(_packet->MAC, 0x0a1c, _packet->frameCnt); // This must be a hard-coded fake ID - AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); + bool isATC = (length == 0x0d); + uint32_t _slot; + if (isATC) _slot = MIBLEgetSensorSlot(_packet->MAC, 0x0a1c, _packet->A.frameCnt); // This must be a hard-coded fake ID + else { + MI32_ReverseMAC(_packet->MAC); + _slot = MIBLEgetSensorSlot(_packet->MAC, 0x944a, _packet->P.frameCnt); // ... and again + } if(_slot==0xff) return; + // AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); MIBLEsensors[_slot].RSSI=RSSI; + MIBLEsensors[_slot].lastTime = millis(); + if(isATC){ + MIBLEsensors[_slot].temp = (float)(int16_t(__builtin_bswap16(_packet->A.temp)))/10.0f; + MIBLEsensors[_slot].hum = (float)_packet->A.hum; + MIBLEsensors[_slot].bat = _packet->A.batPer; + } + else{ + MIBLEsensors[_slot].temp = (float)(_packet->P.temp)/100.0f; + MIBLEsensors[_slot].hum = (float)_packet->P.hum/100.0f; + MIBLEsensors[_slot].bat = _packet->P.batPer; + } - MIBLEsensors.at(_slot).temp = (float)(int16_t(__builtin_bswap16(_packet->temp)))/10.0f; - MIBLEsensors.at(_slot).hum = (float)_packet->hum; MIBLEsensors[_slot].eventType.tempHum = 1; - MIBLEsensors.at(_slot).bat = _packet->batPer; MIBLEsensors[_slot].eventType.bat = 1; - +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].temp_hap_service, MIBLEsensors.at(_slot).temp,0x04); + mi_homekit_update_value(MIBLEsensors[_slot].hum_hap_service, MIBLEsensors.at(_slot).hum,0x06); + mi_homekit_update_value(MIBLEsensors[_slot].bat_hap_service, (float)MIBLEsensors.at(_slot).bat,0x0a); +#endif //USE_MI_HOMEKIT +#ifdef USE_MI_EXT_GUI + bitSet(MI32.widgetSlot,_slot); + MI32addHistory(MIBLEsensors[_slot].temp_history, (float)MIBLEsensors[_slot].temp, 0); + MI32addHistory(MIBLEsensors[_slot].hum_history, (float)MIBLEsensors[_slot].hum, 1); +#endif //USE_MI_EXT_GUI MIBLEsensors[_slot].shallSendMQTT = 1; if(MI32.option.directBridgeMode) MI32.mode.shallTriggerTele = 1; @@ -1408,9 +1464,10 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI uint8_t _addr[6]; memcpy(_addr,addr,6); uint32_t _slot = MIBLEgetSensorSlot(_addr, 0x0576, 0); // This must be hard-coded, no object-id in Cleargrass-packet, we have no packet counter too - AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); if(_slot==0xff) return; + // AddLog(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); MIBLEsensors[_slot].RSSI=RSSI; + MIBLEsensors[_slot].lastTime = millis(); cg_packet_t _packet; memcpy((char*)&_packet,_buf,sizeof(_packet)); switch (_packet.mode){ @@ -1418,21 +1475,33 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI float _tempFloat; _tempFloat=(float)(_packet.temp)/10.0f; if(_tempFloat<60){ - MIBLEsensors.at(_slot).temp = _tempFloat; + MIBLEsensors[_slot].temp = _tempFloat; MIBLEsensors[_slot].eventType.temp = 1; DEBUG_SENSOR_LOG(PSTR("CGD1: temp updated")); +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].temp_hap_service, _tempFloat,0x04); +#endif //USE_MI_HOMEKIT +#ifdef USE_MI_EXT_GUI + MI32addHistory(MIBLEsensors[_slot].temp_history, (float)MIBLEsensors[_slot].temp, 0); +#endif //USE_MI_EXT_GUI } _tempFloat=(float)(_packet.hum)/10.0f; if(_tempFloat<100){ - MIBLEsensors.at(_slot).hum = _tempFloat; + MIBLEsensors[_slot].hum = _tempFloat; MIBLEsensors[_slot].eventType.hum = 1; DEBUG_SENSOR_LOG(PSTR("CGD1: hum updated")); +#ifdef USE_MI_HOMEKIT + mi_homekit_update_value(MIBLEsensors[_slot].hum_hap_service, _tempFloat,0x06); +#endif //USE_MI_HOMEKIT +#ifdef USE_MI_EXT_GUI + MI32addHistory(MIBLEsensors[_slot].hum_history, (float)MIBLEsensors[_slot].hum, 1); +#endif //USE_MI_EXT_GUI } 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].bat = _packet.bat; MIBLEsensors[_slot].eventType.bat = 1; DEBUG_SENSOR_LOG(PSTR("Mode a: bat updated")); } @@ -1443,6 +1512,9 @@ void MI32parseCGD1Packet(char * _buf, uint32_t length, uint8_t addr[6], int RSSI if(MIBLEsensors[_slot].eventType.raw == 0) return; MIBLEsensors[_slot].shallSendMQTT = 1; if(MI32.option.directBridgeMode) MI32.mode.shallTriggerTele = 1; +#ifdef USE_MI_EXT_GUI + bitSet(MI32.widgetSlot,_slot); +#endif //USE_MI_EXT_GUI } void MI32ParseResponse(char *buf, uint16_t bufsize, uint8_t addr[6], int RSSI) { @@ -1460,221 +1532,6 @@ void MI32ParseResponse(char *buf, uint16_t bufsize, uint8_t addr[6], int RSSI) { } } -/** - * @brief Parse a BLE advertisement packet - * - * @param payload - * @param payloadLength - * @param CID - * @param SVC - * @param UUID - */ -void MI32ParseGenericBeacon(uint8_t* payload, size_t payloadLength, uint16_t* CID, uint16_t*SVC, uint16_t* UUID){ - AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("M32: Beacon:____________")); - for (uint32_t i = 0; i19) { - AddLog(LOG_LEVEL_INFO,PSTR("M32: Scan buffer full")); - MI32.state.beaconScanCounter = 1; - return; - } - for(auto _scanResult : MIBLEscanResult){ - if(memcmp(addr,_scanResult.MAC,6)==0){ - // AddLog(LOG_LEVEL_INFO,PSTR("M32: known device")); - return; - } - } - scan_entry_t _new; - _new.RSSI = RSSI; - _new.CID = 0; - _new.SVC = 0; - _new.UUID = 0; - memcpy(_new.MAC,addr,sizeof(_new.MAC)); - MI32ParseGenericBeacon(payload,payloadLength,&_new.CID,&_new.SVC,&_new.UUID); - MIBLEscanResult.push_back(_new); -} - - -/** - * @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 MI32addBeacon(uint8_t index, char* data){ - auto &_new = MIBLEbeacons[index-1]; //TODO: check - MI32HexStringToBytes(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("M32: Beacon%u deactivated"), index); - } - else{ - _new.active = true; - MI32.mode.activeBeacon = 1; - AddLog(LOG_LEVEL_INFO,PSTR("M32: Beacon added with MAC: %s"), _MAC); - } -} - -/** - * @brief Present BLE scan in the console, after that deleting the scan data - * - */ -void MI32showScanResults(){ - 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(); - MI32.mode.shallShowScanResult = 0; -} - -void MI32showBlockList(){ - 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("]")); - MI32.mode.shallShowBlockList = 0; -} - -bool MI32isInBlockList(uint8_t* MAC){ - bool isBlocked = false; - for(auto &_blockedMAC : MIBLEBlockList){ - if(memcmp(_blockedMAC.buf,MAC,6) == 0) isBlocked = true; - } - return isBlocked; -} - -void MI32removeMIBLEsensor(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 ) ); -} -/***********************************************************************\ - * Read data from connections -\***********************************************************************/ - -void MI32readHT_LY(char *_buf){ - DEBUG_SENSOR_LOG(PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_MI32,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); - if(_buf[0] != 0 && _buf[1] != 0){ - memcpy(&LYWSD0x_HT,(void *)_buf,sizeof(LYWSD0x_HT)); - AddLog(LOG_LEVEL_DEBUG, PSTR("%s: T * 100: %u, H: %u, V: %u"),D_CMND_MI32,LYWSD0x_HT.temp,LYWSD0x_HT.hum, LYWSD0x_HT.volt); - uint32_t _slot = MI32.state.sensor; - - DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); - static float _tempFloat; - _tempFloat=(float)(LYWSD0x_HT.temp)/100.0f; - if(_tempFloat<60){ - MIBLEsensors[_slot].temp=_tempFloat; - // MIBLEsensors[_slot].showedUp=255; // this sensor is real - } - _tempFloat=(float)LYWSD0x_HT.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)LYWSD0x_HT.volt-2100.0f)/12.0f; - MI32.mode.willReadBatt = 0; - MIBLEsensors[_slot].eventType.bat = 1; - } - MIBLEsensors[_slot].shallSendMQTT = 1; - MI32.mode.shallTriggerTele = 1; - } -} - -bool MI32readBat(char *_buf){ - DEBUG_SENSOR_LOG(PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_MI32,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); - if(_buf[0] != 0){ - AddLog(LOG_LEVEL_DEBUG,PSTR("%s: Battery: %u"),D_CMND_MI32,_buf[0]); - uint32_t _slot = MI32.state.sensor; - DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot); - if(_buf[0]<101){ - MIBLEsensors[_slot].bat=_buf[0]; - if(MIBLEsensors[_slot].type==FLORA){ - memcpy(MIBLEsensors[_slot].firmware, _buf+2, 5); - MIBLEsensors[_slot].firmware[5] = '\0'; - AddLog(LOG_LEVEL_DEBUG,PSTR("%s: Firmware: %s"),D_CMND_MI32,MIBLEsensors[_slot].firmware); - } - MIBLEsensors[_slot].eventType.bat = 1; - MIBLEsensors[_slot].shallSendMQTT = 1; - MI32.mode.shallTriggerTele = 1; - return true; - } - } - return false; -} - /** * @brief Launch functions from Core 1 to make race conditions less likely * @@ -1685,6 +1542,37 @@ void MI32Every50mSecond(){ MI32.mode.shallTriggerTele = 0; MI32triggerTele(); } + if(MI32.mode.triggerBerryAdvCB == 1){ + if(MI32.beAdvCB != nullptr){ + void (*func_ptr)(void) = (void (*)(void))MI32.beAdvCB; + func_ptr(); + } + MI32.mode.triggerBerryAdvCB = 0; + } + if(MI32.mode.triggerBerryConnCB == 1){ + if(MI32.beConnCB != nullptr){ + void (*func_ptr)(int) = (void (*)(int))MI32.beConnCB; + char _message[32]; + GetTextIndexed(_message, sizeof(_message), MI32.conCtx->error, kMI32_ConnErrorMsg); + AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s"),_message); + func_ptr(MI32.conCtx->error); + } + MI32.mode.triggerBerryConnCB = 0; + } + if(MI32.infoMsg > 0){ + char _message[32]; + GetTextIndexed(_message, sizeof(_message), MI32.infoMsg-1, kMI32_BLEInfoMsg); + AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s"),_message); + MI32.infoMsg = 0; + } +#ifdef USE_MI_HOMEKIT + if(MI32.HKinfoMsg > 0){ + char _message[32]; + GetTextIndexed(_message, sizeof(_message), MI32.HKinfoMsg-1, kMI32_HKInfoMsg); + AddLog(LOG_LEVEL_DEBUG,PSTR("M32: %s"),_message); + MI32.HKinfoMsg = 0; + } +#endif //USE_MI_HOMEKIT } /** @@ -1693,256 +1581,52 @@ void MI32Every50mSecond(){ */ void MI32EverySecond(bool restart){ - static uint32_t _counter = MI32.period - 15; - static uint32_t _nextSensorSlot = 0; + +#ifdef USE_MI_HOMEKIT + if(TasmotaGlobal.devices_present>0){ + for(uint32_t i=0;iMI32.period) { - _counter = 0; - MI32.mode.canScan = 0; - MI32.mode.canConnect = 1; - } - - if(MI32.mode.connected == 1 || MI32.mode.willConnect == 1) return; - - if(MIBLEsensors.size()==0) { - if (MI32.mode.runningScan == 0 && MI32.mode.canScan == 1) MI32StartTask(MI32_TASK_SCAN); - return; - } - - if(_counter==0) { - - MI32.state.sensor = _nextSensorSlot; - MI32.mode.canScan = 0; - // if (MI32.mode.runningScan|| MI32.mode.connected || MI32.mode.willConnect) return; - if (MI32.mode.connected || MI32.mode.willConnect) return; - _nextSensorSlot++; - MI32.mode.canConnect = 1; - if(MI32.mode.connected == 0) { - if (MI32.mode.shallReadBatt) { - //TODO: decide automatically, which sensor can not work without connections - AddLog(LOG_LEVEL_DEBUG,PSTR("%s: active sensor now: %u of %u"),D_CMND_MI32, MI32.state.sensor, MIBLEsensors.size()-1); - AddLog(LOG_LEVEL_DEBUG, PSTR("will connect to %s"),kMI32DeviceType[MIBLEsensors[MI32.state.sensor].type-1] ); - - MI32StartTask(MI32_TASK_BATT); +#ifdef USE_MI_HOMEKIT + if(MIBLEsensors[i].NMT > 20){ //TODO: Make a choosable timeout later + mi_homekit_update_value(MIBLEsensors[i].motion_hap_service,0.0f,0x0f); } -#ifndef USE_MI_DECRYPTION // turn off connections, because we only listen to advertisements - else{ - MI32StartTask(MI32_TASK_CONN); - } -#endif //USE_MI_DECRYPTION - } - if (_nextSensorSlot>(MIBLEsensors.size()-1)) { - _nextSensorSlot= 0; - _counter++; - if (MI32.mode.shallReadBatt){ - MI32.mode.shallReadBatt = 0; - } - MI32.mode.canConnect = 0; - MI32.mode.canScan = 1; +#endif //USE_MI_HOMEKIT } } - else _counter++; - if (MI32.state.sensor>MIBLEsensors.size()-1) { - _nextSensorSlot = 0; - MI32.mode.canScan = 1; - } - MI32StartTask(MI32_TASK_SCAN); } /*********************************************************************************************\ * Commands \*********************************************************************************************/ -void CmndMi32Period(void) { - if (XdrvMailbox.data_len > 0) { - if (1 == XdrvMailbox.payload) { - MI32EverySecond(true); - } else { - MI32.period = XdrvMailbox.payload; - } - } - ResponseCmndNumber(MI32.period); -} - -void CmndMi32Time(void) { - if (XdrvMailbox.data_len > 0) { - if (MIBLEsensors.size() > XdrvMailbox.payload) { - if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) { - AddLog(LOG_LEVEL_DEBUG, PSTR("M32: Will set Time")); - MI32.state.sensor = XdrvMailbox.payload; - MI32.mode.canScan = 0; - MI32.mode.canConnect = 0; - MI32.mode.shallSetTime = 1; - MI32.mode.willSetTime = 0; - ResponseCmndNumber(XdrvMailbox.payload); - } - } - } -} - -void CmndMi32Page(void) { - if (XdrvMailbox.payload > 0) { - MI32.perPage = XdrvMailbox.payload; - } - ResponseCmndNumber(MI32.perPage); -} - -void CmndMi32Battery(void) { - MI32EverySecond(true); - MI32.mode.shallReadBatt = 1; - MI32.mode.canConnect = 1; - ResponseCmndDone(); -} - -void CmndMi32Unit(void) { - if (XdrvMailbox.data_len > 0) { - if (MIBLEsensors.size() > XdrvMailbox.payload) { - if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) { - AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Will set Unit")); - MI32.state.sensor = XdrvMailbox.payload; - MI32.mode.canScan = 0; - MI32.mode.canConnect = 0; - MI32.mode.shallSetUnit = 1; - MI32.mode.willSetUnit = 0; - ResponseCmndNumber(XdrvMailbox.payload); - } - } - } -} - -#ifdef USE_MI_DECRYPTION void CmndMi32Key(void) { - if (44 == XdrvMailbox.data_len) { // a KEY-MAC-string - MI32AddKey(XdrvMailbox.data); + if (44 == XdrvMailbox.data_len || 36 == XdrvMailbox.data_len) { // a KEY-MAC-string + mi_bindKey_t keyMAC; + MI32HexStringToBytes(XdrvMailbox.data,keyMAC.buf); + if(36 == XdrvMailbox.data_len){ + memmove(keyMAC.buf + 10, keyMAC.buf + 6, 12); + const uint8_t _fillbytes[4] = {0x8d,0x3d,0x3c,0x97}; // only valid for YLKG08 and YLKG07 ?? + memcpy(keyMAC.buf + 6,_fillbytes,4); + AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*) keyMAC.buf, 16); + } + MI32AddKey(keyMAC); ResponseCmndDone(); } } -#endif // USE_MI_DECRYPTION -void CmndMi32Beacon(void) { - if (XdrvMailbox.data_len == 0) { - switch (XdrvMailbox.index) { - case 0: - MI32.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 ((12 == XdrvMailbox.data_len) || (17 == XdrvMailbox.data_len)) { // MAC-string without or with colons - switch (XdrvMailbox.index) { - case 1: case 2: case 3: case 4: - MI32addBeacon(XdrvMailbox.index, XdrvMailbox.data); - break; - } - } - ResponseCmndIdxChar(XdrvMailbox.data); - } -} - -void CmndMi32Block(void){ - if (XdrvMailbox.data_len == 0) { - switch (XdrvMailbox.index) { - case 0: - MIBLEBlockList.clear(); - // AddLog(LOG_LEVEL_INFO,PSTR("M32: Size of ilist: %u"), MIBLEBlockList.size()); - ResponseCmndIdxChar(PSTR("Block list cleared")); - break; - case 1: - ResponseCmndIdxChar(PSTR("Show block list")); - break; - } - } - else { - MAC_t _MACasBytes; - MI32HexStringToBytes(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); - MI32removeMIBLEsensor(_MACasBytes.buf); - } - // AddLog(LOG_LEVEL_INFO,PSTR("M32: Size of ilist: %u"), MIBLEBlockList.size()); - break; - } - } - MI32.mode.shallShowBlockList = 1; - MI32triggerTele(); +void CmndMi32Cfg(void) { + MI32saveConfig(); + ResponseCmndDone(); } void CmndMi32Option(void){ @@ -1957,6 +1641,9 @@ void CmndMi32Option(void){ case 2: MI32.option.directBridgeMode = onOff; break; + case 3: + MI32.mode.didGetConfig = onOff; + break; } ResponseCmndDone(); } @@ -1964,16 +1651,205 @@ void CmndMi32Option(void){ /*********************************************************************************************\ * Presentation \*********************************************************************************************/ +#ifdef USE_MI_EXT_GUI +bool MI32HandleWebGUIResponse(void){ + char tmp[16]; + WebGetArg(PSTR("wi"), tmp, sizeof(tmp)); + if (strlen(tmp)) { + WSContentBegin(200, CT_PLAIN); + if(MI32.widgetSlot==0) {WSContentEnd();return true;} + for(uint32_t i=0;i<32;i++){ + if(bitRead(MI32.widgetSlot,i)){ + MI32sendWidget(i); + WSContentEnd(); + bitClear(MI32.widgetSlot,i); + return true; + } + } + WSContentEnd(); + return true; + } + return false; +} -const char HTTP_MI32[] PROGMEM = "{s}MI ESP32 v0917a{m}%u%s / %u{e}"; -const char HTTP_MI32_MAC[] PROGMEM = "{s}%s %s{m}%s{e}"; -const char HTTP_RSSI[] PROGMEM = "{s}%s " D_RSSI "{m}%d dBm{e}"; +#ifdef USE_MI_ESP32_ENERGY +//https://gist.github.com/LingDong-/7e4c4cae5cbbc44400a05fba65f06f23 +// used for logarithmic mapping of 0 - 3600 watts to 0-20 pixel - TaylorLog did not work as expected +float MI32ln(float x) { + unsigned int bx = * (unsigned int *) (&x); + unsigned int ex = bx >> 23; + signed int t = (signed int)ex-(signed int)127; + unsigned int s = (t < 0) ? (-t) : t; + bx = 1065353216 | (bx & 8388607); + x = * (float *) (&bx); + return -1.49278+(2.11263+(-0.729104+0.10969*x)*x)*x+0.6931471806*t; +} +#endif //USE_MI_ESP32_ENERGY + +void MI32createPolyline(char *polyline, uint8_t *history){ + uint32_t _pos = 0; + uint32_t _inc = 0; + for (uint32_t i = 0; i<24;i++){ + uint32_t y = 21-MI32fetchHistory(history,i); + if (y>20){ + y = 150; //create a big gap in the graph to represent invalidated data + } + _inc = snprintf_P(polyline+_pos,10,PSTR("%u,%u "),i*6,y); + _pos+=_inc; + } + // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: polyline: %s"),polyline); +} + +#ifdef USE_MI_ESP32_ENERGY +void MI32sendEnergyWidget(){ + if (Energy.current_available && Energy.voltage_available) { + WSContentSend_P(HTTP_MI32_POWER_WIDGET,MIBLEsensors.size()+1, Energy.voltage,Energy.current[1]); + char _polyline[176]; + MI32createPolyline(_polyline,MI32.energy_history); + WSContentSend_P(PSTR("

" D_POWERUSAGE ": %.1f " D_UNIT_WATT ""),Energy.active_power); + WSContentSend_P(HTTP_MI32_GRAPH,_polyline,185,124,124,_polyline,1); + WSContentSend_P(PSTR("

")); + } +} +#endif //USE_MI_ESP32_ENERGY + +void MI32sendWidget(uint32_t slot){ + auto _sensor = MIBLEsensors[slot]; + char _MAC[13]; + ToHex_P(_sensor.MAC,6,_MAC,13); + uint32_t _opacity = 1; + if(_sensor.RSSI == 0){ + _opacity=0; + } + char _key[33] ={0}; + if(_sensor.feature.needsKey){ + snprintf_P(_key,32,PSTR("!! needs key!!")); + _opacity=0; + } + if(_sensor.key!=nullptr){ + ToHex_P(_sensor.key,16,_key,33); + } + char _bat[24]; + snprintf_P(_bat,24,PSTR("🔋%u%%"), _sensor.bat); + if(!_sensor.feature.bat) _bat[0] = 0; + if (_sensor.bat == 0) _bat[9] = 0; + WSContentSend_P(HTTP_MI32_WIDGET,slot+1,_opacity,_MAC,_sensor.RSSI,_bat,_key,kMI32DeviceType[_sensor.type-1]); + if(_sensor.feature.tempHum){ + if(!isnan(_sensor.temp)){ + char _polyline[176]; + MI32createPolyline(_polyline,_sensor.temp_history); + WSContentSend_P(PSTR("

" D_JSON_TEMPERATURE ": %.1f °C"),_sensor.temp); + WSContentSend_P(HTTP_MI32_GRAPH,_polyline,185,124,124,_polyline,1); + WSContentSend_P(PSTR("

")); + } + if(!isnan(_sensor.hum)){ + char _polyline[176]; + MI32createPolyline(_polyline,_sensor.hum_history); + WSContentSend_P(PSTR("

" D_JSON_HUMIDITY ": %.1f %%"),_sensor.hum); + WSContentSend_P(HTTP_MI32_GRAPH,_polyline,151,190,216,_polyline,2); + WSContentSend_P(PSTR("

")); + } + if(!isnan(_sensor.temp) && !isnan(_sensor.hum)){ + WSContentSend_P(PSTR("" D_JSON_DEWPOINT ": %.1f °C"),CalcTempHumToDew(_sensor.temp,_sensor.hum)); + } + + } + else if(_sensor.feature.temp){ + if(!isnan(_sensor.temp)){ + char _polyline[176]; + MI32createPolyline(_polyline,_sensor.temp_history); + WSContentSend_P(PSTR("

" D_JSON_TEMPERATURE ": %.1f °C"),_sensor.temp); + WSContentSend_P(HTTP_MI32_GRAPH,_polyline,185,124,124,_polyline,1); + WSContentSend_P(PSTR("

")); + } + } + if(_sensor.feature.lux){ + if(_sensor.lux!=0x00ffffff){ + char _polyline[176]; + MI32createPolyline(_polyline,_sensor.lux_history); + WSContentSend_P(PSTR("

" D_JSON_ILLUMINANCE ": %d Lux"),_sensor.lux); + WSContentSend_P(HTTP_MI32_GRAPH,_polyline,242,240,176,_polyline,3); + WSContentSend_P(PSTR("

")); + } + } + if(_sensor.feature.Btn){ + if(_sensor.Btn<12) WSContentSend_P(PSTR("

Last Button: %u

"),_sensor.Btn); + } + if(_sensor.feature.motion){ + WSContentSend_P(PSTR("

Events: %u

"),_sensor.events); + WSContentSend_P(PSTR("

No motion for > %u seconds

"),_sensor.NMT); + } + if(_sensor.feature.door){ + if(_sensor.door!=255){ + if(_sensor.door==1){ + WSContentSend_P(PSTR("

Contact open

")); + } + else{ + WSContentSend_P(PSTR("

Contact closed

")); + } + WSContentSend_P(PSTR("

Events: %u

"),_sensor.events); + } + } + if(_sensor.feature.leak){ + if(_sensor.leak==1){ + WSContentSend_P(PSTR("

Leak !!!

")); + } + else{ + WSContentSend_P(PSTR("

no leak

")); + } + } + WSContentSend_P(PSTR("
")); +} + +void MI32InitGUI(void){ + MI32suspendScanTask(); + MI32.widgetSlot=0; + WSContentStart_P("m32"); + WSContentSend_P(HTTP_MI32_SCRIPT_1); + // WSContentSend_P(HTTP_MI32_SCRIPT_1); + WSContentSendStyle(); + WSContentSend_P(HTTP_MI32_STYLE); + WSContentSend_P(HTTP_MI32_STYLE_SVG,1,185,124,124,185,124,124); + WSContentSend_P(HTTP_MI32_STYLE_SVG,2,151,190,216,151,190,216); + WSContentSend_P(HTTP_MI32_STYLE_SVG,3,242,240,176,242,240,176); +#ifdef USE_MI_HOMEKIT + WSContentSend_P((HTTP_MI32_PARENT_START),MIBLEsensors.size(),UpTime(),MI32.hk_setup_code,MI32.HKconnectedControllers,ESP.getFreeHeap()/1024); +#else + const char _setupCode[1] = {0}; + WSContentSend_P((HTTP_MI32_PARENT_START),MIBLEsensors.size(),UpTime(),_setupCode,ESP.getFreeHeap()/1024); +#endif //USE_MI_HOMEKIT + for(uint32_t _slot = 0;_slot")); + WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); + vTaskResume(MI32.ScanTask); +} + +void MI32HandleWebGUI(void){ + if (!HttpCheckPriviledgedAccess()) { return; } + if (MI32HandleWebGUIResponse()) { return; } + MI32InitGUI(); +} +#endif //USE_MI_EXT_GUI + +const char HTTP_MI32[] PROGMEM = "{s}Mi ESP32 {m} %u devices{e}"; + +#ifndef USE_MI_EXT_GUI const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u %%{e}"; const char HTTP_LASTBUTTON[] PROGMEM = "{s}%s Last Button{m}%u {e}"; const char HTTP_EVENTS[] PROGMEM = "{s}%s Events{m}%u {e}"; const char HTTP_NMT[] PROGMEM = "{s}%s No motion{m}> %u seconds{e}"; +const char HTTP_DOOR[] PROGMEM = "{s}%s Door{m}> %u open/closed{e}"; const char HTTP_MI32_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%u us/cm{e}"; +#endif //USE_MI_EXT_GUI +const char HTTP_MI32_MAC[] PROGMEM = "{s}%s %s{m}%s{e}"; const char HTTP_MI32_HL[] PROGMEM = "{s}
{m}
{e}"; +const char HTTP_RSSI[] PROGMEM = "{s}%s " D_RSSI "{m}%d dBm{e}"; void MI32ShowContinuation(bool *commaflg) { if (*commaflg) { @@ -1986,32 +1862,20 @@ void MI32ShowContinuation(bool *commaflg) { void MI32Show(bool json) { if (json) { - if(MI32.mode.shallShowScanResult) { - return MI32showScanResults(); - } - else if(MI32.mode.shallShowBlockList) { - return MI32showBlockList(); - } #ifdef USE_HOME_ASSISTANT bool _noSummarySave = MI32.option.noSummary; bool _minimalSummarySave = MI32.option.minimalSummary; if(hass_mode==2){ - if(MI32.option.holdBackFirstAutodiscovery){ - if(!MI32.mode.firstAutodiscoveryDone){ - MI32.mode.firstAutodiscoveryDone = 1; - return; - } - } MI32.option.noSummary = false; MI32.option.minimalSummary = false; } #endif //USE_HOME_ASSISTANT if(!MI32.mode.triggeredTele){ - MI32.mode.shallClearResults=1; + // MI32.mode.shallClearResults=1; if(MI32.option.noSummary) return; // no message at TELEPERIOD } - + MI32suspendScanTask(); for (uint32_t i = 0; i < MIBLEsensors.size(); i++) { if(MI32.mode.triggeredTele && MIBLEsensors[i].eventType.raw == 0) continue; if(MI32.mode.triggeredTele && MIBLEsensors[i].shallSendMQTT==0) continue; @@ -2128,18 +1992,29 @@ void MI32Show(bool json) } } } // minimal summary - if (MIBLEsensors[i].feature.PIR){ + if (MIBLEsensors[i].feature.motion){ if(MIBLEsensors[i].eventType.motion || !MI32.mode.triggeredTele){ if(MI32.mode.triggeredTele) { MI32ShowContinuation(&commaflg); - ResponseAppend_P(PSTR("\"PIR\":1")); // only real-time + ResponseAppend_P(PSTR("\"motion\":1")); // only real-time } MI32ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Events\":%u"),MIBLEsensors[i].events); } else if(MIBLEsensors[i].eventType.noMotion && MI32.mode.triggeredTele){ MI32ShowContinuation(&commaflg); - ResponseAppend_P(PSTR("\"PIR\":0")); + ResponseAppend_P(PSTR("\"motion\":0")); + } + } + + if (MIBLEsensors[i].feature.door){ + if(MIBLEsensors[i].eventType.door || !MI32.mode.triggeredTele){ + if(MI32.mode.triggeredTele) { + MI32ShowContinuation(&commaflg); + ResponseAppend_P(PSTR("\"DOOR\":%u"),MIBLEsensors[i].door); // only real-time + } + MI32ShowContinuation(&commaflg); + ResponseAppend_P(PSTR("\"Events\":%u"),MIBLEsensors[i].events); } } @@ -2187,40 +2062,31 @@ void MI32Show(bool json) } } MI32.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}"), - _idx,_MAC,_beacon.CID,_beacon.SVC,_beacon.UUID,_beacon.time,_beacon.RSSI); - } #ifdef USE_HOME_ASSISTANT if(hass_mode==2){ MI32.option.noSummary = _noSummarySave; MI32.option.minimalSummary = _minimalSummarySave; } #endif //USE_HOME_ASSISTANT +#ifdef USE_MI_EXT_GUI + Mi32invalidateOldHistory(); +#ifdef USE_MI_ESP32_ENERGY + MI32addHistory(MI32.energy_history,Energy.active_power[0],100); //TODO: which value?? +#endif //USE_MI_ESP32_ENERGY +#endif //USE_MI_EXT_GUI + vTaskResume(MI32.ScanTask); #ifdef USE_WEBSERVER } else { - static uint16_t _page = 0; - static uint16_t _counter = 0; - int32_t i = _page * MI32.perPage; - uint32_t j = i + MI32.perPage; - if (j+1>MIBLEsensors.size()){ - j = MIBLEsensors.size(); - } - char stemp[5] ={0}; - if (MIBLEsensors.size()-(_page*MI32.perPage)>1 && MI32.perPage!=1) { - sprintf_P(stemp,"-%u",j); - } - if (MIBLEsensors.size()==0) i=-1; // only for the GUI + MI32suspendScanTask(); - WSContentSend_PD(HTTP_MI32, i+1,stemp,MIBLEsensors.size()); - for (i; i0) WSContentSend_PD(HTTP_NMT, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].NMT); } + if(MIBLEsensors[i].door != 255 && MIBLEsensors[i].type==MCCGQ02){ + WSContentSend_PD(HTTP_DOOR, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].door); + } if (MIBLEsensors[i].lux!=0x00ffffff) { // this is the error code -> no valid value WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].lux); } @@ -2260,36 +2133,26 @@ void MI32Show(bool json) WSContentSend_PD(HTTP_LASTBUTTON, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].Btn); } } - _counter++; - if(_counter>3) { - _page++; - _counter=0; - } - if (MIBLEsensors.size()%MI32.perPage==0 && _page==MIBLEsensors.size()/MI32.perPage) { _page = 0; } - if (_page>MIBLEsensors.size()/MI32.perPage) { _page = 0; } - - //always at the bottom of the page - uint32_t _idx=0; - if(MI32.mode.activeBeacon){ - WSContentSend_PD(HTTP_MI32_HL); - char _sbeacon[] = "Beacon1"; - for (auto &_beacon : MIBLEbeacons){ - _idx++; - if(!_beacon.active) continue; - WSContentSend_PD(HTTP_MI32_HL); - _sbeacon[6] = _idx + 0x30; - char _MAC[18]; - ToHex_P(_beacon.MAC,6,_MAC,18,':'); - WSContentSend_PD(HTTP_MI32_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); - WSContentSend_PD(PSTR("{s}Beacon%u Time{m}%u seconds{e}"),_idx, _beacon.time); - } - } +#endif //USE_MI_EXT_GUI #endif // USE_WEBSERVER } + vTaskResume(MI32.ScanTask); +} + +int ExtStopBLE(){ + if(Settings->flag5.mi32_enable == 0) return 0; + if (MI32.ScanTask != nullptr){ + MI32Scan->stop(); + vTaskDelete(MI32.ScanTask); + AddLog(LOG_LEVEL_INFO,PSTR("M32: stop BLE")); + } +#ifdef USE_MI_HOMEKIT + if(MI32.mode.didStartHAP) { + AddLog(LOG_LEVEL_INFO,PSTR("M32: stop Homebridge")); + mi_homekit_stop(); + } +#endif //USE_MI_HOMEKIT + return 0; } /*********************************************************************************************\ @@ -2319,6 +2182,9 @@ bool Xsns62(uint8_t function) case FUNC_EVERY_SECOND: MI32EverySecond(false); break; + case FUNC_SAVE_BEFORE_RESTART: + ExtStopBLE(); + break; case FUNC_COMMAND: result = DecodeCommand(kMI32_Commands, MI32_Commands); break; @@ -2329,6 +2195,14 @@ bool Xsns62(uint8_t function) case FUNC_WEB_SENSOR: MI32Show(0); break; +#ifdef USE_MI_EXT_GUI + case FUNC_WEB_ADD_MAIN_BUTTON: + if (MI32.mode.didGetConfig) WSContentSend_P(HTTP_BTN_MENU_MI32); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer_on(PSTR("/m32"), MI32HandleWebGUI); + break; +#endif //USE_MI_EXT_GUI #endif // USE_WEBSERVER } return result; diff --git a/tasmota/xsns_62_esp32_mi_homekit.c b/tasmota/xsns_62_esp32_mi_homekit.c new file mode 100644 index 000000000..7e6c92ff3 --- /dev/null +++ b/tasmota/xsns_62_esp32_mi_homekit.c @@ -0,0 +1,333 @@ +#if(USE_MI_HOMEKIT==1) + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +//Homekit +static int MI32_bridge_identify(hap_acc_t *ha); +static int MI32_accessory_identify(hap_acc_t *ha); +static void MI32_bridge_thread_entry(void *p); + +extern uint32_t MI32numberOfDevices(); +extern const char *MI32getDeviceName(uint32_t slot); +extern uint32_t MI32getDeviceType(uint32_t slot); +extern void MI32saveHAPhandles(uint32_t slot, uint32_t type, void* handle); +extern void MI32passHapEvent(uint32_t event); +extern void MI32didStartHAP(); +extern const char * MI32getSetupCode(); +extern uint32_t MI32numOfRelays(); +extern void MI32setRelayFromHK(uint32_t relay, bool onOff); + +// static const char *TAG = "Mi Bridge"; +static bool MIBridgeWasNeverConnected = true; + +#define CONFIG_EXAMPLE_SETUP_ID "MI32" + +#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 MCCGQ02 13 +#define SJWS01L 14 +#define PVVX 15 +#define YLKG08 16 + +/*********************************************************************************************\ + * Homekit +\*********************************************************************************************/ +/* Mandatory identify routine for the bridge. + * In a real accessory, something like LED blink should be implemented + * got visual identification + */ +static int MI32_bridge_identify(hap_acc_t *ha) +{ + return HAP_SUCCESS; +} + +void mi_hap_event_handler(hap_event_t event, void *data) +{ + MI32passHapEvent((uint32_t)event); + if(event == HAP_EVENT_CTRL_CONNECTED) MIBridgeWasNeverConnected = false; +} + +static int MI32_bridge_read_callback(hap_read_data_t read_data[], int count, + void *serv_priv, void *read_priv) +{ + return HAP_SUCCESS; +} + +static int MI32_outlets_write_callback(hap_write_data_t write_data[], int count, + void *serv_priv, void *write_priv) +{ + uint8_t _relay = ((uint8_t*)serv_priv)[0]; + int i, ret = HAP_SUCCESS; + hap_write_data_t *write; + for (i = 0; i < count; i++) { + write = &write_data[i]; + if (!strcmp(hap_char_get_type_uuid(write->hc), HAP_CHAR_UUID_ON)) { + MI32setRelayFromHK(_relay-48, write->val.b); + hap_char_update_val(write->hc, &(write->val)); + *(write->status) = HAP_STATUS_SUCCESS; + } else { + *(write->status) = HAP_STATUS_RES_ABSENT; + } + } + return ret; +} + +/* Mandatory identify routine for the bridged accessory + * In a real bridge, the actual accessory must be sent some request to + * identify itself visually + */ +static int MI32_accessory_identify(hap_acc_t *ha) +{ + return HAP_SUCCESS; +} + +/*The main thread for handling the Smart Outlet Accessory */ +static void MI32_bridge_thread_entry(void *p) +{ + // esp_log_level_set("*", ESP_LOG_NONE); + hap_acc_t *accessory; + hap_serv_t *service; + + /* Initialize the HAP core */ + hap_init(HAP_TRANSPORT_WIFI); + + /* Initialise the mandatory parameters for Accessory which will be added as + * the mandatory services internally + */ + hap_acc_cfg_t cfg = { + .name = "Mi-Home-Bridge", + .manufacturer = "Tasmota", + .model = "ESP32", + .serial_num = "9600", + .fw_rev = "0.9.5", + .hw_rev = NULL, + .pv = "1.1.0", + .cid = HAP_CID_BRIDGE, + .identify_routine = MI32_bridge_identify + }; + /* Create accessory object */ + accessory = hap_acc_create(&cfg); + + /* Add a dummy Product Data */ + uint8_t product_data[] = {'T','M','H'}; + hap_acc_add_product_data(accessory, product_data, sizeof(product_data)); + + /* Add the Accessory to the HomeKit Database */ + hap_add_accessory(accessory); + +#define NUM_BRIDGED_ACCESSORIES 1 + /* Create and add the Accessory to the Bridge object*/ + uint32_t _numDevices = MI32numberOfDevices(); + for (uint32_t i = 0; i < _numDevices; i++) { + char *accessory_name = (char*)MI32getDeviceName(i); + char _serialNum[4] = {0}; + snprintf(_serialNum,sizeof(_serialNum),"%u", i); + + hap_acc_cfg_t bridge_cfg = { + .name = accessory_name, + .manufacturer = "Xiaomi", + .model = accessory_name, + .serial_num = _serialNum, + .fw_rev = "0.9.1", + .hw_rev = NULL, + .pv = "1.1.0", + .cid = HAP_CID_SENSOR, + .identify_routine = MI32_accessory_identify, + }; + + /* Create accessory object */ + accessory = hap_acc_create(&bridge_cfg); + + switch (MI32getDeviceType(i)){ + case LYWSD02: case LYWSD03MMC: case CGG1: case CGD1: case MHOC303: case MHOC401: case ATC: case PVVX: + { + service = hap_serv_humidity_sensor_create(50.0f); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x06,(void *)hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_CURRENT_RELATIVE_HUMIDITY)); + + service = hap_serv_temperature_sensor_create(22.5f); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x04,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_CURRENT_TEMPERATURE)); + + service = hap_serv_battery_service_create(99,0,0); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x0a,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_BATTERY_LEVEL)); + } + break; + case FLORA: case MJYD2S: + { + service = hap_serv_light_sensor_create(100.0f); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x07,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_CURRENT_AMBIENT_LIGHT_LEVEL)); + service = hap_serv_battery_service_create(50,0,0); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x0a,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_BATTERY_LEVEL)); + if(MI32getDeviceType(i) == MJYD2S){ + service = hap_serv_motion_sensor_create(false); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x0f,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_MOTION_DETECTED)); + } + break; + } + case NLIGHT: + { + service = hap_serv_motion_sensor_create(false); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x0f,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_MOTION_DETECTED)); + + service = hap_serv_battery_service_create(50,0,0); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x0a,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_BATTERY_LEVEL)); + break; + //motion 0x0f + } + case MCCGQ02: + { + service = hap_serv_contact_sensor_create(0); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x19,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_CONTACT_SENSOR_STATE)); + service = hap_serv_battery_service_create(50,0,0); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x0a,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_BATTERY_LEVEL)); + break; + } + case YEERC: + { + bridge_cfg.cid = HAP_CID_PROGRAMMABLE_SWITCH; + hap_serv_t * _label = hap_serv_service_label_create(1); + hap_acc_add_serv(accessory, _label); + for(uint8_t _but=0;_but<6;_but++){ + hap_serv_t * _newSwitch = hap_serv_stateless_programmable_switch_create(0); + const uint8_t _validVals[] = {0,2}; + hap_char_add_valid_vals(hap_serv_get_char_by_uuid(_newSwitch, HAP_CHAR_UUID_PROGRAMMABLE_SWITCH_EVENT), _validVals, 2); + hap_char_t *_index = hap_char_service_label_index_create(_but+1); + hap_serv_add_char(_newSwitch,_index); + hap_acc_add_serv(accessory, _newSwitch); + MI32saveHAPhandles(i,_but+1000,hap_serv_get_char_by_uuid(_newSwitch, HAP_CHAR_UUID_PROGRAMMABLE_SWITCH_EVENT)); + } + } + break; + case SJWS01L: + service = hap_serv_leak_sensor_create(0); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x14,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_LEAK_DETECTED)); + hap_serv_t * _newSwitch = hap_serv_stateless_programmable_switch_create(0); + const uint8_t _validVals[] = {0,2}; + hap_char_add_valid_vals(hap_serv_get_char_by_uuid(_newSwitch, HAP_CHAR_UUID_PROGRAMMABLE_SWITCH_EVENT), _validVals, 2); + hap_acc_add_serv(accessory, _newSwitch); + MI32saveHAPhandles(i,1000,hap_serv_get_char_by_uuid(_newSwitch, HAP_CHAR_UUID_PROGRAMMABLE_SWITCH_EVENT)); + service = hap_serv_battery_service_create(50,0,0); + hap_serv_set_bulk_read_cb(service, MI32_bridge_read_callback); + hap_acc_add_serv(accessory, service); + MI32saveHAPhandles(i,0x0a,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_BATTERY_LEVEL)); + break; + default: + break; + } + /* Add the Accessory to the HomeKit Database */ + hap_add_bridged_accessory(accessory, hap_get_unique_aid(accessory_name)); + } + // add internal Tasmota devices + for(uint32_t i = 0;i 0.0f); + break; + default: + new_val.f = value; + } + int ret = hap_char_update_val((hap_char_t *)handle, &new_val); + // if(ret!= HAP_SUCCESS){ + // ESP_LOGE(TAG,"error:",ret); + // } +} + +void mi_homekit_stop(){ + hap_stop(); +} + +#endif //USE_MI_ESP32 +