Skip to content

Commit ab7dad7

Browse files
Add lock series products (#340)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent eda622b commit ab7dad7

File tree

9 files changed

+433
-276
lines changed

9 files changed

+433
-276
lines changed

switchbot/adv_parser.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from .adv_parsers.keypad import process_wokeypad
2626
from .adv_parsers.leak import process_leak
2727
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
2929
from .adv_parsers.meter import process_wosensorth, process_wosensorth_c
3030
from .adv_parsers.motion import process_wopresence
3131
from .adv_parsers.plug import process_woplugmini
@@ -300,6 +300,18 @@ class SwitchbotSupportedType(TypedDict):
300300
"func": process_hub3,
301301
"manufacturer_id": 2409,
302302
},
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+
},
303315
}
304316

305317
_SWITCHBOT_MODEL_TO_CHAR = {

switchbot/adv_parsers/hub3.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Air Purifier adv parser."""
1+
"""Hub3 adv parser."""
22

33
from __future__ import annotations
44

switchbot/adv_parsers/lock.py

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
def process_wolock(data: bytes | None, mfr_data: bytes | None) -> dict[str, bool | int]:
13-
"""Process woLock services data."""
13+
"""Support for lock and lock lite process data."""
1414
if mfr_data is None:
1515
return {}
1616

@@ -19,6 +19,7 @@ def process_wolock(data: bytes | None, mfr_data: bytes | None) -> dict[str, bool
1919
_LOGGER.debug("data: %s", data.hex())
2020

2121
return {
22+
"sequence_number": mfr_data[6],
2223
"battery": data[2] & 0b01111111 if data else None,
2324
"calibration": bool(mfr_data[7] & 0b10000000),
2425
"status": LockStatus((mfr_data[7] & 0b01110000) >> 4),
@@ -32,26 +33,53 @@ def process_wolock(data: bytes | None, mfr_data: bytes | None) -> dict[str, bool
3233
}
3334

3435

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())
36+
def parse_common_data(mfr_data: bytes | None) -> dict[str, bool | int]:
37+
if mfr_data is None:
38+
return {}
4139

42-
res = {
43-
"battery": data[2] & 0b01111111 if data else None,
40+
return {
41+
"sequence_number": mfr_data[6],
4442
"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,
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),
5052
"unclosed_alarm": bool(mfr_data[11] & 0b10000000),
5153
"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
5454
"night_latch": False,
5555
}
56-
_LOGGER.debug(res)
57-
return res
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

switchbot/const/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ class SwitchbotModel(StrEnum):
7676
AIR_PURIFIER = "Air Purifier"
7777
AIR_PURIFIER_TABLE = "Air Purifier Table"
7878
HUB3 = "Hub3"
79+
LOCK_ULTRA = "Lock Ultra"
80+
LOCK_LITE = "Lock Lite"
7981

8082

8183
__all__ = [

switchbot/const/lock.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ class LockStatus(Enum):
1111
LOCKING_STOP = 4 # LOCKING_BLOCKED
1212
UNLOCKING_STOP = 5 # UNLOCKING_BLOCKED
1313
NOT_FULLY_LOCKED = 6 # LATCH_LOCKED - Only EU lock type
14+
HALF_LOCKED = 7 # Only Lock2 EU lock type

switchbot/devices/lock.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,32 @@
1010

1111
from ..const import SwitchbotModel
1212
from ..const.lock import LockStatus
13-
from .device import SwitchbotEncryptedDevice
13+
from .device import SwitchbotEncryptedDevice, SwitchbotSequenceDevice
1414

1515
COMMAND_HEADER = "57"
1616
COMMAND_LOCK_INFO = {
1717
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0f4f8101",
18+
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0f4f8101",
1819
SwitchbotModel.LOCK_PRO: f"{COMMAND_HEADER}0f4f8102",
20+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4f8102",
1921
}
2022
COMMAND_UNLOCK = {
2123
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0f4e01011080",
24+
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0f4e01011080",
2225
SwitchbotModel.LOCK_PRO: f"{COMMAND_HEADER}0f4e0101000080",
26+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4e0101000080",
2327
}
2428
COMMAND_UNLOCK_WITHOUT_UNLATCH = {
2529
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0f4e010110a0",
30+
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0f4e010110a0",
2631
SwitchbotModel.LOCK_PRO: f"{COMMAND_HEADER}0f4e01010000a0",
32+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4e01010000a0",
2733
}
2834
COMMAND_LOCK = {
2935
SwitchbotModel.LOCK: f"{COMMAND_HEADER}0f4e01011000",
36+
SwitchbotModel.LOCK_LITE: f"{COMMAND_HEADER}0f4e01011000",
3037
SwitchbotModel.LOCK_PRO: f"{COMMAND_HEADER}0f4e0101000000",
38+
SwitchbotModel.LOCK_ULTRA: f"{COMMAND_HEADER}0f4e0101000000",
3139
}
3240
COMMAND_ENABLE_NOTIFICATIONS = f"{COMMAND_HEADER}0e01001e00008101"
3341
COMMAND_DISABLE_NOTIFICATIONS = f"{COMMAND_HEADER}0e00"
@@ -44,7 +52,7 @@
4452
# The return value of the command is 6 when the command is successful but the battery is low.
4553

4654

47-
class SwitchbotLock(SwitchbotEncryptedDevice):
55+
class SwitchbotLock(SwitchbotSequenceDevice, SwitchbotEncryptedDevice):
4856
"""Representation of a Switchbot Lock."""
4957

5058
def __init__(
@@ -56,7 +64,12 @@ def __init__(
5664
model: SwitchbotModel = SwitchbotModel.LOCK,
5765
**kwargs: Any,
5866
) -> 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+
):
6073
raise ValueError("initializing SwitchbotLock with a non-lock model")
6174
self._notifications_enabled: bool = False
6275
super().__init__(device, key_id, encryption_key, model, interface, **kwargs)

tests/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55

66
@dataclass
7-
class AirPurifierTestCase:
8-
manufacturer_data: bytes
7+
class AdvTestCase:
8+
manufacturer_data: bytes | None
99
service_data: bytes
1010
data: dict
11-
model: str
11+
model: str | bytes
1212
modelFriendlyName: str
1313
modelName: SwitchbotModel

0 commit comments

Comments
 (0)