Skip to content

Commit e1894f0

Browse files
authored
Add manufacturer_data parser for contact sensor (#152)
1 parent 94a86a2 commit e1894f0

File tree

2 files changed

+158
-6
lines changed

2 files changed

+158
-6
lines changed

switchbot/adv_parsers/contact.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,27 @@
44

55
def process_wocontact(data: bytes, mfr_data: bytes | None) -> dict[str, bool | int]:
66
"""Process woContact Sensor services data."""
7+
battery = data[2] & 0b01111111
8+
tested = bool(data[1] & 0b10000000)
79
contact_timeout = data[3] & 0b00000100 == 0b00000100
8-
contact_open = data[3] & 0b00000010 == 0b00000010
10+
11+
if mfr_data and len(mfr_data) >= 13:
12+
motion_detected = bool(mfr_data[7] & 0b10000000)
13+
contact_open = bool(mfr_data[7] & 0b00010000)
14+
button_count = mfr_data[12] & 0b00001111
15+
is_light = bool(mfr_data[7] & 0b01000000)
16+
else:
17+
motion_detected = bool(data[1] & 0b01000000)
18+
contact_open = data[3] & 0b00000010 == 0b00000010
19+
button_count = data[8] & 0b00001111
20+
is_light = bool(data[3] & 0b00000001)
21+
922
return {
10-
"tested": bool(data[1] & 0b10000000),
11-
"motion_detected": bool(data[1] & 0b01000000),
12-
"battery": data[2] & 0b01111111,
23+
"tested": tested,
24+
"motion_detected": motion_detected,
25+
"battery": battery,
1326
"contact_open": contact_open or contact_timeout, # timeout still means its open
1427
"contact_timeout": contact_timeout,
15-
"is_light": bool(data[3] & 0b00000001),
16-
"button_count": (data[8] & 0b00001111),
28+
"is_light": is_light,
29+
"button_count": button_count,
1730
}

tests/test_adv_parser.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,142 @@ def test_parse_advertisement_data_curtain_firmware_six_fully_open():
408408
device=ble_device,
409409
rssi=-2,
410410
)
411+
412+
413+
def test_contact_sensor_mfr():
414+
"""Test parsing adv data from new bot firmware."""
415+
ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
416+
adv_data = generate_advertisement_data(
417+
manufacturer_data={2409: b"\xcb9\xcd\xc4=FA,\x00F\x01\x8f\xc4"},
418+
service_data={
419+
"0000fd3d-0000-1000-8000-00805f9b34fb": b"d\x00\xda\x04\x00F\x01\x8f\xc4"
420+
},
421+
tx_power=-127,
422+
rssi=-70,
423+
)
424+
result = parse_advertisement_data(ble_device, adv_data)
425+
assert result == SwitchBotAdvertisement(
426+
address="aa:bb:cc:dd:ee:ff",
427+
data={
428+
"data": {
429+
"battery": 90,
430+
"button_count": 4,
431+
"contact_open": True,
432+
"contact_timeout": True,
433+
"is_light": False,
434+
"motion_detected": False,
435+
"tested": False,
436+
},
437+
"isEncrypted": False,
438+
"model": "d",
439+
"modelFriendlyName": "Contact Sensor",
440+
"modelName": SwitchbotModel.CONTACT_SENSOR,
441+
"rawAdvData": b"d\x00\xda\x04\x00F\x01\x8f\xc4",
442+
},
443+
device=ble_device,
444+
rssi=-70,
445+
)
446+
447+
448+
def test_contact_sensor_srv():
449+
"""Test parsing adv data from new bot firmware."""
450+
ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
451+
adv_data = generate_advertisement_data(
452+
service_data={
453+
"0000fd3d-0000-1000-8000-00805f9b34fb": b"d\x00\xda\x04\x00F\x01\x8f\xc4"
454+
},
455+
tx_power=-127,
456+
rssi=-70,
457+
)
458+
result = parse_advertisement_data(ble_device, adv_data)
459+
assert result == SwitchBotAdvertisement(
460+
address="aa:bb:cc:dd:ee:ff",
461+
data={
462+
"data": {
463+
"battery": 90,
464+
"button_count": 4,
465+
"contact_open": True,
466+
"contact_timeout": True,
467+
"is_light": False,
468+
"motion_detected": False,
469+
"tested": False,
470+
},
471+
"isEncrypted": False,
472+
"model": "d",
473+
"modelFriendlyName": "Contact Sensor",
474+
"modelName": SwitchbotModel.CONTACT_SENSOR,
475+
"rawAdvData": b"d\x00\xda\x04\x00F\x01\x8f\xc4",
476+
},
477+
device=ble_device,
478+
rssi=-70,
479+
)
480+
481+
482+
def test_contact_sensor_open():
483+
"""Test parsing mfr adv data from new bot firmware."""
484+
ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
485+
adv_data = generate_advertisement_data(
486+
manufacturer_data={2409: b"\xcb9\xcd\xc4=F\x84\x9c\x00\x17\x00QD"},
487+
service_data={
488+
"0000fd3d-0000-1000-8000-00805f9b34fb": b"d@\xda\x02\x00\x17\x00QD"
489+
},
490+
tx_power=-127,
491+
rssi=-59,
492+
)
493+
result = parse_advertisement_data(ble_device, adv_data)
494+
assert result == SwitchBotAdvertisement(
495+
address="aa:bb:cc:dd:ee:ff",
496+
data={
497+
"data": {
498+
"battery": 90,
499+
"button_count": 4,
500+
"contact_open": True,
501+
"contact_timeout": False,
502+
"is_light": False,
503+
"motion_detected": True,
504+
"tested": False,
505+
},
506+
"isEncrypted": False,
507+
"model": "d",
508+
"modelFriendlyName": "Contact Sensor",
509+
"modelName": SwitchbotModel.CONTACT_SENSOR,
510+
"rawAdvData": b"d@\xda\x02\x00\x17\x00QD",
511+
},
512+
device=ble_device,
513+
rssi=-59,
514+
)
515+
516+
517+
def test_contact_sensor_closed():
518+
"""Test parsing mfr adv data from new bot firmware."""
519+
ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
520+
adv_data = generate_advertisement_data(
521+
manufacturer_data={2409: b"\xcb9\xcd\xc4=F\x89\x8c\x00+\x00\x19\x84"},
522+
service_data={
523+
"0000fd3d-0000-1000-8000-00805f9b34fb": b"d@\xda\x00\x00+\x00\x19\x84"
524+
},
525+
tx_power=-127,
526+
rssi=-50,
527+
)
528+
result = parse_advertisement_data(ble_device, adv_data)
529+
assert result == SwitchBotAdvertisement(
530+
address="aa:bb:cc:dd:ee:ff",
531+
data={
532+
"data": {
533+
"battery": 90,
534+
"button_count": 4,
535+
"contact_open": False,
536+
"contact_timeout": False,
537+
"is_light": False,
538+
"motion_detected": True,
539+
"tested": False,
540+
},
541+
"isEncrypted": False,
542+
"model": "d",
543+
"modelFriendlyName": "Contact Sensor",
544+
"modelName": SwitchbotModel.CONTACT_SENSOR,
545+
"rawAdvData": b"d@\xda\x00\x00+\x00\x19\x84",
546+
},
547+
device=ble_device,
548+
rssi=-50,
549+
)

0 commit comments

Comments
 (0)