PySwitchbot 0.63.0__tar.gz → 0.64.1__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.1}/PKG-INFO +1 -1
  2. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/PySwitchbot.egg-info/PKG-INFO +1 -1
  3. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/PySwitchbot.egg-info/SOURCES.txt +1 -0
  4. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/setup.py +1 -1
  5. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parser.py +13 -1
  6. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/blind_tilt.py +1 -1
  7. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/hub3.py +1 -1
  8. pyswitchbot-0.64.1/switchbot/adv_parsers/lock.py +85 -0
  9. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/const/__init__.py +2 -0
  10. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/const/lock.py +1 -0
  11. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/lock.py +16 -3
  12. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_adv_parser.py +395 -250
  13. pyswitchbot-0.64.1/tests/test_lock.py +42 -0
  14. pyswitchbot-0.63.0/switchbot/adv_parsers/lock.py +0 -57
  15. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/LICENSE +0 -0
  16. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/MANIFEST.in +0 -0
  17. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/PySwitchbot.egg-info/dependency_links.txt +0 -0
  18. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/PySwitchbot.egg-info/requires.txt +0 -0
  19. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/PySwitchbot.egg-info/top_level.txt +0 -0
  20. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/README.md +0 -0
  21. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/pyproject.toml +0 -0
  22. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/setup.cfg +0 -0
  23. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/__init__.py +0 -0
  24. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/__init__.py +0 -0
  25. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/air_purifier.py +0 -0
  26. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/bot.py +0 -0
  27. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/bulb.py +0 -0
  28. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/ceiling_light.py +0 -0
  29. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/contact.py +0 -0
  30. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/curtain.py +0 -0
  31. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/fan.py +0 -0
  32. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/hub2.py +0 -0
  33. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/hubmini_matter.py +0 -0
  34. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/humidifier.py +0 -0
  35. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/keypad.py +0 -0
  36. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/leak.py +0 -0
  37. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/light_strip.py +0 -0
  38. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/meter.py +0 -0
  39. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/motion.py +0 -0
  40. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/plug.py +0 -0
  41. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/relay_switch.py +0 -0
  42. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/remote.py +0 -0
  43. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/roller_shade.py +0 -0
  44. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/adv_parsers/vacuum.py +0 -0
  45. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/api_config.py +0 -0
  46. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/const/air_purifier.py +0 -0
  47. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/const/evaporative_humidifier.py +0 -0
  48. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/const/fan.py +0 -0
  49. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/const/hub2.py +0 -0
  50. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/const/hub3.py +0 -0
  51. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/__init__.py +0 -0
  52. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/air_purifier.py +0 -0
  53. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/base_cover.py +0 -0
  54. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/base_light.py +0 -0
  55. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/blind_tilt.py +0 -0
  56. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/bot.py +0 -0
  57. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/bulb.py +0 -0
  58. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/ceiling_light.py +0 -0
  59. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/contact.py +0 -0
  60. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/curtain.py +0 -0
  61. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/device.py +0 -0
  62. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/evaporative_humidifier.py +0 -0
  63. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/fan.py +0 -0
  64. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/humidifier.py +0 -0
  65. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/keypad.py +0 -0
  66. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/light_strip.py +0 -0
  67. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/meter.py +0 -0
  68. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/motion.py +0 -0
  69. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/plug.py +0 -0
  70. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/relay_switch.py +0 -0
  71. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/roller_shade.py +0 -0
  72. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/devices/vacuum.py +0 -0
  73. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/discovery.py +0 -0
  74. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/enum.py +0 -0
  75. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/helpers.py +0 -0
  76. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/switchbot/models.py +0 -0
  77. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_air_purifier.py +0 -0
  78. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_base_cover.py +0 -0
  79. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_blind_tilt.py +0 -0
  80. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_curtain.py +0 -0
  81. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_evaporative_humidifier.py +0 -0
  82. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_fan.py +0 -0
  83. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_hub2.py +0 -0
  84. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_hub3.py +0 -0
  85. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_relay_switch.py +0 -0
  86. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/tests/test_roller_shade.py +0 -0
  87. {pyswitchbot-0.63.0 → pyswitchbot-0.64.1}/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.1
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.1
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.1",
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 = {
@@ -14,7 +14,7 @@ def process_woblindtilt(
14
14
 
15
15
  _tilt = max(min(device_data[2] & 0b01111111, 100), 0)
16
16
  _in_motion = bool(device_data[2] & 0b10000000)
17
- _light_level = (device_data[1] >> 4) & 0b00001111
17
+ _light_level = (device_data[3] >> 4) & 0b00001111
18
18
  _calibrated = bool(device_data[1] & 0b00000001)
19
19
 
20
20
  return {
@@ -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)