Ver código fonte

Add support for presence sensor (#410)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Retha Runolfsson 5 dias atrás
pai
commit
3d82e435c0

+ 7 - 0
switchbot/adv_parser.py

@@ -35,6 +35,7 @@ from .adv_parsers.lock import (
 from .adv_parsers.meter import process_wosensorth, process_wosensorth_c
 from .adv_parsers.motion import process_wopresence
 from .adv_parsers.plug import process_woplugmini
+from .adv_parsers.presence_sensor import process_presence_sensor
 from .adv_parsers.relay_switch import (
     process_garage_door_opener,
     process_relay_switch_1pm,
@@ -390,6 +391,12 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
         "func": process_vacuum,
         "manufacturer_id": 2409,
     },
+    b"\x00\x10\xcc\xc8": {
+        "modelName": SwitchbotModel.PRESENCE_SENSOR,
+        "modelFriendlyName": "Presence Sensor",
+        "func": process_presence_sensor,
+        "manufacturer_id": 2409,
+    },
 }
 
 _SWITCHBOT_MODEL_TO_CHAR = {

+ 45 - 0
switchbot/adv_parsers/presence_sensor.py

@@ -0,0 +1,45 @@
+"""Advertisement data parser for presence sensor devices."""
+
+from __future__ import annotations
+
+import logging
+
+from ..const.presence_sensor import BATTERY_LEVEL_MAP
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def process_presence_sensor(
+    data: bytes | None, mfr_data: bytes | None
+) -> dict[str, bool | int | str]:
+    """Process Presence Sensor data."""
+    if mfr_data is None:
+        return {}
+
+    seq_number = mfr_data[6]
+    adaptive_state = bool(mfr_data[7] & 0x80)
+    motion_detected = bool(mfr_data[7] & 0x40)
+    battery_bits = (mfr_data[7] >> 2) & 0x03
+    battery_range = BATTERY_LEVEL_MAP.get(battery_bits, "Unknown")
+    trigger_flag = mfr_data[10]
+    led_state = bool(mfr_data[11] & 0x80)
+    light_level = mfr_data[11] & 0x0F
+
+    result = {
+        "sequence_number": seq_number,
+        "adaptive_state": adaptive_state,
+        "motion_detected": motion_detected,
+        "battery_range": battery_range,
+        "trigger_flag": trigger_flag,
+        "led_state": led_state,
+        "lightLevel": light_level,
+    }
+
+    if data:
+        battery = data[2] & 0x7F
+        result["battery"] = battery
+
+    _LOGGER.debug(
+        "Processed presence sensor mfr data: %s, result: %s", mfr_data.hex(), result
+    )
+    return result

+ 1 - 0
switchbot/const/__init__.py

@@ -101,6 +101,7 @@ class SwitchbotModel(StrEnum):
     CLIMATE_PANEL = "Climate Panel"
     SMART_THERMOSTAT_RADIATOR = "Smart Thermostat Radiator"
     S20_VACUUM = "S20 Vacuum"
+    PRESENCE_SENSOR = "Presence Sensor"
 
 
 __all__ = [

+ 8 - 0
switchbot/const/presence_sensor.py

@@ -0,0 +1,8 @@
+"""Constants for presence sensor devices."""
+
+BATTERY_LEVEL_MAP = {
+    0: "<10%",
+    1: "10-19%",
+    2: "20-59%",
+    3: ">=60%",
+}

+ 1 - 0
switchbot/devices/device.py

@@ -74,6 +74,7 @@ API_MODEL_TO_ENUM: dict[str, SwitchbotModel] = {
     "W1079000": SwitchbotModel.METER_PRO,  # Meter Pro (another variant)
     "W1102001": SwitchbotModel.STRIP_LIGHT_3,  # RGBWW Strip Light 3
     "W1106000": SwitchbotModel.S20_VACUUM,
+    "W1101000": SwitchbotModel.PRESENCE_SENSOR,
 }
 
 REQ_HEADER = "570f"

+ 41 - 0
tests/test_adv_parser.py

@@ -3501,6 +3501,23 @@ def test_humidifer_with_empty_data() -> None:
             "S20 Vacuum",
             SwitchbotModel.S20_VACUUM,
         ),
+        AdvTestCase(
+            b"\xb0\xe9\xfelf\xaa\x06\xcc\x04V\x00\x8c",
+            b"\x00 d\x00\x10\xcc\xc8",
+            {
+                "adaptive_state": True,
+                "battery": 100,
+                "battery_range": ">=60%",
+                "led_state": True,
+                "lightLevel": 12,
+                "motion_detected": True,
+                "sequence_number": 6,
+                "trigger_flag": 0,
+            },
+            b"\x00\x10\xcc\xc8",
+            "Presence Sensor",
+            SwitchbotModel.PRESENCE_SENSOR,
+        ),
     ],
 )
 def test_adv_active(test_case: AdvTestCase) -> None:
@@ -3768,6 +3785,22 @@ def test_adv_active(test_case: AdvTestCase) -> None:
             "S20 Vacuum",
             SwitchbotModel.S20_VACUUM,
         ),
+        AdvTestCase(
+            b"\xb0\xe9\xfelf\xaa\x06\xcc\x04V\x00\x8c",
+            None,
+            {
+                "adaptive_state": True,
+                "battery_range": ">=60%",
+                "led_state": True,
+                "lightLevel": 12,
+                "motion_detected": True,
+                "sequence_number": 6,
+                "trigger_flag": 0,
+            },
+            b"\x00\x10\xcc\xc8",
+            "Presence Sensor",
+            SwitchbotModel.PRESENCE_SENSOR,
+        ),
     ],
 )
 def test_adv_passive(test_case: AdvTestCase) -> None:
@@ -3957,6 +3990,14 @@ def test_adv_passive(test_case: AdvTestCase) -> None:
             "S20 Vacuum",
             SwitchbotModel.S20_VACUUM,
         ),
+        AdvTestCase(
+            None,
+            b"\x00 d\x00\x10\xcc\xc8",
+            {},
+            b"\x00\x10\xcc\xc8",
+            "Presence Sensor",
+            SwitchbotModel.PRESENCE_SENSOR,
+        ),
     ],
 )
 def test_adv_with_empty_data(test_case: AdvTestCase) -> None: