Skip to content

Commit 8c38ab8

Browse files
authored
Support Curtain 3 (#218)
1 parent 57feb86 commit 8c38ab8

File tree

4 files changed

+81
-3
lines changed

4 files changed

+81
-3
lines changed

switchbot/adv_parser.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ class SwitchbotSupportedType(TypedDict):
7171
"manufacturer_id": 2409,
7272
"manufacturer_data_length": 16,
7373
},
74+
"{": {
75+
"modelName": SwitchbotModel.CURTAIN,
76+
"modelFriendlyName": "Curtain 3",
77+
"func": process_wocurtain,
78+
"manufacturer_id": 2409,
79+
},
7480
"c": {
7581
"modelName": SwitchbotModel.CURTAIN,
7682
"modelFriendlyName": "Curtain",

switchbot/adv_parsers/curtain.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ 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 mfr_data and len(mfr_data) >= 11:
9+
if mfr_data and len(mfr_data) >= 12: # Curtain 3
1010
device_data = mfr_data[8:11]
11+
battery_data = mfr_data[12]
12+
elif mfr_data and len(mfr_data) >= 11:
13+
device_data = mfr_data[8:11]
14+
battery_data = data[2] if data else None
1115
elif data:
1216
device_data = data[3:6]
17+
battery_data = data[2]
1318
else:
1419
return {}
1520

@@ -20,7 +25,7 @@ def process_wocurtain(
2025

2126
return {
2227
"calibration": bool(data[1] & 0b01000000) if data else None,
23-
"battery": data[2] & 0b01111111 if data else None,
28+
"battery": battery_data & 0b01111111 if battery_data is not None else None,
2429
"inMotion": _in_motion,
2530
"position": (100 - _position) if reverse else _position,
2631
"lightLevel": _light_level,

switchbot/discovery.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ async def get_curtains(self) -> dict[str, SwitchBotAdvertisement]:
9393
"""Return all WoCurtain/Curtains devices with services data."""
9494
regular_curtains = await self._get_devices_by_model("c")
9595
pairing_curtains = await self._get_devices_by_model("C")
96-
return {**regular_curtains, **pairing_curtains}
96+
regular_curtains3 = await self._get_devices_by_model("{")
97+
pairing_curtains3 = await self._get_devices_by_model("[")
98+
return {**regular_curtains, **pairing_curtains, **regular_curtains3, **pairing_curtains3}
9799

98100
async def get_bots(self) -> dict[str, SwitchBotAdvertisement]:
99101
"""Return all WoHand/Bot devices with services data."""

tests/test_adv_parser.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,71 @@ def test_parse_advertisement_data_curtain_fully_open():
322322
)
323323

324324

325+
def test_parse_advertisement_data_curtain3():
326+
"""Test parse_advertisement_data for curtain 3."""
327+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
328+
adv_data = generate_advertisement_data(
329+
manufacturer_data={2409: b"\xaa\xbb\xcc\xdd\xee\xff\xf7\x07\x00\x11\x04\x00\x49"},
330+
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"{\xc0\x49\x00\x11\x04"},
331+
rssi=-80,
332+
)
333+
334+
result = parse_advertisement_data(ble_device, adv_data)
335+
assert result == SwitchBotAdvertisement(
336+
address="aa:bb:cc:dd:ee:ff",
337+
data={
338+
"rawAdvData": b"{\xc0\x49\x00\x11\x04",
339+
"data": {
340+
"calibration": True,
341+
"battery": 73,
342+
"inMotion": False,
343+
"position": 100,
344+
"lightLevel": 1,
345+
"deviceChain": 1,
346+
},
347+
"isEncrypted": False,
348+
"model": "{",
349+
"modelFriendlyName": "Curtain 3",
350+
"modelName": SwitchbotModel.CURTAIN,
351+
},
352+
device=ble_device,
353+
rssi=-80,
354+
active=True,
355+
)
356+
357+
358+
def test_parse_advertisement_data_curtain3_passive():
359+
"""Test parse_advertisement_data for curtain passive."""
360+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
361+
adv_data = generate_advertisement_data(
362+
manufacturer_data={2409: b"\xaa\xbb\xcc\xdd\xee\xff\xf7\x07\x00\x11\x04\x00\x49"},
363+
service_data={},
364+
rssi=-80,
365+
)
366+
result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.CURTAIN)
367+
assert result == SwitchBotAdvertisement(
368+
address="aa:bb:cc:dd:ee:ff",
369+
data={
370+
"rawAdvData": None,
371+
"data": {
372+
"calibration": None,
373+
"battery": 73,
374+
"inMotion": False,
375+
"position": 100,
376+
"lightLevel": 1,
377+
"deviceChain": 1,
378+
},
379+
"isEncrypted": False,
380+
"model": "c",
381+
"modelFriendlyName": "Curtain",
382+
"modelName": SwitchbotModel.CURTAIN,
383+
},
384+
device=ble_device,
385+
rssi=-80,
386+
active=False,
387+
)
388+
389+
325390
def test_parse_advertisement_data_contact():
326391
"""Test parse_advertisement_data for the contact sensor."""
327392
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")

0 commit comments

Comments
 (0)