|
4 | 4 | import struct
|
5 | 5 | from Cryptodome.Cipher import AES
|
6 | 6 |
|
| 7 | +from homeassistant.util import datetime |
| 8 | + |
7 | 9 | _LOGGER = logging.getLogger(__name__)
|
8 | 10 |
|
9 | 11 | # Device type dictionary
|
|
46 | 48 | 0x04E6: "YLYK01YL-VENFAN",
|
47 | 49 | 0x03BF: "YLYB01YL-BHFRC",
|
48 | 50 | 0x03B6: "YLKG07YL/YLKG08YL",
|
| 51 | + 0x069E: "ZNMS16LM", |
| 52 | + 0x069F: "ZNMS17LM", |
49 | 53 | }
|
50 | 54 |
|
51 | 55 | # Structured objects for data conversions
|
|
61 | 65 | P_STRUCT = struct.Struct("<H")
|
62 | 66 | BUTTON_STRUCT = struct.Struct("<BBB")
|
63 | 67 |
|
| 68 | +# Definition of lock messages |
| 69 | +BLE_LOCK_ERROR = { |
| 70 | + 0xC0DE0000: "frequent unlocking with incorrect password", |
| 71 | + 0xC0DE0001: "frequent unlocking with wrong fingerprints", |
| 72 | + 0xC0DE0002: "operation timeout (password input timeout)", |
| 73 | + 0xC0DE0003: "lock picking", |
| 74 | + 0xC0DE0004: "reset button is pressed", |
| 75 | + 0xC0DE0005: "the wrong key is frequently unlocked", |
| 76 | + 0xC0DE0006: "foreign body in the keyhole", |
| 77 | + 0xC0DE0007: "the key has not been taken out", |
| 78 | + 0xC0DE0008: "error NFC frequently unlocks", |
| 79 | + 0xC0DE0009: "timeout is not locked as required", |
| 80 | + 0xC0DE000A: "failure to unlock frequently in multiple ways", |
| 81 | + 0xC0DE000B: "unlocking the face frequently fails", |
| 82 | + 0xC0DE000C: "failure to unlock the vein frequently", |
| 83 | + 0xC0DE000D: "hijacking alarm", |
| 84 | + 0xC0DE000E: "unlock inside the door after arming", |
| 85 | + 0xC0DE000F: "palmprints frequently fail to unlock", |
| 86 | + 0xC0DE0010: "the safe was moved", |
| 87 | + 0xC0DE1000: "the battery level is less than 10%", |
| 88 | + 0xC0DE1001: "the battery is less than 5%", |
| 89 | + 0xC0DE1002: "the fingerprint sensor is abnormal", |
| 90 | + 0xC0DE1003: "the accessory battery is low", |
| 91 | + 0xC0DE1004: "mechanical failure", |
| 92 | + 0xC0DE1005: "the lock sensor is faulty", |
| 93 | +} |
| 94 | + |
| 95 | +BLE_LOCK_ACTION = { |
| 96 | + 0b0000: [0, "unlock outside the door"], |
| 97 | + 0b0001: [1, "lock"], |
| 98 | + 0b0010: [1, "turn on anti-lock"], |
| 99 | + 0b0011: [0, "turn off anti-lock"], |
| 100 | + 0b0100: [0, "unlock inside the door"], |
| 101 | + 0b0101: [1, "lock inside the door"], |
| 102 | + 0b0110: [1, "turn on child lock"], |
| 103 | + 0b0111: [0, "turn off child lock"], |
| 104 | + 0b1000: [1, "lock outside the door"], |
| 105 | + 0b1111: [0, "abnormal"], |
| 106 | +} |
| 107 | + |
| 108 | +BLE_LOCK_METHOD = { |
| 109 | + 0b0000: "bluetooth", |
| 110 | + 0b0001: "password", |
| 111 | + 0b0010: "biometrics", |
| 112 | + 0b0011: "key", |
| 113 | + 0b0100: "turntable", |
| 114 | + 0b0101: "nfc", |
| 115 | + 0b0110: "one-time password", |
| 116 | + 0b0111: "two-step verification", |
| 117 | + 0b1001: "Homekit", |
| 118 | + 0b1000: "coercion", |
| 119 | + 0b1010: "manual", |
| 120 | + 0b1011: "automatic", |
| 121 | + 0b1111: "abnormal" |
| 122 | +} |
| 123 | + |
64 | 124 |
|
65 | 125 | # Advertisement conversion of measurement data
|
66 | 126 | # https://iot.mi.com/new/doc/embedded-development/ble/object-definition
|
67 | 127 | def obj0003(xobj):
|
68 | 128 | return {"motion": xobj[0], "motion timer": xobj[0]}
|
69 | 129 |
|
70 | 130 |
|
| 131 | +def obj0006(xobj): |
| 132 | + if len(xobj) == 5: |
| 133 | + key_id = xobj[0:4] |
| 134 | + match_byte = xobj[4] |
| 135 | + if key_id == b'\x00\x00\x00\x00': |
| 136 | + key_id = "administrator" |
| 137 | + elif key_id == b'\xff\xff\xff\xff': |
| 138 | + key_id = "unknown operator" |
| 139 | + else: |
| 140 | + key_id = int.from_bytes(key_id, 'little') |
| 141 | + if match_byte == 0x00: |
| 142 | + result = "match successful" |
| 143 | + elif match_byte == 0x01: |
| 144 | + result = "match failed" |
| 145 | + elif match_byte == 0x02: |
| 146 | + result = "timeout" |
| 147 | + elif match_byte == 0x033: |
| 148 | + result = "low quality (too light, fuzzy)" |
| 149 | + elif match_byte == 0x04: |
| 150 | + result = "insufficient area" |
| 151 | + elif match_byte == 0x05: |
| 152 | + result = "skin is too dry" |
| 153 | + elif match_byte == 0x06: |
| 154 | + result = "skin is too wet" |
| 155 | + else: |
| 156 | + result = None |
| 157 | + |
| 158 | + fingerprint = 1 if match_byte == 0x00 else 0 |
| 159 | + |
| 160 | + return { |
| 161 | + "fingerprint": fingerprint, |
| 162 | + "result": result, |
| 163 | + "key id": key_id, |
| 164 | + } |
| 165 | + else: |
| 166 | + return {} |
| 167 | + |
| 168 | + |
71 | 169 | def obj0010(xobj):
|
72 | 170 | return {"toothbrush mode": xobj[1]}
|
73 | 171 |
|
74 | 172 |
|
| 173 | +def obj000b(xobj): |
| 174 | + if len(xobj) == 9: |
| 175 | + action = xobj[0] & 0x0F |
| 176 | + method = xobj[0] >> 4 |
| 177 | + key_id = int.from_bytes(xobj[1:5], 'little') |
| 178 | + timestamp = int.from_bytes(xobj[5:], 'little') |
| 179 | + |
| 180 | + timestamp = datetime.fromtimestamp(timestamp).isoformat() |
| 181 | + |
| 182 | + # all keys except Bluetooth have only 65536 values |
| 183 | + error = BLE_LOCK_ERROR.get(key_id) |
| 184 | + if error is None and method > 0: |
| 185 | + key_id &= 0xFFFF |
| 186 | + elif error: |
| 187 | + key_id = hex(key_id) |
| 188 | + |
| 189 | + if action not in BLE_LOCK_ACTION or method not in BLE_LOCK_METHOD: |
| 190 | + return {} |
| 191 | + |
| 192 | + lock = BLE_LOCK_ACTION[action][0] |
| 193 | + action = BLE_LOCK_ACTION[action][1] |
| 194 | + method = BLE_LOCK_METHOD[method] |
| 195 | + |
| 196 | + return { |
| 197 | + "lock": lock, |
| 198 | + "action": action, |
| 199 | + "method": method, |
| 200 | + "error": error, |
| 201 | + "key id": key_id, |
| 202 | + "timestamp": timestamp, |
| 203 | + } |
| 204 | + else: |
| 205 | + return {} |
| 206 | + |
| 207 | + |
75 | 208 | def obj000f(xobj):
|
76 | 209 | if len(xobj) == 3:
|
77 | 210 | (value,) = LIGHT_STRUCT.unpack(xobj + b'\x00')
|
@@ -370,7 +503,9 @@ def obj2000(xobj):
|
370 | 503 | # {dataObject_id: (converter}
|
371 | 504 | xiaomi_dataobject_dict = {
|
372 | 505 | 0x0003: obj0003,
|
| 506 | + 0x0006: obj0006, |
373 | 507 | 0x0010: obj0010,
|
| 508 | + 0x000B: obj000b, |
374 | 509 | 0x000F: obj000f,
|
375 | 510 | 0x1001: obj1001,
|
376 | 511 | 0x1004: obj1004,
|
|
0 commit comments