Skip to content

Commit 8e38b6a

Browse files
zerzhangpre-commit-ci[bot]bdraco
authored
Add support for relay switch series (#347)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston <[email protected]>
1 parent 857bb0c commit 8e38b6a

File tree

11 files changed

+1089
-189
lines changed

11 files changed

+1089
-189
lines changed

switchbot/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from .devices.light_strip import SwitchbotLightStrip
3636
from .devices.lock import SwitchbotLock
3737
from .devices.plug import SwitchbotPlugMini
38-
from .devices.relay_switch import SwitchbotRelaySwitch
38+
from .devices.relay_switch import SwitchbotRelaySwitch, SwitchbotRelaySwitch2PM
3939
from .devices.roller_shade import SwitchbotRollerShade
4040
from .devices.vacuum import SwitchbotVacuum
4141
from .discovery import GetSwitchbotDevices
@@ -74,6 +74,7 @@
7474
"SwitchbotPlugMini",
7575
"SwitchbotPlugMini",
7676
"SwitchbotRelaySwitch",
77+
"SwitchbotRelaySwitch2PM",
7778
"SwitchbotRollerShade",
7879
"SwitchbotSupportedType",
7980
"SwitchbotSupportedType",

switchbot/adv_parser.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030
from .adv_parsers.motion import process_wopresence
3131
from .adv_parsers.plug import process_woplugmini
3232
from .adv_parsers.relay_switch import (
33-
process_worelay_switch_1,
34-
process_worelay_switch_1pm,
33+
process_garage_door_opener,
34+
process_relay_switch_2pm,
35+
process_relay_switch_common_data,
3536
)
3637
from .adv_parsers.remote import process_woremote
3738
from .adv_parsers.roller_shade import process_worollershade
@@ -207,13 +208,13 @@ class SwitchbotSupportedType(TypedDict):
207208
"<": {
208209
"modelName": SwitchbotModel.RELAY_SWITCH_1PM,
209210
"modelFriendlyName": "Relay Switch 1PM",
210-
"func": process_worelay_switch_1pm,
211+
"func": process_relay_switch_common_data,
211212
"manufacturer_id": 2409,
212213
},
213214
";": {
214215
"modelName": SwitchbotModel.RELAY_SWITCH_1,
215216
"modelFriendlyName": "Relay Switch 1",
216-
"func": process_worelay_switch_1,
217+
"func": process_relay_switch_common_data,
217218
"manufacturer_id": 2409,
218219
},
219220
"b": {
@@ -312,6 +313,18 @@ class SwitchbotSupportedType(TypedDict):
312313
"func": process_lock2,
313314
"manufacturer_id": 2409,
314315
},
316+
">": {
317+
"modelName": SwitchbotModel.GARAGE_DOOR_OPENER,
318+
"modelFriendlyName": "Garage Door Opener",
319+
"func": process_garage_door_opener,
320+
"manufacturer_id": 2409,
321+
},
322+
"=": {
323+
"modelName": SwitchbotModel.RELAY_SWITCH_2PM,
324+
"modelFriendlyName": "Relay Switch 2PM",
325+
"func": process_relay_switch_2pm,
326+
"manufacturer_id": 2409,
327+
},
315328
}
316329

317330
_SWITCHBOT_MODEL_TO_CHAR = {

switchbot/adv_parsers/plug.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
from ..helpers import parse_power_data
6+
57

68
def process_woplugmini(
79
data: bytes | None, mfr_data: bytes | None
@@ -13,5 +15,5 @@ def process_woplugmini(
1315
"switchMode": True,
1416
"isOn": mfr_data[7] == 0x80,
1517
"wifi_rssi": -mfr_data[9],
16-
"power": (((mfr_data[10] << 8) + mfr_data[11]) & 0x7FFF) / 10, # W
18+
"power": parse_power_data(mfr_data, 10, 10.0, 0x7FFF), # W
1719
}

switchbot/adv_parsers/relay_switch.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,47 @@
22

33
from __future__ import annotations
44

5+
from typing import Any
56

6-
def process_worelay_switch_1pm(
7+
8+
def process_relay_switch_common_data(
79
data: bytes | None, mfr_data: bytes | None
8-
) -> dict[str, bool | int]:
9-
"""Process WoStrip services data."""
10+
) -> dict[str, Any]:
11+
"""Process relay switch 1 and 1PM common data."""
1012
if mfr_data is None:
1113
return {}
1214
return {
1315
"switchMode": True, # for compatibility, useless
1416
"sequence_number": mfr_data[6],
1517
"isOn": bool(mfr_data[7] & 0b10000000),
16-
"power": ((mfr_data[10] << 8) + mfr_data[11]) / 10,
17-
"voltage": 0,
18-
"current": 0,
1918
}
2019

2120

22-
def process_worelay_switch_1(
21+
def process_garage_door_opener(
22+
data: bytes | None, mfr_data: bytes | None
23+
) -> dict[str, Any]:
24+
"""Process garage door opener services data."""
25+
if mfr_data is None:
26+
return {}
27+
common_data = process_relay_switch_common_data(data, mfr_data)
28+
common_data["door_open"] = not bool(mfr_data[7] & 0b00100000)
29+
return common_data
30+
31+
32+
def process_relay_switch_2pm(
2333
data: bytes | None, mfr_data: bytes | None
24-
) -> dict[str, bool | int]:
25-
"""Process WoStrip services data."""
34+
) -> dict[int, dict[str, Any]]:
35+
"""Process Relay Switch 2PM services data."""
2636
if mfr_data is None:
2737
return {}
38+
2839
return {
29-
"switchMode": True, # for compatibility, useless
30-
"sequence_number": mfr_data[6],
31-
"isOn": bool(mfr_data[7] & 0b10000000),
40+
1: {
41+
**process_relay_switch_common_data(data, mfr_data),
42+
},
43+
2: {
44+
"switchMode": True, # for compatibility, useless
45+
"sequence_number": mfr_data[6],
46+
"isOn": bool(mfr_data[7] & 0b01000000),
47+
},
3248
}

switchbot/const/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class SwitchbotModel(StrEnum):
8383
HUB3 = "Hub3"
8484
LOCK_ULTRA = "Lock Ultra"
8585
LOCK_LITE = "Lock Lite"
86+
GARAGE_DOOR_OPENER = "Garage Door Opener"
87+
RELAY_SWITCH_2PM = "Relay Switch 2PM"
8688

8789

8890
__all__ = [

switchbot/devices/device.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ def _merge_data(old_data: dict[str, Any], new_data: dict[str, Any]) -> dict[str,
116116
"""Merge data but only add None keys if they are missing."""
117117
merged = old_data.copy()
118118
for key, value in new_data.items():
119-
if value is not None or key not in old_data:
119+
if isinstance(value, dict) and isinstance(old_data.get(key), dict):
120+
merged[key] = _merge_data(old_data[key], value)
121+
elif value is not None or key not in old_data:
120122
merged[key] = value
121123
return merged
122124

@@ -538,7 +540,7 @@ def _override_state(self, state: dict[str, Any]) -> None:
538540
self._override_adv_data.update(state)
539541
self._update_parsed_data(state)
540542

541-
def _get_adv_value(self, key: str) -> Any:
543+
def _get_adv_value(self, key: str, channel: int | None = None) -> Any:
542544
"""Return value from advertisement data."""
543545
if self._override_adv_data and key in self._override_adv_data:
544546
_LOGGER.debug(
@@ -550,6 +552,8 @@ def _get_adv_value(self, key: str) -> Any:
550552
return self._override_adv_data[key]
551553
if not self._sb_adv_data:
552554
return None
555+
if channel is not None:
556+
return self._sb_adv_data.data["data"].get(channel, {}).get(key)
553557
return self._sb_adv_data.data["data"].get(key)
554558

555559
def get_battery_percent(self) -> Any:

0 commit comments

Comments
 (0)