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.
Files changed (87) hide show
  1. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PKG-INFO +1 -1
  2. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/PKG-INFO +1 -1
  3. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/SOURCES.txt +1 -0
  4. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/setup.py +1 -1
  5. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parser.py +13 -1
  6. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/hub3.py +1 -1
  7. pyswitchbot-0.64.0/switchbot/adv_parsers/lock.py +85 -0
  8. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/__init__.py +2 -0
  9. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/lock.py +1 -0
  10. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/lock.py +16 -3
  11. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_adv_parser.py +309 -250
  12. pyswitchbot-0.64.0/tests/test_lock.py +42 -0
  13. pyswitchbot-0.63.0/switchbot/adv_parsers/lock.py +0 -57
  14. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/LICENSE +0 -0
  15. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/MANIFEST.in +0 -0
  16. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
  17. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/requires.txt +0 -0
  18. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/PySwitchbot.egg-info/top_level.txt +0 -0
  19. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/README.md +0 -0
  20. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/pyproject.toml +0 -0
  21. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/setup.cfg +0 -0
  22. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/__init__.py +0 -0
  23. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/__init__.py +0 -0
  24. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/air_purifier.py +0 -0
  25. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
  26. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/bot.py +0 -0
  27. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/bulb.py +0 -0
  28. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
  29. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/contact.py +0 -0
  30. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/curtain.py +0 -0
  31. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/fan.py +0 -0
  32. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/hub2.py +0 -0
  33. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/hubmini_matter.py +0 -0
  34. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/humidifier.py +0 -0
  35. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/keypad.py +0 -0
  36. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/leak.py +0 -0
  37. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/light_strip.py +0 -0
  38. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/meter.py +0 -0
  39. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/motion.py +0 -0
  40. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/plug.py +0 -0
  41. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/relay_switch.py +0 -0
  42. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/remote.py +0 -0
  43. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/roller_shade.py +0 -0
  44. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/adv_parsers/vacuum.py +0 -0
  45. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/api_config.py +0 -0
  46. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/air_purifier.py +0 -0
  47. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/evaporative_humidifier.py +0 -0
  48. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/fan.py +0 -0
  49. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/hub2.py +0 -0
  50. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/const/hub3.py +0 -0
  51. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/__init__.py +0 -0
  52. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/air_purifier.py +0 -0
  53. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/base_cover.py +0 -0
  54. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/base_light.py +0 -0
  55. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/blind_tilt.py +0 -0
  56. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/bot.py +0 -0
  57. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/bulb.py +0 -0
  58. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/ceiling_light.py +0 -0
  59. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/contact.py +0 -0
  60. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/curtain.py +0 -0
  61. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/device.py +0 -0
  62. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/evaporative_humidifier.py +0 -0
  63. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/fan.py +0 -0
  64. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/humidifier.py +0 -0
  65. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/keypad.py +0 -0
  66. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/light_strip.py +0 -0
  67. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/meter.py +0 -0
  68. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/motion.py +0 -0
  69. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/plug.py +0 -0
  70. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/relay_switch.py +0 -0
  71. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/roller_shade.py +0 -0
  72. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/devices/vacuum.py +0 -0
  73. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/discovery.py +0 -0
  74. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/enum.py +0 -0
  75. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/helpers.py +0 -0
  76. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/switchbot/models.py +0 -0
  77. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_air_purifier.py +0 -0
  78. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_base_cover.py +0 -0
  79. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_blind_tilt.py +0 -0
  80. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_curtain.py +0 -0
  81. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_evaporative_humidifier.py +0 -0
  82. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_fan.py +0 -0
  83. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_hub2.py +0 -0
  84. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_hub3.py +0 -0
  85. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_relay_switch.py +0 -0
  86. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_roller_shade.py +0 -0
  87. {pyswitchbot-0.63.0 → pyswitchbot-0.64.0}/tests/test_vacuum.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySwitchbot
3
- Version: 0.63.0
3
+ Version: 0.64.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/sblibs/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySwitchbot
3
- Version: 0.63.0
3
+ Version: 0.64.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/sblibs/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -78,6 +78,7 @@ tests/test_evaporative_humidifier.py
78
78
  tests/test_fan.py
79
79
  tests/test_hub2.py
80
80
  tests/test_hub3.py
81
+ tests/test_lock.py
81
82
  tests/test_relay_switch.py
82
83
  tests/test_roller_shade.py
83
84
  tests/test_vacuum.py
@@ -20,7 +20,7 @@ setup(
20
20
  "cryptography>=39.0.0",
21
21
  "pyOpenSSL>=23.0.0",
22
22
  ],
23
- version="0.63.0",
23
+ version="0.64.0",
24
24
  description="A library to communicate with Switchbot",
25
25
  long_description=long_description,
26
26
  long_description_content_type="text/markdown",
@@ -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 = {
@@ -1,4 +1,4 @@
1
- """Air Purifier adv parser."""
1
+ """Hub3 adv parser."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -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
@@ -76,6 +76,8 @@ class SwitchbotModel(StrEnum):
76
76
  AIR_PURIFIER = "Air Purifier"
77
77
  AIR_PURIFIER_TABLE = "Air Purifier Table"
78
78
  HUB3 = "Hub3"
79
+ LOCK_ULTRA = "Lock Ultra"
80
+ LOCK_LITE = "Lock Lite"
79
81
 
80
82
 
81
83
  __all__ = [
@@ -11,3 +11,4 @@ class LockStatus(Enum):
11
11
  LOCKING_STOP = 4 # LOCKING_BLOCKED
12
12
  UNLOCKING_STOP = 5 # UNLOCKING_BLOCKED
13
13
  NOT_FULLY_LOCKED = 6 # LATCH_LOCKED - Only EU lock type
14
+ HALF_LOCKED = 7 # Only Lock2 EU lock type
@@ -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 (SwitchbotModel.LOCK, SwitchbotModel.LOCK_PRO):
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 AirPurifierTestCase
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
- AirPurifierTestCase(
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
- AirPurifierTestCase(
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
- AirPurifierTestCase(
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
- AirPurifierTestCase(
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
- AirPurifierTestCase(
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
- AirPurifierTestCase(
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: AirPurifierTestCase) -> None:
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