From 9749c9af599e58cc97ce7cff1e5696e2291d7b02 Mon Sep 17 00:00:00 2001
From: Staars
Date: Fri, 11 Feb 2022 18:03:22 +0100
Subject: [PATCH] add yeelight dimmer
---
tasmota/xsns_62_esp32_mi.h | 25 ++++--
tasmota/xsns_62_esp32_mi.ino | 135 ++++++++++++++++++-----------
tasmota/xsns_62_esp32_mi_homekit.c | 18 +++-
3 files changed, 119 insertions(+), 59 deletions(-)
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);