PySwitchbot 0.63.0__tar.gz → 0.64.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PKG-INFO +1 -1
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/PKG-INFO +1 -1
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/SOURCES.txt +1 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/setup.py +1 -1
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parser.py +13 -1
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/hub3.py +1 -1
- pyswitchbot-0.64.0/switchbot/adv_parsers/lock.py +85 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/__init__.py +2 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/lock.py +1 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/lock.py +16 -3
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_adv_parser.py +309 -250
- pyswitchbot-0.64.0/tests/test_lock.py +42 -0
- pyswitchbot-0.63.0/switchbot/adv_parsers/lock.py +0 -57
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/LICENSE +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/MANIFEST.in +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/requires.txt +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/top_level.txt +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/README.md +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/pyproject.toml +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/setup.cfg +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/__init__.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/__init__.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/air_purifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/bot.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/bulb.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/contact.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/curtain.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/fan.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/hub2.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/hubmini_matter.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/humidifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/keypad.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/leak.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/light_strip.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/meter.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/motion.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/plug.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/relay_switch.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/remote.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/roller_shade.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/vacuum.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/api_config.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/air_purifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/evaporative_humidifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/fan.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/hub2.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/hub3.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/__init__.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/air_purifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/base_cover.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/base_light.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/blind_tilt.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/bot.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/bulb.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/ceiling_light.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/contact.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/curtain.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/device.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/evaporative_humidifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/fan.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/humidifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/keypad.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/light_strip.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/meter.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/motion.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/plug.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/relay_switch.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/roller_shade.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/vacuum.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/discovery.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/enum.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/helpers.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/models.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_air_purifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_base_cover.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_blind_tilt.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_curtain.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_evaporative_humidifier.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_fan.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_hub2.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_hub3.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_relay_switch.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_roller_shade.py +0 -0
- {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_vacuum.py +0 -0
|
@@ -25,7 +25,7 @@ from .adv_parsers.humidifier import process_evaporative_humidifier, process_wohu
|
|
|
25
25
|
from .adv_parsers.keypad import process_wokeypad
|
|
26
26
|
from .adv_parsers.leak import process_leak
|
|
27
27
|
from .adv_parsers.light_strip import process_wostrip
|
|
28
|
-
from .adv_parsers.lock import process_wolock, process_wolock_pro
|
|
28
|
+
from .adv_parsers.lock import process_lock2, process_wolock, process_wolock_pro
|
|
29
29
|
from .adv_parsers.meter import process_wosensorth, process_wosensorth_c
|
|
30
30
|
from .adv_parsers.motion import process_wopresence
|
|
31
31
|
from .adv_parsers.plug import process_woplugmini
|
|
@@ -300,6 +300,18 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
|
|
|
300
300
|
"func": process_hub3,
|
|
301
301
|
"manufacturer_id": 2409,
|
|
302
302
|
},
|
|
303
|
+
"-": {
|
|
304
|
+
"modelName": SwitchbotModel.LOCK_LITE,
|
|
305
|
+
"modelFriendlyName": "Lock Lite",
|
|
306
|
+
"func": process_wolock,
|
|
307
|
+
"manufacturer_id": 2409,
|
|
308
|
+
},
|
|
309
|
+
b"\x00\x10\xa5\xb8": {
|
|
310
|
+
"modelName": SwitchbotModel.LOCK_ULTRA,
|
|
311
|
+
"modelFriendlyName": "Lock Ultra",
|
|
312
|
+
"func": process_lock2,
|
|
313
|
+
"manufacturer_id": 2409,
|
|
314
|
+
},
|
|
303
315
|
}
|
|
304
316
|
|
|
305
317
|
_SWITCHBOT_MODEL_TO_CHAR = {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Lock parser."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from ..const.lock import LockStatus
|
|
8
|
+
|
|
9
|
+
_LOGGER = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def process_wolock(data: bytes | None, mfr_data: bytes | None) -> dict[str, bool | int]:
|
|
13
|
+
"""Support for lock and lock lite process data."""
|
|
14
|
+
if mfr_data is None:
|
|
15
|
+
return {}
|
|
16
|
+
|
|
17
|
+
_LOGGER.debug("mfr_data: %s", mfr_data.hex())
|
|
18
|
+
if data:
|
|
19
|
+
_LOGGER.debug("data: %s", data.hex())
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
"sequence_number": mfr_data[6],
|
|
23
|
+
"battery": data[2] & 0b01111111 if data else None,
|
|
24
|
+
"calibration": bool(mfr_data[7] & 0b10000000),
|
|
25
|
+
"status": LockStatus((mfr_data[7] & 0b01110000) >> 4),
|
|
26
|
+
"update_from_secondary_lock": bool(mfr_data[7] & 0b00001000),
|
|
27
|
+
"door_open": bool(mfr_data[7] & 0b00000100),
|
|
28
|
+
"double_lock_mode": bool(mfr_data[8] & 0b10000000),
|
|
29
|
+
"unclosed_alarm": bool(mfr_data[8] & 0b00100000),
|
|
30
|
+
"unlocked_alarm": bool(mfr_data[8] & 0b00010000),
|
|
31
|
+
"auto_lock_paused": bool(mfr_data[8] & 0b00000010),
|
|
32
|
+
"night_latch": bool(mfr_data[9] & 0b00000001) if len(mfr_data) > 9 else False,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_common_data(mfr_data: bytes | None) -> dict[str, bool | int]:
|
|
37
|
+
if mfr_data is None:
|
|
38
|
+
return {}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
"sequence_number": mfr_data[6],
|
|
42
|
+
"calibration": bool(mfr_data[7] & 0b10000000),
|
|
43
|
+
"status": LockStatus((mfr_data[7] & 0b01111000) >> 4),
|
|
44
|
+
"update_from_secondary_lock": bool(mfr_data[8] & 0b11000000),
|
|
45
|
+
"door_open_from_secondary_lock": bool(mfr_data[8] & 0b00100000),
|
|
46
|
+
"door_open": bool(mfr_data[8] & 0b00010000),
|
|
47
|
+
"auto_lock_paused": bool(mfr_data[8] & 0b00001000),
|
|
48
|
+
"battery": mfr_data[9] & 0b01111111,
|
|
49
|
+
"double_lock_mode": bool(mfr_data[10] & 0b10000000),
|
|
50
|
+
"is_secondary_lock": bool(mfr_data[10] & 0b01000000),
|
|
51
|
+
"manual_unlock_linkage": bool(mfr_data[10] & 0b00100000),
|
|
52
|
+
"unclosed_alarm": bool(mfr_data[11] & 0b10000000),
|
|
53
|
+
"unlocked_alarm": bool(mfr_data[11] & 0b01000000),
|
|
54
|
+
"night_latch": False,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def process_wolock_pro(
|
|
59
|
+
data: bytes | None, mfr_data: bytes | None
|
|
60
|
+
) -> dict[str, bool | int]:
|
|
61
|
+
"""Support for lock pro process data."""
|
|
62
|
+
common_data = parse_common_data(mfr_data)
|
|
63
|
+
if not common_data:
|
|
64
|
+
return {}
|
|
65
|
+
|
|
66
|
+
lock_pro_data = {
|
|
67
|
+
"low_temperature_alarm": bool(mfr_data[11] & 0b00100000),
|
|
68
|
+
"left_battery_compartment_alarm": mfr_data[11] & 0b000000100,
|
|
69
|
+
"right_battery_compartment_alarm": mfr_data[11] & 0b000000010,
|
|
70
|
+
}
|
|
71
|
+
return common_data | lock_pro_data
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def process_lock2(data: bytes | None, mfr_data: bytes | None) -> dict[str, bool | int]:
|
|
75
|
+
"""Support for lock2 process data."""
|
|
76
|
+
common_data = parse_common_data(mfr_data)
|
|
77
|
+
if not common_data:
|
|
78
|
+
return {}
|
|
79
|
+
|
|
80
|
+
lock2_data = {
|
|
81
|
+
"power_alarm": bool(mfr_data[11] & 0b00010000),
|
|
82
|
+
"battery_status": mfr_data[11] & 0b00000111,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return common_data | lock2_data
|
|
@@ -10,24 +10,32 @@ from bleak.backends.device import BLEDevice
|
|
|
10
10
|
|
|
11
11
|
from ..const import SwitchbotModel
|
|
12
12
|
from ..const.lock import LockStatus
|
|
13
|
-
from .device import SwitchbotEncryptedDevice
|
|
13
|
+
from .device import SwitchbotEncryptedDevice, SwitchbotSequenceDevice
|
|
14
14
|
|
|
15
15
|
COMMAND_HEADER = "57"
|
|
16
16
|
COMMAND_LOCK_INFO = {
|
|
17
17
|
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0f4f8101",
|
|
18
|
+
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0f4f8101",
|
|
18
19
|
SwitchbotModel.LOCK_PRO: f"{COMMAND_HEADER}0f4f8102",
|
|
20
|
+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4f8102",
|
|
19
21
|
}
|
|
20
22
|
COMMAND_UNLOCK = {
|
|
21
23
|
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0f4e01011080",
|
|
24
|
+
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0f4e01011080",
|
|
22
25
|
SwitchbotModel.LOCK_PRO: f"{COMMAND_HEADER}0f4e0101000080",
|
|
26
|
+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4e0101000080",
|
|
23
27
|
}
|
|
24
28
|
COMMAND_UNLOCK_WITHOUT_UNLATCH = {
|
|
25
29
|
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0f4e010110a0",
|
|
30
|
+
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0f4e010110a0",
|
|
26
31
|
SwitchbotModel.LOCK_PRO: f"{COMMAND_HEADER}0f4e01010000a0",
|
|
32
|
+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4e01010000a0",
|
|
27
33
|
}
|
|
28
34
|
COMMAND_LOCK = {
|
|
29
35
|
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0f4e01011000",
|
|
36
|
+
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0f4e01011000",
|
|
30
37
|
SwitchbotModel.LOCK_PRO: f"{COMMAND_HEADER}0f4e0101000000",
|
|
38
|
+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4e0101000000",
|
|
31
39
|
}
|
|
32
40
|
COMMAND_ENABLE_NOTIFICATIONS = f"{COMMAND_HEADER}0e01001e00008101"
|
|
33
41
|
COMMAND_DISABLE_NOTIFICATIONS = f"{COMMAND_HEADER}0e00"
|
|
@@ -44,7 +52,7 @@ COMMAND_RESULT_EXPECTED_VALUES = {1, 6}
|
|
|
44
52
|
# The return value of the command is 6 when the command is successful but the battery is low.
|
|
45
53
|
|
|
46
54
|
|
|
47
|
-
class SwitchbotLock(SwitchbotEncryptedDevice):
|
|
55
|
+
class SwitchbotLock(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
|
|
48
56
|
"""Representation of a Switchbot Lock."""
|
|
49
57
|
|
|
50
58
|
def __init__(
|
|
@@ -56,7 +64,12 @@ class SwitchbotLock(SwitchbotEncryptedDevice):
|
|
|
56
64
|
model: SwitchbotModel = SwitchbotModel.LOCK,
|
|
57
65
|
**kwargs: Any,
|
|
58
66
|
) -> None:
|
|
59
|
-
if model not in (
|
|
67
|
+
if model not in (
|
|
68
|
+
SwitchbotModel.LOCK,
|
|
69
|
+
SwitchbotModel.LOCK_PRO,
|
|
70
|
+
SwitchbotModel.LOCK_LITE,
|
|
71
|
+
SwitchbotModel.LOCK_ULTRA,
|
|
72
|
+
):
|
|
60
73
|
raise ValueError("initializing SwitchbotLock with a non-lock model")
|
|
61
74
|
self._notifications_enabled: bool = False
|
|
62
75
|
super().__init__(device, key_id, encryption_key, model, interface, **kwargs)
|
|
@@ -11,7 +11,7 @@ from switchbot.adv_parser import parse_advertisement_data
|
|
|
11
11
|
from switchbot.const.lock import LockStatus
|
|
12
12
|
from switchbot.models import SwitchBotAdvertisement
|
|
13
13
|
|
|
14
|
-
from . import
|
|
14
|
+
from . import AdvTestCase
|
|
15
15
|
|
|
16
16
|
ADVERTISEMENT_DATA_DEFAULTS = {
|
|
17
17
|
"local_name": "",
|
|
@@ -1288,248 +1288,6 @@ def test_motion_with_light_detected():
|
|
|
1288
1288
|
)
|
|
1289
1289
|
|
|
1290
1290
|
|
|
1291
|
-
def test_parsing_lock_active():
|
|
1292
|
-
"""Test parsing lock with active data."""
|
|
1293
|
-
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1294
|
-
adv_data = generate_advertisement_data(
|
|
1295
|
-
manufacturer_data={2409: b"\xf1\t\x9fE\x1a]\x07\x83\x00 "},
|
|
1296
|
-
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"o\x80d"},
|
|
1297
|
-
rssi=-67,
|
|
1298
|
-
)
|
|
1299
|
-
result = parse_advertisement_data(ble_device, adv_data)
|
|
1300
|
-
assert result == SwitchBotAdvertisement(
|
|
1301
|
-
address="aa:bb:cc:dd:ee:ff",
|
|
1302
|
-
data={
|
|
1303
|
-
"data": {
|
|
1304
|
-
"auto_lock_paused": False,
|
|
1305
|
-
"battery": 100,
|
|
1306
|
-
"calibration": True,
|
|
1307
|
-
"door_open": False,
|
|
1308
|
-
"double_lock_mode": False,
|
|
1309
|
-
"night_latch": False,
|
|
1310
|
-
"status": LockStatus.LOCKED,
|
|
1311
|
-
"unclosed_alarm": False,
|
|
1312
|
-
"unlocked_alarm": False,
|
|
1313
|
-
"update_from_secondary_lock": False,
|
|
1314
|
-
},
|
|
1315
|
-
"isEncrypted": False,
|
|
1316
|
-
"model": "o",
|
|
1317
|
-
"modelFriendlyName": "Lock",
|
|
1318
|
-
"modelName": SwitchbotModel.LOCK,
|
|
1319
|
-
"rawAdvData": b"o\x80d",
|
|
1320
|
-
},
|
|
1321
|
-
device=ble_device,
|
|
1322
|
-
rssi=-67,
|
|
1323
|
-
active=True,
|
|
1324
|
-
)
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
def test_parsing_lock_passive():
|
|
1328
|
-
"""Test parsing lock with active data."""
|
|
1329
|
-
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1330
|
-
adv_data = generate_advertisement_data(
|
|
1331
|
-
manufacturer_data={2409: b"\xf1\t\x9fE\x1a]\x07\x83\x00 "}, rssi=-67
|
|
1332
|
-
)
|
|
1333
|
-
result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.LOCK)
|
|
1334
|
-
assert result == SwitchBotAdvertisement(
|
|
1335
|
-
address="aa:bb:cc:dd:ee:ff",
|
|
1336
|
-
data={
|
|
1337
|
-
"data": {
|
|
1338
|
-
"auto_lock_paused": False,
|
|
1339
|
-
"battery": None,
|
|
1340
|
-
"calibration": True,
|
|
1341
|
-
"door_open": False,
|
|
1342
|
-
"double_lock_mode": False,
|
|
1343
|
-
"night_latch": False,
|
|
1344
|
-
"status": LockStatus.LOCKED,
|
|
1345
|
-
"unclosed_alarm": False,
|
|
1346
|
-
"unlocked_alarm": False,
|
|
1347
|
-
"update_from_secondary_lock": False,
|
|
1348
|
-
},
|
|
1349
|
-
"isEncrypted": False,
|
|
1350
|
-
"model": "o",
|
|
1351
|
-
"modelFriendlyName": "Lock",
|
|
1352
|
-
"modelName": SwitchbotModel.LOCK,
|
|
1353
|
-
"rawAdvData": None,
|
|
1354
|
-
},
|
|
1355
|
-
device=ble_device,
|
|
1356
|
-
rssi=-67,
|
|
1357
|
-
active=False,
|
|
1358
|
-
)
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
def test_parsing_lock_pro_active():
|
|
1362
|
-
"""Test parsing lock pro with active data."""
|
|
1363
|
-
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1364
|
-
adv_data = generate_advertisement_data(
|
|
1365
|
-
manufacturer_data={2409: b"\xc8\xf5,\xd9-V\x07\x82\x00d\x00\x00"},
|
|
1366
|
-
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"$\x80d"},
|
|
1367
|
-
rssi=-80,
|
|
1368
|
-
)
|
|
1369
|
-
result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.LOCK_PRO)
|
|
1370
|
-
assert result == SwitchBotAdvertisement(
|
|
1371
|
-
address="aa:bb:cc:dd:ee:ff",
|
|
1372
|
-
data={
|
|
1373
|
-
"data": {
|
|
1374
|
-
"battery": 100,
|
|
1375
|
-
"calibration": True,
|
|
1376
|
-
"status": LockStatus.LOCKED,
|
|
1377
|
-
"update_from_secondary_lock": False,
|
|
1378
|
-
"door_open": False,
|
|
1379
|
-
"double_lock_mode": False,
|
|
1380
|
-
"unclosed_alarm": False,
|
|
1381
|
-
"unlocked_alarm": False,
|
|
1382
|
-
"auto_lock_paused": False,
|
|
1383
|
-
"night_latch": False,
|
|
1384
|
-
},
|
|
1385
|
-
"model": "$",
|
|
1386
|
-
"isEncrypted": False,
|
|
1387
|
-
"modelFriendlyName": "Lock Pro",
|
|
1388
|
-
"modelName": SwitchbotModel.LOCK_PRO,
|
|
1389
|
-
"rawAdvData": b"$\x80d",
|
|
1390
|
-
},
|
|
1391
|
-
device=ble_device,
|
|
1392
|
-
rssi=-80,
|
|
1393
|
-
active=True,
|
|
1394
|
-
)
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
def test_parsing_lock_pro_passive():
|
|
1398
|
-
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1399
|
-
adv_data = generate_advertisement_data(
|
|
1400
|
-
manufacturer_data={2409: bytes.fromhex("aabbccddeeff208200640000")}, rssi=-67
|
|
1401
|
-
)
|
|
1402
|
-
result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.LOCK_PRO)
|
|
1403
|
-
assert result == SwitchBotAdvertisement(
|
|
1404
|
-
address="aa:bb:cc:dd:ee:ff",
|
|
1405
|
-
data={
|
|
1406
|
-
"data": {
|
|
1407
|
-
"battery": None,
|
|
1408
|
-
"calibration": True,
|
|
1409
|
-
"status": LockStatus.LOCKED,
|
|
1410
|
-
"update_from_secondary_lock": False,
|
|
1411
|
-
"door_open": False,
|
|
1412
|
-
"double_lock_mode": False,
|
|
1413
|
-
"unclosed_alarm": False,
|
|
1414
|
-
"unlocked_alarm": False,
|
|
1415
|
-
"auto_lock_paused": False,
|
|
1416
|
-
"night_latch": False,
|
|
1417
|
-
},
|
|
1418
|
-
"model": "$",
|
|
1419
|
-
"isEncrypted": False,
|
|
1420
|
-
"modelFriendlyName": "Lock Pro",
|
|
1421
|
-
"modelName": SwitchbotModel.LOCK_PRO,
|
|
1422
|
-
"rawAdvData": None,
|
|
1423
|
-
},
|
|
1424
|
-
device=ble_device,
|
|
1425
|
-
rssi=-67,
|
|
1426
|
-
active=False,
|
|
1427
|
-
)
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
def test_parsing_lock_pro_passive_nightlatch_disabled():
|
|
1431
|
-
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1432
|
-
adv_data = generate_advertisement_data(
|
|
1433
|
-
manufacturer_data={2409: bytes.fromhex("aabbccddeeff0a8200630000")}, rssi=-67
|
|
1434
|
-
)
|
|
1435
|
-
result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.LOCK_PRO)
|
|
1436
|
-
assert result == SwitchBotAdvertisement(
|
|
1437
|
-
address="aa:bb:cc:dd:ee:ff",
|
|
1438
|
-
data={
|
|
1439
|
-
"data": {
|
|
1440
|
-
"battery": None,
|
|
1441
|
-
"calibration": True,
|
|
1442
|
-
"status": LockStatus.LOCKED,
|
|
1443
|
-
"update_from_secondary_lock": False,
|
|
1444
|
-
"door_open": False,
|
|
1445
|
-
"double_lock_mode": False,
|
|
1446
|
-
"unclosed_alarm": False,
|
|
1447
|
-
"unlocked_alarm": False,
|
|
1448
|
-
"auto_lock_paused": False,
|
|
1449
|
-
"night_latch": False,
|
|
1450
|
-
},
|
|
1451
|
-
"model": "$",
|
|
1452
|
-
"isEncrypted": False,
|
|
1453
|
-
"modelFriendlyName": "Lock Pro",
|
|
1454
|
-
"modelName": SwitchbotModel.LOCK_PRO,
|
|
1455
|
-
"rawAdvData": None,
|
|
1456
|
-
},
|
|
1457
|
-
device=ble_device,
|
|
1458
|
-
rssi=-67,
|
|
1459
|
-
active=False,
|
|
1460
|
-
)
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
def test_parsing_lock_active_old_firmware():
|
|
1464
|
-
"""Test parsing lock with active data. Old firmware."""
|
|
1465
|
-
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1466
|
-
adv_data = generate_advertisement_data(
|
|
1467
|
-
manufacturer_data={2409: b"\xf1\t\x9fE\x1a]\x07\x83\x00"},
|
|
1468
|
-
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"o\x80d"},
|
|
1469
|
-
rssi=-67,
|
|
1470
|
-
)
|
|
1471
|
-
result = parse_advertisement_data(ble_device, adv_data)
|
|
1472
|
-
assert result == SwitchBotAdvertisement(
|
|
1473
|
-
address="aa:bb:cc:dd:ee:ff",
|
|
1474
|
-
data={
|
|
1475
|
-
"data": {
|
|
1476
|
-
"auto_lock_paused": False,
|
|
1477
|
-
"battery": 100,
|
|
1478
|
-
"calibration": True,
|
|
1479
|
-
"door_open": False,
|
|
1480
|
-
"double_lock_mode": False,
|
|
1481
|
-
"night_latch": False,
|
|
1482
|
-
"status": LockStatus.LOCKED,
|
|
1483
|
-
"unclosed_alarm": False,
|
|
1484
|
-
"unlocked_alarm": False,
|
|
1485
|
-
"update_from_secondary_lock": False,
|
|
1486
|
-
},
|
|
1487
|
-
"isEncrypted": False,
|
|
1488
|
-
"model": "o",
|
|
1489
|
-
"modelFriendlyName": "Lock",
|
|
1490
|
-
"modelName": SwitchbotModel.LOCK,
|
|
1491
|
-
"rawAdvData": b"o\x80d",
|
|
1492
|
-
},
|
|
1493
|
-
device=ble_device,
|
|
1494
|
-
rssi=-67,
|
|
1495
|
-
active=True,
|
|
1496
|
-
)
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
def test_parsing_lock_passive_old_firmware():
|
|
1500
|
-
"""Test parsing lock with active data. Old firmware."""
|
|
1501
|
-
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1502
|
-
adv_data = generate_advertisement_data(
|
|
1503
|
-
manufacturer_data={2409: b"\xf1\t\x9fE\x1a]\x07\x83\x00"}, rssi=-67
|
|
1504
|
-
)
|
|
1505
|
-
result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.LOCK)
|
|
1506
|
-
assert result == SwitchBotAdvertisement(
|
|
1507
|
-
address="aa:bb:cc:dd:ee:ff",
|
|
1508
|
-
data={
|
|
1509
|
-
"data": {
|
|
1510
|
-
"auto_lock_paused": False,
|
|
1511
|
-
"battery": None,
|
|
1512
|
-
"calibration": True,
|
|
1513
|
-
"door_open": False,
|
|
1514
|
-
"double_lock_mode": False,
|
|
1515
|
-
"night_latch": False,
|
|
1516
|
-
"status": LockStatus.LOCKED,
|
|
1517
|
-
"unclosed_alarm": False,
|
|
1518
|
-
"unlocked_alarm": False,
|
|
1519
|
-
"update_from_secondary_lock": False,
|
|
1520
|
-
},
|
|
1521
|
-
"isEncrypted": False,
|
|
1522
|
-
"model": "o",
|
|
1523
|
-
"modelFriendlyName": "Lock",
|
|
1524
|
-
"modelName": SwitchbotModel.LOCK,
|
|
1525
|
-
"rawAdvData": None,
|
|
1526
|
-
},
|
|
1527
|
-
device=ble_device,
|
|
1528
|
-
rssi=-67,
|
|
1529
|
-
active=False,
|
|
1530
|
-
)
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
1291
|
def test_meter_pro_active() -> None:
|
|
1534
1292
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1535
1293
|
adv_data = generate_advertisement_data(
|
|
@@ -2651,7 +2409,7 @@ def test_s10_with_empty_data() -> None:
|
|
|
2651
2409
|
@pytest.mark.parametrize(
|
|
2652
2410
|
"test_case",
|
|
2653
2411
|
[
|
|
2654
|
-
|
|
2412
|
+
AdvTestCase(
|
|
2655
2413
|
b"\xf0\x9e\x9e\x96j\xd6\xa1\x81\x88\xe4\x00\x01\x95\x00\x00",
|
|
2656
2414
|
b"7\x00\x00\x95-\x00",
|
|
2657
2415
|
{
|
|
@@ -2669,7 +2427,7 @@ def test_s10_with_empty_data() -> None:
|
|
|
2669
2427
|
"Air Purifier Table",
|
|
2670
2428
|
SwitchbotModel.AIR_PURIFIER_TABLE,
|
|
2671
2429
|
),
|
|
2672
|
-
|
|
2430
|
+
AdvTestCase(
|
|
2673
2431
|
b'\xcc\x8d\xa2\xa7\x92>\t"\x80\x000\x00\x0f\x00\x00',
|
|
2674
2432
|
b"*\x00\x00\x15\x04\x00",
|
|
2675
2433
|
{
|
|
@@ -2687,7 +2445,7 @@ def test_s10_with_empty_data() -> None:
|
|
|
2687
2445
|
"Air Purifier",
|
|
2688
2446
|
SwitchbotModel.AIR_PURIFIER,
|
|
2689
2447
|
),
|
|
2690
|
-
|
|
2448
|
+
AdvTestCase(
|
|
2691
2449
|
b"\xcc\x8d\xa2\xa7\xe4\xa6\x0b\x83\x88d\x00\xea`\x00\x00",
|
|
2692
2450
|
b"+\x00\x00\x15\x04\x00",
|
|
2693
2451
|
{
|
|
@@ -2705,7 +2463,7 @@ def test_s10_with_empty_data() -> None:
|
|
|
2705
2463
|
"Air Purifier",
|
|
2706
2464
|
SwitchbotModel.AIR_PURIFIER,
|
|
2707
2465
|
),
|
|
2708
|
-
|
|
2466
|
+
AdvTestCase(
|
|
2709
2467
|
b"\xcc\x8d\xa2\xa7\xc1\xae\x9b\x81\x8c\xb2\x00\x01\x94\x00\x00",
|
|
2710
2468
|
b"8\x00\x00\x95-\x00",
|
|
2711
2469
|
{
|
|
@@ -2723,7 +2481,7 @@ def test_s10_with_empty_data() -> None:
|
|
|
2723
2481
|
"Air Purifier Table",
|
|
2724
2482
|
SwitchbotModel.AIR_PURIFIER_TABLE,
|
|
2725
2483
|
),
|
|
2726
|
-
|
|
2484
|
+
AdvTestCase(
|
|
2727
2485
|
b"\xcc\x8d\xa2\xa7\xc1\xae\x9e\xa1\x8c\x800\x01\x95\x00\x00",
|
|
2728
2486
|
b"8\x00\x00\x95-\x00",
|
|
2729
2487
|
{
|
|
@@ -2741,7 +2499,7 @@ def test_s10_with_empty_data() -> None:
|
|
|
2741
2499
|
"Air Purifier Table",
|
|
2742
2500
|
SwitchbotModel.AIR_PURIFIER_TABLE,
|
|
2743
2501
|
),
|
|
2744
|
-
|
|
2502
|
+
AdvTestCase(
|
|
2745
2503
|
b"\xcc\x8d\xa2\xa7\xc1\xae\x9e\x05\x8c\x800\x01\x95\x00\x00",
|
|
2746
2504
|
b"8\x00\x00\x95-\x00",
|
|
2747
2505
|
{
|
|
@@ -2761,7 +2519,7 @@ def test_s10_with_empty_data() -> None:
|
|
|
2761
2519
|
),
|
|
2762
2520
|
],
|
|
2763
2521
|
)
|
|
2764
|
-
def test_air_purifier_active(test_case:
|
|
2522
|
+
def test_air_purifier_active(test_case: AdvTestCase) -> None:
|
|
2765
2523
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
2766
2524
|
adv_data = generate_advertisement_data(
|
|
2767
2525
|
manufacturer_data={2409: test_case.manufacturer_data},
|
|
@@ -2942,3 +2700,304 @@ def test_hub3_with_empty_data() -> None:
|
|
|
2942
2700
|
rssi=-97,
|
|
2943
2701
|
active=True,
|
|
2944
2702
|
)
|
|
2703
|
+
|
|
2704
|
+
|
|
2705
|
+
@pytest.mark.parametrize(
|
|
2706
|
+
"test_case",
|
|
2707
|
+
[
|
|
2708
|
+
AdvTestCase(
|
|
2709
|
+
b"\xe9\xd5\x11\xb2kS\x17\x93\x08 ",
|
|
2710
|
+
b"-\x80d",
|
|
2711
|
+
{
|
|
2712
|
+
"sequence_number": 23,
|
|
2713
|
+
"battery": 100,
|
|
2714
|
+
"calibration": True,
|
|
2715
|
+
"status": LockStatus.UNLOCKED,
|
|
2716
|
+
"update_from_secondary_lock": False,
|
|
2717
|
+
"door_open": False,
|
|
2718
|
+
"double_lock_mode": False,
|
|
2719
|
+
"unclosed_alarm": False,
|
|
2720
|
+
"unlocked_alarm": False,
|
|
2721
|
+
"auto_lock_paused": False,
|
|
2722
|
+
"night_latch": False,
|
|
2723
|
+
},
|
|
2724
|
+
"-",
|
|
2725
|
+
"Lock Lite",
|
|
2726
|
+
SwitchbotModel.LOCK_LITE,
|
|
2727
|
+
),
|
|
2728
|
+
AdvTestCase(
|
|
2729
|
+
b"\xee\xf5\xe6\t\x8f\xe8\x11\x97\x08 ",
|
|
2730
|
+
b"o\x80d",
|
|
2731
|
+
{
|
|
2732
|
+
"sequence_number": 17,
|
|
2733
|
+
"battery": 100,
|
|
2734
|
+
"calibration": True,
|
|
2735
|
+
"status": LockStatus.UNLOCKED,
|
|
2736
|
+
"update_from_secondary_lock": False,
|
|
2737
|
+
"door_open": True,
|
|
2738
|
+
"double_lock_mode": False,
|
|
2739
|
+
"unclosed_alarm": False,
|
|
2740
|
+
"unlocked_alarm": False,
|
|
2741
|
+
"auto_lock_paused": False,
|
|
2742
|
+
"night_latch": False,
|
|
2743
|
+
},
|
|
2744
|
+
"o",
|
|
2745
|
+
"Lock",
|
|
2746
|
+
SwitchbotModel.LOCK,
|
|
2747
|
+
),
|
|
2748
|
+
AdvTestCase(
|
|
2749
|
+
b"\xf7a\x07H\xe6\xe8:\x8a\x00d\x00\x00",
|
|
2750
|
+
b"$\x80d",
|
|
2751
|
+
{
|
|
2752
|
+
"sequence_number": 58,
|
|
2753
|
+
"battery": 100,
|
|
2754
|
+
"calibration": True,
|
|
2755
|
+
"status": LockStatus.LOCKED,
|
|
2756
|
+
"update_from_secondary_lock": False,
|
|
2757
|
+
"door_open": False,
|
|
2758
|
+
"door_open_from_secondary_lock": False,
|
|
2759
|
+
"double_lock_mode": False,
|
|
2760
|
+
"is_secondary_lock": False,
|
|
2761
|
+
"left_battery_compartment_alarm": 0,
|
|
2762
|
+
"right_battery_compartment_alarm": 0,
|
|
2763
|
+
"low_temperature_alarm": False,
|
|
2764
|
+
"manual_unlock_linkage": False,
|
|
2765
|
+
"unclosed_alarm": False,
|
|
2766
|
+
"unlocked_alarm": False,
|
|
2767
|
+
"auto_lock_paused": False,
|
|
2768
|
+
"night_latch": False,
|
|
2769
|
+
},
|
|
2770
|
+
"$",
|
|
2771
|
+
"Lock Pro",
|
|
2772
|
+
SwitchbotModel.LOCK_PRO,
|
|
2773
|
+
),
|
|
2774
|
+
AdvTestCase(
|
|
2775
|
+
b"\xb0\xe9\xfe\xb6j=%\x8204\x00\x04",
|
|
2776
|
+
b"\x00\x804\x00\x10\xa5\xb8",
|
|
2777
|
+
{
|
|
2778
|
+
"sequence_number": 37,
|
|
2779
|
+
"battery": 52,
|
|
2780
|
+
"calibration": True,
|
|
2781
|
+
"status": LockStatus.LOCKED,
|
|
2782
|
+
"update_from_secondary_lock": False,
|
|
2783
|
+
"door_open": True,
|
|
2784
|
+
"door_open_from_secondary_lock": True,
|
|
2785
|
+
"double_lock_mode": False,
|
|
2786
|
+
"is_secondary_lock": False,
|
|
2787
|
+
"manual_unlock_linkage": False,
|
|
2788
|
+
"unclosed_alarm": False,
|
|
2789
|
+
"unlocked_alarm": False,
|
|
2790
|
+
"auto_lock_paused": False,
|
|
2791
|
+
"night_latch": False,
|
|
2792
|
+
"power_alarm": False,
|
|
2793
|
+
"battery_status": 4,
|
|
2794
|
+
},
|
|
2795
|
+
b"\x00\x10\xa5\xb8",
|
|
2796
|
+
"Lock Ultra",
|
|
2797
|
+
SwitchbotModel.LOCK_ULTRA,
|
|
2798
|
+
),
|
|
2799
|
+
],
|
|
2800
|
+
)
|
|
2801
|
+
def test_lock_active(test_case: AdvTestCase) -> None:
|
|
2802
|
+
"""Test lokc series with active data."""
|
|
2803
|
+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
2804
|
+
adv_data = generate_advertisement_data(
|
|
2805
|
+
manufacturer_data={2409: test_case.manufacturer_data},
|
|
2806
|
+
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": test_case.service_data},
|
|
2807
|
+
rssi=-97,
|
|
2808
|
+
)
|
|
2809
|
+
result = parse_advertisement_data(ble_device, adv_data)
|
|
2810
|
+
assert result == SwitchBotAdvertisement(
|
|
2811
|
+
address="aa:bb:cc:dd:ee:ff",
|
|
2812
|
+
data={
|
|
2813
|
+
"rawAdvData": test_case.service_data,
|
|
2814
|
+
"data": test_case.data,
|
|
2815
|
+
"isEncrypted": False,
|
|
2816
|
+
"model": test_case.model,
|
|
2817
|
+
"modelFriendlyName": test_case.modelFriendlyName,
|
|
2818
|
+
"modelName": test_case.modelName,
|
|
2819
|
+
},
|
|
2820
|
+
device=ble_device,
|
|
2821
|
+
rssi=-97,
|
|
2822
|
+
active=True,
|
|
2823
|
+
)
|
|
2824
|
+
|
|
2825
|
+
|
|
2826
|
+
@pytest.mark.parametrize(
|
|
2827
|
+
"test_case",
|
|
2828
|
+
[
|
|
2829
|
+
AdvTestCase(
|
|
2830
|
+
b"\xe9\xd5\x11\xb2kS\x17\x93\x08 ",
|
|
2831
|
+
b"-\x80d",
|
|
2832
|
+
{
|
|
2833
|
+
"sequence_number": 23,
|
|
2834
|
+
"battery": None,
|
|
2835
|
+
"calibration": True,
|
|
2836
|
+
"status": LockStatus.UNLOCKED,
|
|
2837
|
+
"update_from_secondary_lock": False,
|
|
2838
|
+
"door_open": False,
|
|
2839
|
+
"double_lock_mode": False,
|
|
2840
|
+
"unclosed_alarm": False,
|
|
2841
|
+
"unlocked_alarm": False,
|
|
2842
|
+
"auto_lock_paused": False,
|
|
2843
|
+
"night_latch": False,
|
|
2844
|
+
},
|
|
2845
|
+
"-",
|
|
2846
|
+
"Lock Lite",
|
|
2847
|
+
SwitchbotModel.LOCK_LITE,
|
|
2848
|
+
),
|
|
2849
|
+
AdvTestCase(
|
|
2850
|
+
b"\xee\xf5\xe6\t\x8f\xe8\x11\x97\x08 ",
|
|
2851
|
+
b"o\x80d",
|
|
2852
|
+
{
|
|
2853
|
+
"sequence_number": 17,
|
|
2854
|
+
"battery": None,
|
|
2855
|
+
"calibration": True,
|
|
2856
|
+
"status": LockStatus.UNLOCKED,
|
|
2857
|
+
"update_from_secondary_lock": False,
|
|
2858
|
+
"door_open": True,
|
|
2859
|
+
"double_lock_mode": False,
|
|
2860
|
+
"unclosed_alarm": False,
|
|
2861
|
+
"unlocked_alarm": False,
|
|
2862
|
+
"auto_lock_paused": False,
|
|
2863
|
+
"night_latch": False,
|
|
2864
|
+
},
|
|
2865
|
+
"o",
|
|
2866
|
+
"Lock",
|
|
2867
|
+
SwitchbotModel.LOCK,
|
|
2868
|
+
),
|
|
2869
|
+
AdvTestCase(
|
|
2870
|
+
b"\xf7a\x07H\xe6\xe8:\x8a\x00d\x00\x00",
|
|
2871
|
+
b"$\x80d",
|
|
2872
|
+
{
|
|
2873
|
+
"sequence_number": 58,
|
|
2874
|
+
"battery": 100,
|
|
2875
|
+
"calibration": True,
|
|
2876
|
+
"status": LockStatus.LOCKED,
|
|
2877
|
+
"update_from_secondary_lock": False,
|
|
2878
|
+
"door_open": False,
|
|
2879
|
+
"door_open_from_secondary_lock": False,
|
|
2880
|
+
"double_lock_mode": False,
|
|
2881
|
+
"is_secondary_lock": False,
|
|
2882
|
+
"left_battery_compartment_alarm": 0,
|
|
2883
|
+
"right_battery_compartment_alarm": 0,
|
|
2884
|
+
"low_temperature_alarm": False,
|
|
2885
|
+
"manual_unlock_linkage": False,
|
|
2886
|
+
"unclosed_alarm": False,
|
|
2887
|
+
"unlocked_alarm": False,
|
|
2888
|
+
"auto_lock_paused": False,
|
|
2889
|
+
"night_latch": False,
|
|
2890
|
+
},
|
|
2891
|
+
"$",
|
|
2892
|
+
"Lock Pro",
|
|
2893
|
+
SwitchbotModel.LOCK_PRO,
|
|
2894
|
+
),
|
|
2895
|
+
AdvTestCase(
|
|
2896
|
+
b"\xb0\xe9\xfe\xb6j=%\x8204\x00\x04",
|
|
2897
|
+
b"\x00\x804\x00\x10\xa5\xb8",
|
|
2898
|
+
{
|
|
2899
|
+
"sequence_number": 37,
|
|
2900
|
+
"battery": 52,
|
|
2901
|
+
"calibration": True,
|
|
2902
|
+
"status": LockStatus.LOCKED,
|
|
2903
|
+
"update_from_secondary_lock": False,
|
|
2904
|
+
"door_open": True,
|
|
2905
|
+
"door_open_from_secondary_lock": True,
|
|
2906
|
+
"double_lock_mode": False,
|
|
2907
|
+
"is_secondary_lock": False,
|
|
2908
|
+
"manual_unlock_linkage": False,
|
|
2909
|
+
"unclosed_alarm": False,
|
|
2910
|
+
"unlocked_alarm": False,
|
|
2911
|
+
"auto_lock_paused": False,
|
|
2912
|
+
"night_latch": False,
|
|
2913
|
+
"power_alarm": False,
|
|
2914
|
+
"battery_status": 4,
|
|
2915
|
+
},
|
|
2916
|
+
b"\x00\x10\xa5\xb8",
|
|
2917
|
+
"Lock Ultra",
|
|
2918
|
+
SwitchbotModel.LOCK_ULTRA,
|
|
2919
|
+
),
|
|
2920
|
+
],
|
|
2921
|
+
)
|
|
2922
|
+
def test_lock_passive(test_case: AdvTestCase) -> None:
|
|
2923
|
+
"""Test lokc series with passive data."""
|
|
2924
|
+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
2925
|
+
adv_data = generate_advertisement_data(
|
|
2926
|
+
manufacturer_data={2409: test_case.manufacturer_data},
|
|
2927
|
+
rssi=-97,
|
|
2928
|
+
)
|
|
2929
|
+
result = parse_advertisement_data(ble_device, adv_data, test_case.modelName)
|
|
2930
|
+
assert result == SwitchBotAdvertisement(
|
|
2931
|
+
address="aa:bb:cc:dd:ee:ff",
|
|
2932
|
+
data={
|
|
2933
|
+
"rawAdvData": None,
|
|
2934
|
+
"data": test_case.data,
|
|
2935
|
+
"isEncrypted": False,
|
|
2936
|
+
"model": test_case.model,
|
|
2937
|
+
"modelFriendlyName": test_case.modelFriendlyName,
|
|
2938
|
+
"modelName": test_case.modelName,
|
|
2939
|
+
},
|
|
2940
|
+
device=ble_device,
|
|
2941
|
+
rssi=-97,
|
|
2942
|
+
active=False,
|
|
2943
|
+
)
|
|
2944
|
+
|
|
2945
|
+
|
|
2946
|
+
@pytest.mark.parametrize(
|
|
2947
|
+
"test_case",
|
|
2948
|
+
[
|
|
2949
|
+
AdvTestCase(
|
|
2950
|
+
None,
|
|
2951
|
+
b"-\x80d",
|
|
2952
|
+
{},
|
|
2953
|
+
"-",
|
|
2954
|
+
"Lock Lite",
|
|
2955
|
+
SwitchbotModel.LOCK_LITE,
|
|
2956
|
+
),
|
|
2957
|
+
AdvTestCase(
|
|
2958
|
+
None,
|
|
2959
|
+
b"o\x80d",
|
|
2960
|
+
{},
|
|
2961
|
+
"o",
|
|
2962
|
+
"Lock",
|
|
2963
|
+
SwitchbotModel.LOCK,
|
|
2964
|
+
),
|
|
2965
|
+
AdvTestCase(
|
|
2966
|
+
None,
|
|
2967
|
+
b"$\x80d",
|
|
2968
|
+
{},
|
|
2969
|
+
"$",
|
|
2970
|
+
"Lock Pro",
|
|
2971
|
+
SwitchbotModel.LOCK_PRO,
|
|
2972
|
+
),
|
|
2973
|
+
AdvTestCase(
|
|
2974
|
+
None,
|
|
2975
|
+
b"\x00\x804\x00\x10\xa5\xb8",
|
|
2976
|
+
{},
|
|
2977
|
+
b"\x00\x10\xa5\xb8",
|
|
2978
|
+
"Lock Ultra",
|
|
2979
|
+
SwitchbotModel.LOCK_ULTRA,
|
|
2980
|
+
),
|
|
2981
|
+
],
|
|
2982
|
+
)
|
|
2983
|
+
def test_lock_with_empty_data(test_case: AdvTestCase) -> None:
|
|
2984
|
+
"""Test lokc series with empty data."""
|
|
2985
|
+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
2986
|
+
adv_data = generate_advertisement_data(
|
|
2987
|
+
manufacturer_data={2409: test_case.manufacturer_data},
|
|
2988
|
+
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": test_case.service_data},
|
|
2989
|
+
rssi=-97,
|
|
2990
|
+
)
|
|
2991
|
+
result = parse_advertisement_data(ble_device, adv_data, test_case.modelName)
|
|
2992
|
+
assert result == SwitchBotAdvertisement(
|
|
2993
|
+
address="aa:bb:cc:dd:ee:ff",
|
|
2994
|
+
data={
|
|
2995
|
+
"rawAdvData": test_case.service_data,
|
|
2996
|
+
"data": {},
|
|
2997
|
+
"isEncrypted": False,
|
|
2998
|
+
"model": test_case.model,
|
|
2999
|
+
},
|
|
3000
|
+
device=ble_device,
|
|
3001
|
+
rssi=-97,
|
|
3002
|
+
active=True,
|
|
3003
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from switchbot import SwitchbotModel
|
|
4
|
+
from switchbot.devices import lock
|
|
5
|
+
|
|
6
|
+
from .test_adv_parser import generate_ble_device
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_device_for_command_testing(model: str):
|
|
10
|
+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
11
|
+
return lock.SwitchbotLock(
|
|
12
|
+
ble_device, "ff", "ffffffffffffffffffffffffffffffff", model=model
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize(
|
|
17
|
+
"model",
|
|
18
|
+
[
|
|
19
|
+
SwitchbotModel.LOCK,
|
|
20
|
+
SwitchbotModel.LOCK_LITE,
|
|
21
|
+
SwitchbotModel.LOCK_PRO,
|
|
22
|
+
SwitchbotModel.LOCK_ULTRA,
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_lock_init(model: str):
|
|
26
|
+
"""Test the initialization of the lock device."""
|
|
27
|
+
device = create_device_for_command_testing(model)
|
|
28
|
+
assert device._model == model
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"model",
|
|
33
|
+
[
|
|
34
|
+
SwitchbotModel.AIR_PURIFIER,
|
|
35
|
+
],
|
|
36
|
+
)
|
|
37
|
+
def test_lock_init_with_invalid_model(model: str):
|
|
38
|
+
"""Test that initializing with an invalid model raises ValueError."""
|
|
39
|
+
with pytest.raises(
|
|
40
|
+
ValueError, match="initializing SwitchbotLock with a non-lock model"
|
|
41
|
+
):
|
|
42
|
+
create_device_for_command_testing(model)
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"""Lock parser."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
|
|
7
|
-
from ..const.lock import LockStatus
|
|
8
|
-
|
|
9
|
-
_LOGGER = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def process_wolock(data: bytes | None, mfr_data: bytes | None) -> dict[str, bool | int]:
|
|
13
|
-
"""Process woLock services data."""
|
|
14
|
-
if mfr_data is None:
|
|
15
|
-
return {}
|
|
16
|
-
|
|
17
|
-
_LOGGER.debug("mfr_data: %s", mfr_data.hex())
|
|
18
|
-
if data:
|
|
19
|
-
_LOGGER.debug("data: %s", data.hex())
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
"battery": data[2] & 0b01111111 if data else None,
|
|
23
|
-
"calibration": bool(mfr_data[7] & 0b10000000),
|
|
24
|
-
"status": LockStatus((mfr_data[7] & 0b01110000) >> 4),
|
|
25
|
-
"update_from_secondary_lock": bool(mfr_data[7] & 0b00001000),
|
|
26
|
-
"door_open": bool(mfr_data[7] & 0b00000100),
|
|
27
|
-
"double_lock_mode": bool(mfr_data[8] & 0b10000000),
|
|
28
|
-
"unclosed_alarm": bool(mfr_data[8] & 0b00100000),
|
|
29
|
-
"unlocked_alarm": bool(mfr_data[8] & 0b00010000),
|
|
30
|
-
"auto_lock_paused": bool(mfr_data[8] & 0b00000010),
|
|
31
|
-
"night_latch": bool(mfr_data[9] & 0b00000001) if len(mfr_data) > 9 else False,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def process_wolock_pro(
|
|
36
|
-
data: bytes | None, mfr_data: bytes | None
|
|
37
|
-
) -> dict[str, bool | int]:
|
|
38
|
-
_LOGGER.debug("mfr_data: %s", mfr_data.hex())
|
|
39
|
-
if data:
|
|
40
|
-
_LOGGER.debug("data: %s", data.hex())
|
|
41
|
-
|
|
42
|
-
res = {
|
|
43
|
-
"battery": data[2] & 0b01111111 if data else None,
|
|
44
|
-
"calibration": bool(mfr_data[7] & 0b10000000),
|
|
45
|
-
"status": LockStatus((mfr_data[7] & 0b00111000) >> 3),
|
|
46
|
-
"door_open": bool(mfr_data[8] & 0b01100000),
|
|
47
|
-
# Double lock mode is not supported on Lock Pro
|
|
48
|
-
"update_from_secondary_lock": False,
|
|
49
|
-
"double_lock_mode": False,
|
|
50
|
-
"unclosed_alarm": bool(mfr_data[11] & 0b10000000),
|
|
51
|
-
"unlocked_alarm": bool(mfr_data[11] & 0b01000000),
|
|
52
|
-
"auto_lock_paused": bool(mfr_data[8] & 0b100000),
|
|
53
|
-
# Looks like night latch bit is not anymore in ADV
|
|
54
|
-
"night_latch": False,
|
|
55
|
-
}
|
|
56
|
-
_LOGGER.debug(res)
|
|
57
|
-
return res
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|