From 3d70a4734a9aaf1234b034e6023e14f958d510e5 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sat, 16 Dec 2023 18:48:59 +0100 Subject: [PATCH] Add Lockin SV40 --- .../ble_monitor/ble_parser/xiaomi.py | 1 + custom_components/ble_monitor/const.py | 2 + custom_components/ble_monitor/manifest.json | 2 +- .../ble_monitor/test/test_xiaomi_parser.py | 58 +++++++++++++ docs/_devices/Lockin_SV40.md | 77 ++++++++++++++++++ docs/assets/images/Lockin_SV40.png | Bin 0 -> 15878 bytes 6 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 docs/_devices/Lockin_SV40.md create mode 100644 docs/assets/images/Lockin_SV40.png 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 0000000000000000000000000000000000000000..397042e28741efc20dfe1104ff8e41a3a36741b3 GIT binary patch literal 15878 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 i8gW*HJt=zjia?#Y`7$UmDgexGgOuddW$R&