Browse Source

Add support for climate panel (#403)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Retha Runolfsson 2 days ago
parent
commit
9d8a9c2954

+ 13 - 6
switchbot/adv_parser.py

@@ -15,6 +15,7 @@ from .adv_parsers.blind_tilt import process_woblindtilt
 from .adv_parsers.bot import process_wohand
 from .adv_parsers.bulb import process_color_bulb
 from .adv_parsers.ceiling_light import process_woceiling
+from .adv_parsers.climate_panel import process_climate_panel
 from .adv_parsers.contact import process_wocontact
 from .adv_parsers.curtain import process_wocurtain
 from .adv_parsers.fan import process_fan
@@ -370,6 +371,12 @@ SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
         "func": process_vacuum,
         "manufacturer_id": 2409,
     },
+    b"\x00\x10\xf3\xd8": {
+        "modelName": SwitchbotModel.CLIMATE_PANEL,
+        "modelFriendlyName": "Climate Panel",
+        "func": process_climate_panel,
+        "manufacturer_id": 2409,
+    },
 }
 
 _SWITCHBOT_MODEL_TO_CHAR = {
@@ -452,12 +459,12 @@ def _parse_data(
             if model_data.get("manufacturer_data_length") == len(_mfr_data):
                 _model = model_chr
                 break
-    if (
-        _service_data
-        and len(_service_data) > 5
-        and _service_data[-4:] in SUPPORTED_TYPES
-    ):
-        _model = _service_data[-4:]
+
+    if _service_data and len(_service_data) > 5:
+        for s in (_service_data[-4:], _service_data[-5:-1]):
+            if s in SUPPORTED_TYPES:
+                _model = s
+                break
 
     if not _model:
         return None

+ 46 - 0
switchbot/adv_parsers/climate_panel.py

@@ -0,0 +1,46 @@
+"""Advertisement data parser for climate panel devices."""
+
+import logging
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def process_climate_panel(
+    data: bytes | None, mfr_data: bytes | None
+) -> dict[str, bool | int | str]:
+    """Process Climate Panel data."""
+    if mfr_data is None:
+        return {}
+
+    seq_number = mfr_data[6]
+    isOn = bool(mfr_data[7] & 0x80)
+    battery = mfr_data[7] & 0x7F
+    humidity_alarm = (mfr_data[8] >> 6) & 0x03
+    temp_alarm = (mfr_data[8] >> 4) & 0x03
+
+    temp_decimal = mfr_data[8] & 0x0F
+    temp_sign = 1 if (mfr_data[9] & 0x80) else -1
+    temp_int = mfr_data[9] & 0x7F
+    temperature = temp_sign * (temp_int + temp_decimal / 10)
+
+    humidity = mfr_data[10] & 0x7F
+
+    pir_state = bool(mfr_data[15] & 0x80)
+    is_light = ((mfr_data[15] >> 2) & 0x03) == 0x10
+
+    result = {
+        "sequence_number": seq_number,
+        "isOn": isOn,
+        "battery": battery,
+        "temperature": temperature,
+        "humidity": humidity,
+        "temp_alarm": temp_alarm,
+        "humidity_alarm": humidity_alarm,
+        "motion_detected": pir_state,
+        "is_light": is_light,
+    }
+
+    _LOGGER.debug(
+        "Processed climate panel mfr data: %s, result: %s", mfr_data.hex(), result
+    )
+    return result

+ 1 - 0
switchbot/const/__init__.py

@@ -97,6 +97,7 @@ class SwitchbotModel(StrEnum):
     RGBICWW_STRIP_LIGHT = "RGBICWW Strip Light"
     RGBICWW_FLOOR_LAMP = "RGBICWW Floor Lamp"
     K11_VACUUM = "K11+ Vacuum"
+    CLIMATE_PANEL = "Climate Panel"
 
 
 __all__ = [

+ 44 - 0
tests/test_adv_parser.py

@@ -3430,6 +3430,24 @@ def test_humidifer_with_empty_data() -> None:
             "K11+ Vacuum",
             SwitchbotModel.K11_VACUUM,
         ),
+        AdvTestCase(
+            b"\xb0\xe9\xfe\x8e\x98Oi_\x06\x9a,\x00\x00\x00\x00\xe4\x00\x08\x04\x00\x01\x00\x00",
+            b"\x00 _\x00\x10\xf3\xd8@",
+            {
+                "battery": 95,
+                "humidity": 44,
+                "sequence_number": 105,
+                "humidity_alarm": 0,
+                "isOn": False,
+                "is_light": False,
+                "motion_detected": True,
+                "temp_alarm": 0,
+                "temperature": 26.6,
+            },
+            b"\x00\x10\xf3\xd8",
+            "Climate Panel",
+            SwitchbotModel.CLIMATE_PANEL,
+        ),
     ],
 )
 def test_adv_active(test_case: AdvTestCase) -> None:
@@ -3644,6 +3662,24 @@ def test_adv_active(test_case: AdvTestCase) -> None:
             "K11+ Vacuum",
             SwitchbotModel.K11_VACUUM,
         ),
+        AdvTestCase(
+            b"\xb0\xe9\xfe\x8e\x98Oi_\x06\x9a,\x00\x00\x00\x00\xe4\x00\x08\x04\x00\x01\x00\x00",
+            None,
+            {
+                "battery": 95,
+                "humidity": 44,
+                "sequence_number": 105,
+                "humidity_alarm": 0,
+                "isOn": False,
+                "is_light": False,
+                "motion_detected": True,
+                "temp_alarm": 0,
+                "temperature": 26.6,
+            },
+            b"\x00\x10\xf3\xd8",
+            "Climate Panel",
+            SwitchbotModel.CLIMATE_PANEL,
+        ),
     ],
 )
 def test_adv_passive(test_case: AdvTestCase) -> None:
@@ -3809,6 +3845,14 @@ def test_adv_passive(test_case: AdvTestCase) -> None:
             "K11+ Vacuum",
             SwitchbotModel.K11_VACUUM,
         ),
+        AdvTestCase(
+            None,
+            b"\x00 _\x00\x10\xf3\xd8@",
+            {},
+            b"\x00\x10\xf3\xd8",
+            "Climate Panel",
+            SwitchbotModel.CLIMATE_PANEL,
+        ),
     ],
 )
 def test_adv_with_empty_data(test_case: AdvTestCase) -> None: