Преглед изворни кода

Add support for retrieving sensor data from WoHub2 device (#240)

Huyuwei пре 4 месеци
родитељ
комит
fb376c4c7d
5 измењених фајлова са 86 додато и 40 уклоњено
  1. 8 1
      switchbot/adv_parser.py
  2. 40 0
      switchbot/adv_parsers/hub2.py
  3. 1 0
      switchbot/const.py
  4. 3 2
      switchbot/discovery.py
  5. 34 37
      tests/test_adv_parser.py

+ 8 - 1
switchbot/adv_parser.py

@@ -1,4 +1,5 @@
 """Library to handle connection with Switchbot."""
+
 from __future__ import annotations
 
 import logging
@@ -15,6 +16,7 @@ from .adv_parsers.bulb import process_color_bulb
 from .adv_parsers.ceiling_light import process_woceiling
 from .adv_parsers.contact import process_wocontact
 from .adv_parsers.curtain import process_wocurtain
+from .adv_parsers.hub2 import process_wohub2
 from .adv_parsers.humidifier import process_wohumidifier
 from .adv_parsers.light_strip import process_wostrip
 from .adv_parsers.lock import process_wolock
@@ -54,7 +56,6 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
         "modelName": SwitchbotModel.BOT,
         "modelFriendlyName": "Bot",
         "func": process_wohand,
-        "service_uuids": {"cba20d00-224d-11e6-9fb8-0002a5d5c51b"},
         "manufacturer_id": 89,
     },
     "s": {
@@ -100,6 +101,12 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
         "func": process_wosensorth,
         "manufacturer_id": 2409,
     },
+    "v": {
+        "modelName": SwitchbotModel.HUB2,
+        "modelFriendlyName": "Hub 2",
+        "func": process_wohub2,
+        "manufacturer_id": 2409,
+    },
     "g": {
         "modelName": SwitchbotModel.PLUG_MINI,
         "modelFriendlyName": "Plug Mini",

+ 40 - 0
switchbot/adv_parsers/hub2.py

@@ -0,0 +1,40 @@
+"""Hub2 parser."""
+
+from __future__ import annotations
+
+from typing import Any
+
+
+def process_wohub2(data: bytes | None, mfr_data: bytes | None) -> dict[str, Any]:
+    """Process woHub2 sensor manufacturer data."""
+    temp_data = None
+
+    if mfr_data:
+        status = mfr_data[12]
+        temp_data = mfr_data[13:16]
+
+    if not temp_data:
+        return {}
+
+    _temp_sign = 1 if temp_data[1] & 0b10000000 else -1
+    _temp_c = _temp_sign * (
+        (temp_data[1] & 0b01111111) + ((temp_data[0] & 0b00001111) / 10)
+    )
+    _temp_f = (_temp_c * 9 / 5) + 32
+    _temp_f = (_temp_f * 10) / 10
+    humidity = temp_data[2] & 0b01111111
+    light_level = status & 0b11111
+
+    if _temp_c == 0 and humidity == 0:
+        return {}
+
+    _wohub2_data = {
+        # Data should be flat, but we keep the original structure for now
+        "temp": {"c": _temp_c, "f": _temp_f},
+        "temperature": _temp_c,
+        "fahrenheit": bool(temp_data[2] & 0b10000000),
+        "humidity": humidity,
+        "lightLevel": light_level,
+    }
+
+    return _wohub2_data

+ 1 - 0
switchbot/const.py

@@ -48,6 +48,7 @@ class SwitchbotModel(StrEnum):
     CEILING_LIGHT = "WoCeiling"
     LOCK = "WoLock"
     BLIND_TILT = "WoBlindTilt"
+    HUB2 = "WoHub2"
 
 
 class LockStatus(Enum):

+ 3 - 2
switchbot/discovery.py

@@ -42,11 +42,11 @@ class GetSwitchbotDevices:
 
         devices = None
         devices = bleak.BleakScanner(
+            detection_callback=self.detection_callback,
             # TODO: Find new UUIDs to filter on. For example, see
             # https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/4ad138bb09f0fbbfa41b152ca327a78c1d0b6ba9/devicetypes/meter.md
             adapter=self._interface,
         )
-        devices.register_detection_callback(self.detection_callback)
 
         async with CONNECT_LOCK:
             await devices.start()
@@ -111,7 +111,8 @@ class GetSwitchbotDevices:
         base_meters = await self._get_devices_by_model("T")
         plus_meters = await self._get_devices_by_model("i")
         io_meters = await self._get_devices_by_model("w")
-        return {**base_meters, **plus_meters, **io_meters}
+        hub2_meters = await self._get_devices_by_model("v")
+        return {**base_meters, **plus_meters, **io_meters, **hub2_meters}
 
     async def get_contactsensors(self) -> dict[str, SwitchBotAdvertisement]:
         """Return all WoContact/Contact sensor devices with services data."""

+ 34 - 37
tests/test_adv_parser.py

@@ -964,6 +964,40 @@ def test_wosensor_active_zero_data():
     )
 
 
+def test_wohub2_passive_and_active():
+    """Test parsing wosensor as passive with active data as well."""
+    ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
+    adv_data = generate_advertisement_data(
+        manufacturer_data={
+            2409: b"\xaa\xbb\xcc\xdd\xee\xff\x00\xfffT\x1a\xf1\x82\x07\x9a2\x00"
+        },
+        service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"v\x00"},
+        tx_power=-127,
+        rssi=-50,
+    )
+    result = parse_advertisement_data(ble_device, adv_data)
+    assert result == SwitchBotAdvertisement(
+        address="aa:bb:cc:dd:ee:ff",
+        data={
+            "data": {
+                "fahrenheit": False,
+                "humidity": 50,
+                "lightLevel": 2,
+                "temp": {"c": 26.7, "f": 80.06},
+                "temperature": 26.7,
+            },
+            "isEncrypted": False,
+            "model": "v",
+            "modelFriendlyName": "Hub 2",
+            "modelName": SwitchbotModel.HUB2,
+            "rawAdvData": b"v\x00",
+        },
+        device=ble_device,
+        rssi=-50,
+        active=True,
+    )
+
+
 def test_woiosensor_passive_and_active():
     """Test parsing woiosensor as passive with active data as well."""
     ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
@@ -1286,43 +1320,6 @@ def test_motion_with_light_detected():
     )
 
 
-def test_motion_sensor_motion_passive():
-    """Test parsing motion sensor with motion data."""
-    ble_device = generate_ble_device("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,
-        active=False,
-    )
-
-
 def test_parsing_lock_active():
     """Test parsing lock with active data."""
     ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")