Skip to content

Commit 89b9313

Browse files
authored
Add support for passive curtain data (#160)
1 parent a495a75 commit 89b9313

File tree

4 files changed

+49
-22
lines changed

4 files changed

+49
-22
lines changed

switchbot/adv_parser.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class SwitchbotSupportedType(TypedDict):
3838
modelName: SwitchbotModel
3939
modelFriendlyName: str
4040
func: Callable[[bytes, bytes | None], dict[str, bool | int]]
41+
manufacturer_id: int | None
42+
manufacturer_data_length: int | None
4143

4244

4345
SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
@@ -73,12 +75,7 @@ class SwitchbotSupportedType(TypedDict):
7375
"modelName": SwitchbotModel.CURTAIN,
7476
"modelFriendlyName": "Curtain",
7577
"func": process_wocurtain,
76-
"service_uuids": {
77-
"00001800-0000-1000-8000-00805f9b34fb",
78-
"00001801-0000-1000-8000-00805f9b34fb",
79-
"cba20d00-224d-11e6-9fb8-0002a5d5c51b",
80-
},
81-
"manufacturer_id": 89,
78+
"manufacturer_id": 2409,
8279
},
8380
"i": {
8481
"modelName": SwitchbotModel.METER,
@@ -166,7 +163,6 @@ def parse_advertisement_data(
166163

167164
try:
168165
data = _parse_data(
169-
"".join(sorted(advertisement_data.service_uuids)),
170166
_service_data,
171167
_mfr_data,
172168
_mfr_id,
@@ -186,7 +182,6 @@ def parse_advertisement_data(
186182

187183
@lru_cache(maxsize=128)
188184
def _parse_data(
189-
_service_uuids_str: str,
190185
_service_data: bytes | None,
191186
_mfr_data: bytes | None,
192187
_mfr_id: int | None = None,
@@ -199,15 +194,10 @@ def _parse_data(
199194
_model = _SWITCHBOT_MODEL_TO_CHAR[_switchbot_model]
200195

201196
if not _model and _mfr_id and _mfr_id in MODELS_BY_MANUFACTURER_DATA:
202-
_service_uuids = set(_service_uuids_str.split(","))
203197
for model_chr, model_data in MODELS_BY_MANUFACTURER_DATA[_mfr_id]:
204198
if model_data.get("manufacturer_data_length") == len(_mfr_data):
205199
_model = model_chr
206200
break
207-
service_uuids = model_data.get("service_uuids", set())
208-
if service_uuids and service_uuids.intersection(_service_uuids):
209-
_model = model_chr
210-
break
211201

212202
if not _model:
213203
return None

switchbot/adv_parsers/curtain.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,23 @@ def process_wocurtain(
66
data: bytes | None, mfr_data: bytes | None, reverse: bool = True
77
) -> dict[str, bool | int]:
88
"""Process woCurtain/Curtain services data."""
9-
if data is None:
9+
if mfr_data and len(mfr_data) >= 11:
10+
device_data = mfr_data[8:11]
11+
elif data:
12+
device_data = data[3:6]
13+
else:
1014
return {}
1115

12-
_position = max(min(data[3] & 0b01111111, 100), 0)
16+
_position = max(min(device_data[0] & 0b01111111, 100), 0)
17+
_in_motion = bool(device_data[0] & 0b10000000)
18+
_light_level = (device_data[1] >> 4) & 0b00001111
19+
_device_chain = device_data[1] & 0b00000111
1320

1421
return {
15-
"calibration": bool(data[1] & 0b01000000),
16-
"battery": data[2] & 0b01111111,
17-
"inMotion": bool(data[3] & 0b10000000),
22+
"calibration": bool(data[1] & 0b01000000) if data else None,
23+
"battery": data[2] & 0b01111111 if data else None,
24+
"inMotion": _in_motion,
1825
"position": (100 - _position) if reverse else _position,
19-
"lightLevel": (data[4] >> 4) & 0b00001111,
20-
"deviceChain": data[4] & 0b00000111,
21-
}
26+
"lightLevel": _light_level,
27+
"deviceChain": _device_chain,
28+
}

switchbot/adv_parsers/motion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def process_wopresence(
2525
sense_distance = (data[5] & 0b00001100) >> 2
2626
light_intensity = data[5] & 0b00000011
2727
is_light = bool(data[5] & 0b00000010)
28-
if mfr_data:
28+
if mfr_data and len(mfr_data) >= 8:
2929
motion_detected = bool(mfr_data[7] & 0b01000000)
3030
is_light = bool(mfr_data[7] & 0b00100000)
3131

tests/test_adv_parser.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,36 @@ def test_parse_advertisement_data_curtain():
5757
)
5858

5959

60+
def test_parse_advertisement_data_curtain_passive():
61+
"""Test parse_advertisement_data for curtain passive."""
62+
ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
63+
adv_data = generate_advertisement_data(
64+
manufacturer_data={2409: b"\xe7\xabF\xac\x8f\x92|\x0f\x00\x11\x04"},
65+
service_data={},
66+
rssi=-80,
67+
)
68+
result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.CURTAIN)
69+
assert result == SwitchBotAdvertisement(
70+
address="aa:bb:cc:dd:ee:ff",
71+
data={
72+
"rawAdvData": None,
73+
"data": {
74+
"calibration": None,
75+
"battery": None,
76+
"inMotion": False,
77+
"position": 100,
78+
"lightLevel": 1,
79+
"deviceChain": 1,
80+
},
81+
"isEncrypted": False,
82+
"model": "c",
83+
"modelFriendlyName": "Curtain",
84+
"modelName": SwitchbotModel.CURTAIN,
85+
},
86+
device=ble_device,
87+
rssi=-80,
88+
)
89+
6090
def test_parse_advertisement_data_curtain_position_zero():
6191
"""Test parse_advertisement_data for curtain position zero."""
6292
ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")

0 commit comments

Comments
 (0)