diff --git a/custom_components/ble_monitor/ble_parser/xiaomi.py b/custom_components/ble_monitor/ble_parser/xiaomi.py index e2274fc55..d716d2888 100755 --- a/custom_components/ble_monitor/ble_parser/xiaomi.py +++ b/custom_components/ble_monitor/ble_parser/xiaomi.py @@ -73,6 +73,7 @@ 0x0DE7: "SU001-T", 0x20DB: "MJZNZ018H", 0x18E3: "ZX1", + 0x11C2: "SV40", } # Structured objects for data conversions diff --git a/custom_components/ble_monitor/const.py b/custom_components/ble_monitor/const.py index c90465663..86ada798a 100755 --- a/custom_components/ble_monitor/const.py +++ b/custom_components/ble_monitor/const.py @@ -1581,6 +1581,7 @@ class BLEMonitorBinarySensorEntityDescription( 'T700i' : [["consumable", "score", "battery", "rssi"], [], ["toothbrush"]], 'ZNMS16LM' : [["battery", "rssi"], [], ["lock", "door", "fingerprint", "armed away"]], 'ZNMS17LM' : [["battery", "rssi"], [], ["lock", "door", "fingerprint", "antilock", "childlock", "armed away"]], + 'SV40' : [["battery", "rssi"], [], ["lock", "door", "fingerprint", "antilock", "childlock", "armed away"]], 'MJZNMSQ01YD' : [["battery", "rssi"], [], ["lock", "fingerprint"]], 'MJWSD05MMC' : [["temperature", "humidity", "battery", "rssi"], [], []], 'XMZNMST02YD' : [["battery", "rssi"], [], ["lock", "door", "fingerprint"]], @@ -1749,6 +1750,7 @@ class BLEMonitorBinarySensorEntityDescription( 'HS1BB(MI)' : 'Linptech', 'XMWXKG01YL' : 'Xiaomi', 'XMWXKG01LM' : 'Xiaomi', + 'SV40' : 'Lockin', 'SU001-T' : 'Petoneer', 'ATC' : 'ATC', 'Mi Scale V1' : 'Xiaomi', diff --git a/custom_components/ble_monitor/manifest.json b/custom_components/ble_monitor/manifest.json index bc12740a3..263bd43c9 100644 --- a/custom_components/ble_monitor/manifest.json +++ b/custom_components/ble_monitor/manifest.json @@ -14,5 +14,5 @@ "btsocket>=0.2.0", "pyric>=0.1.6.3" ], - "version": "12.6.5" + "version": "12.7.0" } diff --git a/custom_components/ble_monitor/test/test_xiaomi_parser.py b/custom_components/ble_monitor/test/test_xiaomi_parser.py index caff1b5ef..30dd4b1ea 100644 --- a/custom_components/ble_monitor/test/test_xiaomi_parser.py +++ b/custom_components/ble_monitor/test/test_xiaomi_parser.py @@ -487,6 +487,64 @@ def test_Xiaomi_ZNMS16LM_lock(self): assert sensor_msg["key id"] == 2 assert sensor_msg["rssi"] == -87 + def test_Xiaomi_SV40_door(self): + """Test Xiaomi parser for Lockin SV40.""" + self.aeskeys = {} + data_string = "043E27020100003d04a3330c981B020106171695fe4855c211144e28703276fccd3d00000080e72280C0" + data = bytes(bytearray.fromhex(data_string)) + + aeskey = "54d84797cb77f9538b224b305c877d1e" + + is_ext_packet = True if data[3] == 0x0D else False + mac = (data[8 if is_ext_packet else 7:14 if is_ext_packet else 13])[::-1] + mac_address = mac.hex() + p_mac = bytes.fromhex(mac_address.replace(":", "").lower()) + p_key = bytes.fromhex(aeskey.lower()) + self.aeskeys[p_mac] = p_key + # pylint: disable=unused-variable + ble_parser = BleParser(aeskeys=self.aeskeys) + sensor_msg, tracker_msg = ble_parser.parse_raw_data(data) + + assert sensor_msg["firmware"] == "Xiaomi (MiBeacon V5 encrypted)" + assert sensor_msg["type"] == "SV40" + assert sensor_msg["mac"] == "980C33A3043D" + assert sensor_msg["packet"] == 20 + assert sensor_msg["data"] + assert sensor_msg["door"] == 0 + assert sensor_msg["door action"] == "close the door" + assert sensor_msg["rssi"] == -64 + + def test_Xiaomi_SV40_lock(self): + """Test Xiaomi parser for Lockin SV40.""" + self.aeskeys = {} + data_string = "043E2B020100003d04a3330c981F0201061b1695fe4855c211165068b6fe3c878095c8a5834f000000463221c6C0" + data = bytes(bytearray.fromhex(data_string)) + + aeskey = "54d84797cb77f9538b224b305c877d1e" + + is_ext_packet = True if data[3] == 0x0D else False + mac = (data[8 if is_ext_packet else 7:14 if is_ext_packet else 13])[::-1] + mac_address = mac.hex() + p_mac = bytes.fromhex(mac_address.replace(":", "").lower()) + p_key = bytes.fromhex(aeskey.lower()) + self.aeskeys[p_mac] = p_key + # pylint: disable=unused-variable + ble_parser = BleParser(aeskeys=self.aeskeys) + sensor_msg, tracker_msg = ble_parser.parse_raw_data(data) + + assert sensor_msg["firmware"] == "Xiaomi (MiBeacon V5 encrypted)" + assert sensor_msg["type"] == "SV40" + assert sensor_msg["mac"] == "980C33A3043D" + assert sensor_msg["packet"] == 22 + assert sensor_msg["data"] + assert sensor_msg["lock"] == 1 + assert sensor_msg["locktype"] == "lock" + assert sensor_msg["action"] == "unlock inside the door" + assert sensor_msg["method"] == "automatic" + assert sensor_msg["error"] is None + assert sensor_msg["key id"] == 0 + assert sensor_msg["rssi"] == -64 + def test_Xiaomi_YLAI003(self): """Test Xiaomi parser for YLAI003.""" diff --git a/docs/_devices/Lockin_SV40.md b/docs/_devices/Lockin_SV40.md new file mode 100644 index 000000000..418aa73da --- /dev/null +++ b/docs/_devices/Lockin_SV40.md @@ -0,0 +1,77 @@ +--- +manufacturer: Lockin +name: Lockin Push-Pull Smart Lock SV40 +model: SV40 +image: Locking_SV40.png +physical_description: +broadcasted_properties: + - fingerprint + - lock + - battery + - result + - key id + - action + - method + - error + - timestamp + - door + - rssi +broadcasted_property_notes: + - property: fingerprint + note: The fingerprint sensor is `On` if the fingerprint scan was successful, otherwise it is `Off` The fingerprint entity has two extra attributes, `result` and `key id`. + - property: result + note: > + `result` shows the result of the last fingerprint reading and can have the following values: + * match successful + * match failed + * timeout + * low quality (too light, fuzzy) + * insufficient area + * skin is too dry + * skin is too wet + - property: key id + note: > + `key id` is an id number. For the fingerprint sensor, it can also be `administrator` or `unknown operator` + - property: lock + note: The state of the lock depends on the last `action`. The lock entity has five extra attributes, `action`, `method`, `error` and `key id` and `timestamp` + - property: action + note: > + `action` shows the last change of the lock and can have the following values: + * unlock outside the door + * lock + * turn on anti-lock + * turn off anti-lock + * unlock inside the door + * lock inside the door + * turn on child lock + * turn off child lock + * lock outside the door + * abnormal + - property: method + note: > + `method` shows the last used locking mechanism and can have the following values: + * unlock outside the door + * lock + * bluetooth + * password + * biometrics + * key + * turntable + * nfc + * one-time password + * two-step verification + * Homekit + * coercion + * manual + * automatic + * abnormal + - property: error + note: The error state of the lock + - property: timestamp + note: The timestamp of the latest lock change +broadcast_rate: +active_scan: +encryption_key: +custom_firmware: +notes: +--- diff --git a/docs/assets/images/Lockin_SV40.png b/docs/assets/images/Lockin_SV40.png new file mode 100644 index 000000000..397042e28 Binary files /dev/null and b/docs/assets/images/Lockin_SV40.png differ