diff --git a/tasmota/xsns_62_esp32_mi.h b/tasmota/xsns_62_esp32_mi.h index 3dd9bf89b..4b8f30f99 100644 --- a/tasmota/xsns_62_esp32_mi.h +++ b/tasmota/xsns_62_esp32_mi.h @@ -236,6 +236,7 @@ struct mi_sensor_t{ uint32_t NMT:1; uint32_t motion:1; uint32_t Btn:1; + uint32_t knob:1; uint32_t door:1; uint32_t leak:1; }; @@ -254,6 +255,8 @@ struct mi_sensor_t{ uint32_t motion:1; uint32_t noMotion:1; uint32_t Btn:1; + uint32_t knob:1; + uint32_t longpress:1; //needs no extra feature bit, because knob is sufficient uint32_t door:1; uint32_t leak:1; }; @@ -285,11 +288,15 @@ struct mi_sensor_t{ }; // MJ_HT_V1, LYWSD0x struct { uint16_t events; //"alarms" since boot - uint32_t NMT; // no motion time in seconds for the MJYD2S + uint32_t NMT; // no motion time in seconds for the MJYD2S and NLIGHT }; struct { - uint16_t Btn; - uint8_t leak; + uint8_t Btn; // number starting with 0 + uint8_t BtnType; // 0 -single, 1 - double, 2 - hold + uint8_t leak; // the leak sensor is the only non-RC device so far with a button fuctionality, so we handle it here + int8_t dimmer; + uint8_t pressed; // dimmer knob pressed while rotating + uint8_t longpress; // dimmer knob pressed without rotating }; uint8_t door; }; @@ -315,9 +322,9 @@ struct mi_sensor_t{ #define D_CMND_MI32 "MI32" -const char kMI32_Commands[] PROGMEM = D_CMND_MI32 "|Key|"/*Time|Battery|Unit|Beacon|*/"Cfg|Option"; +const char kMI32_Commands[] PROGMEM = D_CMND_MI32 "|Key|Cfg|Option"; -void (*const MI32_Commands[])(void) PROGMEM = {&CmndMi32Key, /*&CmndMi32Time, &CmndMi32Battery, &CmndMi32Unit, &CmndMi32Beacon,*/ &CmndMi32Cfg, &CmndMi32Option }; +void (*const MI32_Commands[])(void) PROGMEM = {&CmndMi32Key, &CmndMi32Cfg, &CmndMi32Option }; #define FLORA 1 #define MJ_HT_V1 2 @@ -327,7 +334,7 @@ void (*const MI32_Commands[])(void) PROGMEM = {&CmndMi32Key, /*&CmndMi32Time, &C #define CGD1 6 #define NLIGHT 7 #define MJYD2S 8 -#define YEERC 9 +#define YLYK01 9 #define MHOC401 10 #define MHOC303 11 #define ATC 12 @@ -346,7 +353,7 @@ const uint16_t kMI32DeviceID[MI32_TYPES]={ 0x0098, // Flora 0x0576, // CGD1 0x03dd, // NLIGHT 0x07f6, // MJYD2S - 0x0153, // yee-rc + 0x0153, // YLYK01, old name yee-rc 0x0387, // MHO-C401 0x06d3, // MHO-C303 0x0a1c, // ATC -> this is a fake ID @@ -364,7 +371,7 @@ 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 kMI32DeviceType9[] PROGMEM = "YLYK01"; //old name yeerc const char kMI32DeviceType10[] PROGMEM ="MHOC401"; const char kMI32DeviceType11[] PROGMEM ="MHOC303"; const char kMI32DeviceType12[] PROGMEM ="ATC"; @@ -382,6 +389,8 @@ const char kMI32_ConnErrorMsg[] PROGMEM = "no Error|could not connect|got no ser 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"; + +const char kMI32_ButtonMsg[] PROGMEM = "Single|Double|Hold"; //mapping: in Tasmota: 1,2,3 ; for HomeKit and Xiaomi 0,1,2 /*********************************************************************************************\ * enumerations \*********************************************************************************************/ diff --git a/tasmota/xsns_62_esp32_mi.ino b/tasmota/xsns_62_esp32_mi.ino index 30fea697e..619428b27 100644 --- a/tasmota/xsns_62_esp32_mi.ino +++ b/tasmota/xsns_62_esp32_mi.ino @@ -22,13 +22,15 @@ -------------------------------------------------------------------------------------------- Version yyyymmdd Action Description -------------------------------------------------------------------------------------------- + 0.9.5.1 20220209 changed - rename YEERC to YLYK01, add dimmer YLKG08 (incl. YLKG07), change button report scheme + ------- 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.0 20200712 changed - add lights and yeerc, add pure passive mode with decryption, + 0.9.1.0 20200712 changed - add lights and YLYK01, add pure passive mode with decryption, lots of refactoring ------- 0.9.0.1 20200706 changed - adapt to new NimBLE-API, tweak scan process @@ -44,7 +46,7 @@ #ifdef USE_MI_ESP32 #ifdef USE_ENERGY_SENSOR -// #define USE_MI_ESP32_ENERGY //perpare for some GUI extensions +// #define USE_MI_ESP32_ENERGY //prepare for some GUI extensions #endif #define XSNS_62 62 @@ -256,6 +258,7 @@ void MI32AddKey(mi_bindKey_t keyMAC){ _sensor.key = _key; unknownMAC=false; _sensor.status.hasWrongKey = 0; + AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*) _sensor.key, 16); } } if(unknownMAC){ @@ -278,7 +281,7 @@ int MI32_decryptPacket(char * _buf, uint16_t _bufSize, uint8_t * _payload, uint3 mi_beacon_t *_beacon = (mi_beacon_t *)_buf; uint8_t nonce[13]; //v3:13, v5:12 - uint32_t nonceLen =12; // most devices are v5 + uint32_t nonceLen = 12; // most devices are v5 uint8_t tag[4] = {0}; const unsigned char authData[1] = {0x11}; size_t dataLen = _bufSize - 11 ; // _bufsize - frame - type - frame.counter - MAC @@ -288,13 +291,6 @@ int MI32_decryptPacket(char * _buf, uint16_t _bufSize, uint8_t * _payload, uint3 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); @@ -335,8 +331,7 @@ int MI32_decryptPacket(char * _buf, uint16_t _bufSize, uint8_t * _payload, uint3 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); + // tag[0] = _buf[_bufSize-1]; // it is unclear, if this value is a checksum dataLen -= 4; } else{ @@ -345,7 +340,6 @@ int MI32_decryptPacket(char * _buf, uint16_t _bufSize, uint8_t * _payload, uint3 br_aes_small_ctrcbc_keys keyCtx; 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); @@ -355,9 +349,9 @@ int MI32_decryptPacket(char * _buf, uint16_t _bufSize, uint8_t * _payload, uint3 br_ccm_run(&ctx, 0, _payload, dataLen); if(br_ccm_check_tag(&ctx, &tag)) return 0; - // AddLog(LOG_LEVEL_DEBUG,PSTR("M32: decrypted in %.2f mSec"),enctime); // AddLogBuffer(LOG_LEVEL_DEBUG,(uint8_t*) _payload, dataLen); + if(_version == 3 && _payload[1] == 0x10) return 0; // no known way to really verify decryption, but 0x10 is expected here for button events return -1; // wrong key ... maybe corrupt data packet too } @@ -457,9 +451,13 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter) _newSensor.feature.bat=1; _newSensor.NMT=0; break; - case YEERC: case YLKG08: - _newSensor.feature.Btn=1; - _newSensor.Btn=99; + case YLYK01: case YLKG08: + _newSensor.feature.Btn = 1; + _newSensor.Btn = 99; + if(_type == YLKG08){ + _newSensor.feature.knob = 1; + _newSensor.dimmer = 0; + } #ifdef USE_MI_HOMEKIT _newSensor.button_hap_service[0] = nullptr; #endif //USE_MI_HOMEKIT @@ -1222,40 +1220,46 @@ if(decryptRet!=0){ MIBLEsensors[_slot].lastTime = millis(); switch(_payload.type){ case 0x01: - if(_payload.Btn.type == 4){ //knob dimmer + if(_payload.Btn.type == 4){ //dimmer knob rotation + MIBLEsensors[_slot].eventType.knob = 1; 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); - } + MIBLEsensors[_slot].pressed = 0; + MIBLEsensors[_slot].dimmer = _payload.Btn.value; } - else if(_payload.Btn.num<128){ - AddLog(LOG_LEVEL_DEBUG,PSTR("Rotate right: %u"),_payload.Btn.num); + else { + MIBLEsensors[_slot].pressed = 1; + MIBLEsensors[_slot].dimmer = _payload.Btn.num; } - else{ - AddLog(LOG_LEVEL_DEBUG,PSTR("Rotate left: %u"),256 - _payload.Btn.num); - } - return; //TODO: implement MQTT later + MI32.mode.shallTriggerTele = 1; + break; //To-Do: Map to HomeKit somehow or wait for real support of this device class in HomeKit + } + if(_payload.Btn.num == 1 && MIBLEsensors[_slot].feature.knob){ //dimmer knob long press + MIBLEsensors[_slot].longpress = _payload.Btn.value; + MI32.mode.shallTriggerTele = 1; + MIBLEsensors[_slot].eventType.longpress = 1; +#ifdef USE_MI_HOMEKIT + if((void**)MIBLEsensors[_slot].button_hap_service[0] != nullptr){ + mi_homekit_update_value(MIBLEsensors[_slot].button_hap_service[0], (float)2.0f, 0x01); // only one button, long press = 2 + } +#endif //USE_MI_HOMEKIT + break; + } + // single, double, long + MIBLEsensors[_slot].Btn = _payload.Btn.num; + if(MIBLEsensors[_slot].feature.knob){ + MIBLEsensors[_slot].BtnType = _payload.Btn.value - 1; + } + else{ + MIBLEsensors[_slot].BtnType = _payload.Btn.type; } - 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(MIBLEsensors[_slot].Btn>5) break; // hard coded limit for now 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); - } - } + mi_homekit_update_value(MIBLEsensors[_slot].button_hap_service[MIBLEsensors[_slot].Btn], (float)MIBLEsensors[_slot].BtnType, 0x01); + } #endif //USE_MI_HOMEKIT // AddLog(LOG_LEVEL_DEBUG,PSTR("Mode 1: U16: %u Button"), MIBLEsensors[_slot].Btn ); break; @@ -1774,8 +1778,19 @@ void MI32sendWidget(uint32_t slot){ WSContentSend_P(PSTR("
")); } } + if(_sensor.feature.knob){ + if(_sensor.pressed == 0) { + WSContentSend_P(PSTR("Dimmer Steps: %d
"),_sensor.dimmer); + } + else { + WSContentSend_P(PSTR("Dimmer Steps pressed: %d
"),_sensor.dimmer); + } + WSContentSend_P(PSTR("Long: %u
"),_sensor.longpress); + } if(_sensor.feature.Btn){ - if(_sensor.Btn<12) WSContentSend_P(PSTR("Last Button: %u
"),_sensor.Btn); + char _message[16]; + GetTextIndexed(_message, sizeof(_message), _sensor.BtnType, kMI32_ButtonMsg); + if(_sensor.Btn<12) WSContentSend_P(PSTR("Button%u: %s
"),_sensor.Btn,_message); } if(_sensor.feature.motion){ WSContentSend_P(PSTR("Events: %u
"),_sensor.events); @@ -1797,7 +1812,7 @@ void MI32sendWidget(uint32_t slot){ WSContentSend_P(PSTR("Leak !!!
")); } else{ - WSContentSend_P(PSTR("no leak
")); + WSContentSend_P(PSTR("No leak
")); } } WSContentSend_P(PSTR("")); @@ -1990,7 +2005,29 @@ void MI32Show(bool json) #endif //USE_HOME_ASSISTANT ){ MI32ShowContinuation(&commaflg); - ResponseAppend_P(PSTR("\"Btn\":%u"),MIBLEsensors[i].Btn); + ResponseAppend_P(PSTR("\"Button%u\":%u"),MIBLEsensors[i].Btn,MIBLEsensors[i].BtnType + 1); //internal type is Xiaomi/Homekit 0,1,2 -> Tasmota 1,2,3 + } + } + if (MIBLEsensors[i].feature.knob){ + if(MIBLEsensors[i].eventType.knob +#ifdef USE_HOME_ASSISTANT + ||(hass_mode==2) +#endif //USE_HOME_ASSISTANT + ){ + MI32ShowContinuation(&commaflg); + char _pressed[3] = {'_','P',0}; + if (MIBLEsensors[i].pressed == 0){ + _pressed[0] = 0; + } + ResponseAppend_P(PSTR("\"Dimmer%s\":%d"),_pressed, MIBLEsensors[i].dimmer); + } + if(MIBLEsensors[i].eventType.longpress +#ifdef USE_HOME_ASSISTANT + ||(hass_mode==2) +#endif //USE_HOME_ASSISTANT + ){ + MI32ShowContinuation(&commaflg); + ResponseAppend_P(PSTR("\"Hold\":%d"), MIBLEsensors[i].longpress); } } } // minimal summary @@ -1998,14 +2035,14 @@ void MI32Show(bool json) if(MIBLEsensors[i].eventType.motion || !MI32.mode.triggeredTele){ if(MI32.mode.triggeredTele) { MI32ShowContinuation(&commaflg); - ResponseAppend_P(PSTR("\"motion\":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("\"motion\":0")); + ResponseAppend_P(PSTR("\"Motion\":0")); } } @@ -2013,7 +2050,7 @@ void MI32Show(bool json) 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 + ResponseAppend_P(PSTR("\"Door\":%u"),MIBLEsensors[i].door); } MI32ShowContinuation(&commaflg); ResponseAppend_P(PSTR("\"Events\":%u"),MIBLEsensors[i].events); @@ -2131,7 +2168,7 @@ void MI32Show(bool json) if(MIBLEsensors[i].bat!=0x00){ WSContentSend_PD(HTTP_BATTERY, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].bat); } - if (MIBLEsensors[i].type==YEERC){ + if (MIBLEsensors[i].type==YLYK01){ WSContentSend_PD(HTTP_LASTBUTTON, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].Btn); } } diff --git a/tasmota/xsns_62_esp32_mi_homekit.c b/tasmota/xsns_62_esp32_mi_homekit.c index 7e6c92ff3..cc7b25e5b 100644 --- a/tasmota/xsns_62_esp32_mi_homekit.c +++ b/tasmota/xsns_62_esp32_mi_homekit.c @@ -40,7 +40,7 @@ static bool MIBridgeWasNeverConnected = true; #define CGD1 6 #define NLIGHT 7 #define MJYD2S 8 -#define YEERC 9 +#define YLYK01 9 #define MHOC401 10 #define MHOC303 11 #define ATC 12 @@ -221,7 +221,7 @@ static void MI32_bridge_thread_entry(void *p) MI32saveHAPhandles(i,0x0a,hap_serv_get_char_by_uuid(service, HAP_CHAR_UUID_BATTERY_LEVEL)); break; } - case YEERC: + case YLYK01: { bridge_cfg.cid = HAP_CID_PROGRAMMABLE_SWITCH; hap_serv_t * _label = hap_serv_service_label_create(1); @@ -237,6 +237,20 @@ static void MI32_bridge_thread_entry(void *p) } } break; + case YLKG08: //without the dimmer function due to lack of HomeKit support + { + bridge_cfg.cid = HAP_CID_PROGRAMMABLE_SWITCH; + hap_serv_t * _label = hap_serv_service_label_create(1); + hap_acc_add_serv(accessory, _label); + hap_serv_t * _newSwitch = hap_serv_stateless_programmable_switch_create(0); + const uint8_t _validVals[] = {0,1,2}; + hap_char_add_valid_vals(hap_serv_get_char_by_uuid(_newSwitch, HAP_CHAR_UUID_PROGRAMMABLE_SWITCH_EVENT), _validVals, 3); + hap_char_t *_index = hap_char_service_label_index_create(1); + hap_serv_add_char(_newSwitch,_index); + hap_acc_add_serv(accessory, _newSwitch); + MI32saveHAPhandles(i,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);