Browse Source

Passive motion support (#158)

J. Nick Koston 1 year ago
parent
commit
0a0066be62
3 changed files with 282 additions and 9 deletions
  1. 1 0
      switchbot/adv_parser.py
  2. 30 9
      switchbot/adv_parsers/motion.py
  3. 251 0
      tests/test_adv_parser.py

+ 1 - 0
switchbot/adv_parser.py

@@ -59,6 +59,7 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
         "modelName": SwitchbotModel.MOTION_SENSOR,
         "modelFriendlyName": "Motion Sensor",
         "func": process_wopresence,
+        "manufacturer_data_length": 10,
     },
     "r": {
         "modelName": SwitchbotModel.LIGHT_STRIP,

+ 30 - 9
switchbot/adv_parsers/motion.py

@@ -6,15 +6,36 @@ def process_wopresence(
     data: bytes | None, mfr_data: bytes | None
 ) -> dict[str, bool | int]:
     """Process WoPresence Sensor services data."""
-    if data is None:
+    if data is None and mfr_data is None:
         return {}
+    tested = None
+    battery = None
+    led = None
+    iot = None
+    sense_distance = None
+    light_intensity = None
+    is_light = None
+
+    if data:
+        tested = bool(data[1] & 0b10000000)
+        motion_detected = bool(data[1] & 0b01000000)
+        battery = data[2] & 0b01111111
+        led = (data[5] & 0b00100000) >> 5
+        iot = (data[5] & 0b00010000) >> 4
+        sense_distance = (data[5] & 0b00001100) >> 2
+        light_intensity = data[5] & 0b00000011
+        is_light = bool(data[5] & 0b00000010)
+    if mfr_data:
+        motion_detected = bool(mfr_data[7] & 0b01000000)
+        is_light = bool(mfr_data[7] & 0b00100000)
+
     return {
-        "tested": bool(data[1] & 0b10000000),
-        "motion_detected": bool(data[1] & 0b01000000),
-        "battery": data[2] & 0b01111111,
-        "led": (data[5] & 0b00100000) >> 5,
-        "iot": (data[5] & 0b00010000) >> 4,
-        "sense_distance": (data[5] & 0b00001100) >> 2,
-        "light_intensity": data[5] & 0b00000011,
-        "is_light": bool(data[5] & 0b00000010),
+        "tested": tested,
+        "motion_detected": motion_detected,
+        "battery": battery,
+        "led": led,
+        "iot": iot,
+        "sense_distance": sense_distance,
+        "light_intensity": light_intensity,
+        "is_light": is_light,
     }

+ 251 - 0
tests/test_adv_parser.py

@@ -769,3 +769,254 @@ def test_wosensor_passive_only():
         device=ble_device,
         rssi=-50,
     )
+
+
+def test_motion_sensor_clear():
+    """Test parsing motion sensor with clear data."""
+    ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIj\x1c\x00f"},
+        service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s\x00\xe2\x00f\x01"},
+        tx_power=-127,
+        rssi=-87,
+    )
+    result = parse_advertisement_data(
+        ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
+    )
+    assert result == SwitchBotAdvertisement(
+        address="aa:bb:cc:dd:ee:ff",
+        data={
+            "data": {
+                "battery": 98,
+                "iot": 0,
+                "is_light": False,
+                "led": 0,
+                "light_intensity": 1,
+                "motion_detected": False,
+                "sense_distance": 0,
+                "tested": False,
+            },
+            "isEncrypted": False,
+            "model": "s",
+            "modelFriendlyName": "Motion Sensor",
+            "modelName": SwitchbotModel.MOTION_SENSOR,
+            "rawAdvData": b"s\x00\xe2\x00f\x01",
+        },
+        device=ble_device,
+        rssi=-87,
+    )
+
+
+def test_motion_sensor_clear_passive():
+    """Test parsing motion sensor with clear data."""
+    ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIj\x1c\x00f"},
+        service_data={},
+        tx_power=-127,
+        rssi=-87,
+    )
+    result = parse_advertisement_data(
+        ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
+    )
+    assert result == SwitchBotAdvertisement(
+        address="aa:bb:cc:dd:ee:ff",
+        data={
+            "data": {
+                "battery": None,
+                "iot": None,
+                "is_light": False,
+                "led": None,
+                "light_intensity": None,
+                "motion_detected": False,
+                "sense_distance": None,
+                "tested": None,
+            },
+            "isEncrypted": False,
+            "model": "s",
+            "modelFriendlyName": "Motion Sensor",
+            "modelName": SwitchbotModel.MOTION_SENSOR,
+            "rawAdvData": None,
+        },
+        device=ble_device,
+        rssi=-87,
+    )
+
+
+def test_motion_sensor_motion():
+    """Test parsing motion sensor with motion data."""
+    ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIi\\\x008"},
+        service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s@\xe2\x008\x01"},
+        tx_power=-127,
+        rssi=-87,
+    )
+    result = parse_advertisement_data(
+        ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
+    )
+    assert result == SwitchBotAdvertisement(
+        address="aa:bb:cc:dd:ee:ff",
+        data={
+            "data": {
+                "battery": 98,
+                "iot": 0,
+                "is_light": False,
+                "led": 0,
+                "light_intensity": 1,
+                "motion_detected": True,
+                "sense_distance": 0,
+                "tested": False,
+            },
+            "isEncrypted": False,
+            "model": "s",
+            "modelFriendlyName": "Motion Sensor",
+            "modelName": SwitchbotModel.MOTION_SENSOR,
+            "rawAdvData": b"s@\xe2\x008\x01",
+        },
+        device=ble_device,
+        rssi=-87,
+    )
+
+
+def test_motion_sensor_motion_passive():
+    """Test parsing motion sensor with motion data."""
+    ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIi\\\x008"},
+        service_data={},
+        tx_power=-127,
+        rssi=-87,
+    )
+    result = parse_advertisement_data(
+        ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
+    )
+    assert result == SwitchBotAdvertisement(
+        address="aa:bb:cc:dd:ee:ff",
+        data={
+            "data": {
+                "battery": None,
+                "iot": None,
+                "is_light": False,
+                "led": None,
+                "light_intensity": None,
+                "motion_detected": True,
+                "sense_distance": None,
+                "tested": None,
+            },
+            "isEncrypted": False,
+            "model": "s",
+            "modelFriendlyName": "Motion Sensor",
+            "modelName": SwitchbotModel.MOTION_SENSOR,
+            "rawAdvData": None,
+        },
+        device=ble_device,
+        rssi=-87,
+    )
+
+
+def test_motion_sensor_is_light_passive():
+    """Test parsing motion sensor with motion data."""
+    ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIs,\x04g"},
+        service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s\x00\xe2\x04g\x02"},
+        tx_power=-127,
+        rssi=-93,
+    )
+    result = parse_advertisement_data(
+        ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
+    )
+    assert result == SwitchBotAdvertisement(
+        address="aa:bb:cc:dd:ee:ff",
+        data={
+            "data": {
+                "battery": 98,
+                "iot": 0,
+                "is_light": True,
+                "led": 0,
+                "light_intensity": 2,
+                "motion_detected": False,
+                "sense_distance": 0,
+                "tested": False,
+            },
+            "isEncrypted": False,
+            "model": "s",
+            "modelFriendlyName": "Motion Sensor",
+            "modelName": SwitchbotModel.MOTION_SENSOR,
+            "rawAdvData": b"s\x00\xe2\x04g\x02",
+        },
+        device=ble_device,
+        rssi=-93,
+    )
+
+
+def test_motion_sensor_is_light_active():
+    """Test parsing motion sensor with motion data."""
+    ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={},
+        service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s\x00\xe2\x04g\x02"},
+        tx_power=-127,
+        rssi=-93,
+    )
+    result = parse_advertisement_data(
+        ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
+    )
+    assert result == SwitchBotAdvertisement(
+        address="aa:bb:cc:dd:ee:ff",
+        data={
+            "data": {
+                "battery": 98,
+                "iot": 0,
+                "is_light": True,
+                "led": 0,
+                "light_intensity": 2,
+                "motion_detected": False,
+                "sense_distance": 0,
+                "tested": False,
+            },
+            "isEncrypted": False,
+            "model": "s",
+            "modelFriendlyName": "Motion Sensor",
+            "modelName": SwitchbotModel.MOTION_SENSOR,
+            "rawAdvData": b"s\x00\xe2\x04g\x02",
+        },
+        device=ble_device,
+        rssi=-93,
+    )
+
+
+def test_motion_with_light_detected():
+    ble_device = BLEDevice("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIvl\x00,"},
+        service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s@\xe2\x00,\x02"},
+        tx_power=-127,
+        rssi=-84,
+    )
+    result = parse_advertisement_data(
+        ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
+    )
+    assert result == SwitchBotAdvertisement(
+        address="aa:bb:cc:dd:ee:ff",
+        data={
+            "data": {
+                "battery": 98,
+                "iot": 0,
+                "is_light": True,
+                "led": 0,
+                "light_intensity": 2,
+                "motion_detected": True,
+                "sense_distance": 0,
+                "tested": False,
+            },
+            "isEncrypted": False,
+            "model": "s",
+            "modelFriendlyName": "Motion Sensor",
+            "modelName": SwitchbotModel.MOTION_SENSOR,
+            "rawAdvData": b"s@\xe2\x00,\x02",
+        },
+        device=ble_device,
+        rssi=-84,
+    )