From 342f406c21b6eae5b361571d51c5f5c9f842878f Mon Sep 17 00:00:00 2001 From: Dian FAN Date: Tue, 27 Aug 2024 19:38:38 +0800 Subject: [PATCH] support Xiaomi Occupancy(Human Presence) Sensor XMOSB01XS --- src/xiaomi_ble/const.py | 6 +++ src/xiaomi_ble/devices.py | 4 ++ src/xiaomi_ble/parser.py | 41 +++++++++++++++++ tests/test_parser.py | 97 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) diff --git a/src/xiaomi_ble/const.py b/src/xiaomi_ble/const.py index 644e843..803e5db 100644 --- a/src/xiaomi_ble/const.py +++ b/src/xiaomi_ble/const.py @@ -80,3 +80,9 @@ class ExtendedSensorDeviceClass(BaseDeviceClass): # Toothbrush score SCORE = "score" + + # Has-Someone-Duration + HAS_SOMEONE_DURATION = "has_someone_duration" + + # No-One-Duration + NO_ONE_DURATION = "no_one_duration" diff --git a/src/xiaomi_ble/devices.py b/src/xiaomi_ble/devices.py index 9f0bfae..5c2f578 100644 --- a/src/xiaomi_ble/devices.py +++ b/src/xiaomi_ble/devices.py @@ -158,6 +158,10 @@ class DeviceEntry: name="Motion Sensor", model="XMPIRO2SXS", ), + 0x4683: DeviceEntry( + name="Occupancy Sensor", + model="XMOSB01XS", + ), 0x0863: DeviceEntry( name="Flood Detector", model="SJWS01LM", diff --git a/src/xiaomi_ble/parser.py b/src/xiaomi_ble/parser.py index df08e52..be2cccb 100644 --- a/src/xiaomi_ble/parser.py +++ b/src/xiaomi_ble/parser.py @@ -1074,6 +1074,44 @@ def obj4818( return {} +def obj484e( + xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str +) -> dict[str, Any]: + """Occupancy-status: uint8: 0 - No One, 1 - Has One""" + device.update_predefined_binary_sensor( + BinarySensorDeviceClass.OCCUPANCY, xobj[0] > 0 + ) + return {} + + +def obj4851( + xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str +) -> dict[str, Any]: + """has-someone-duration: uint8: 2 - 2 minutes, 5 - 5 minutes""" + device.update_sensor( + key=ExtendedSensorDeviceClass.HAS_SOMEONE_DURATION, + name="Has someone duration", + native_unit_of_measurement=Units.TIME_MINUTES, + device_class=ExtendedSensorDeviceClass.HAS_SOMEONE_DURATION, + native_value=xobj[0], + ) + return {} + + +def obj4852( + xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str +) -> dict[str, Any]: + """no-one-duration: uint8: 2/5/10/30 - 2/5/10/30 minutes""" + device.update_sensor( + key=ExtendedSensorDeviceClass.NO_ONE_DURATION, + name="No one duration", + native_unit_of_measurement=Units.TIME_MINUTES, + device_class=ExtendedSensorDeviceClass.NO_ONE_DURATION, + native_value=xobj[0], + ) + return {} + + def obj4a01( xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str ) -> dict[str, Any]: @@ -1441,6 +1479,9 @@ def obj4e1c( 0x4805: obj4805, 0x4806: obj4806, 0x4818: obj4818, + 0x484E: obj484e, + 0x4851: obj4851, + 0x4852: obj4852, 0x4A01: obj4a01, 0x4A08: obj4a08, 0x4A0C: obj4a0c, diff --git a/tests/test_parser.py b/tests/test_parser.py index 4bf8110..3e1b1c5 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -31,6 +31,7 @@ KEY_BINARY_DOOR = DeviceKey(key="door", device_id=None) KEY_BINARY_FINGERPRINT = DeviceKey(key="fingerprint", device_id=None) KEY_BINARY_MOTION = DeviceKey(key="motion", device_id=None) +KEY_BINARY_OCCUPANCY = DeviceKey(key="occupancy", device_id=None) KEY_BINARY_LIGHT = DeviceKey(key="light", device_id=None) KEY_BINARY_LOCK = DeviceKey(key="lock", device_id=None) KEY_BINARY_OPENING = DeviceKey(key="opening", device_id=None) @@ -3009,6 +3010,102 @@ def test_Xiaomi_XMPIRO2SXS(): ) +def test_Xiaomi_XMOSB01XS_ILLUMINANCE(): + """Test Xiaomi parser for Xiaomi Occupancy(Human Presence) Sensor XMOSB01XS.""" + data_string = ( + b"\x48\x59\x83\x46\x0D\xDC\x21\x3C\xE9\x81\xDA\x7A\xE2\x02\x00\x44\x41\xF8\x8C" + ) + advertisement = bytes_to_service_info(data_string, address="0C:43:14:A1:41:1E") + bindkey = "0a4552cb19a639b72b8ed09bde6d5bfa" + + device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) + assert device.supported(advertisement) + assert device.bindkey_verified + assert device.update(advertisement) == SensorUpdate( + title="Occupancy Sensor 411E (XMOSB01XS)", + devices={ + None: SensorDeviceInfo( + name="Occupancy Sensor 411E", + manufacturer="Xiaomi", + model="XMOSB01XS", + hw_version=None, + sw_version="Xiaomi (MiBeacon V5 encrypted)", + ) + }, + entity_descriptions={ + KEY_ILLUMINANCE: SensorDescription( + device_key=KEY_ILLUMINANCE, + device_class=DeviceClass.ILLUMINANCE, + native_unit_of_measurement=Units.LIGHT_LUX, + ), + KEY_SIGNAL_STRENGTH: SensorDescription( + device_key=KEY_SIGNAL_STRENGTH, + device_class=DeviceClass.SIGNAL_STRENGTH, + native_unit_of_measurement="dBm", + ), + }, + entity_values={ + KEY_ILLUMINANCE: SensorValue( + name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=38 + ), + KEY_SIGNAL_STRENGTH: SensorValue( + name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 + ), + }, + binary_entity_descriptions={}, + binary_entity_values={}, + ) + + +def test_Xiaomi_XMOSB01XS_OCCUPANCY(): + """Test Xiaomi parser for Xiaomi Occupancy(Human Presence) Sensor XMOSB01XS.""" + data_string = ( + b"\x58\x59\x83\x46\x1F\xBD\xB1\xC4\x67\x48\xD4" + b"\x9D\x1E\xFD\x8C\x04\x00\x00\xE5\x7E\x87\x3A" + ) + advertisement = bytes_to_service_info(data_string, address="D4:48:67:C4:B1:BD") + bindkey = "920ce119b34410d38251ccea54c0f915" + + device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) + assert device.supported(advertisement) + assert device.bindkey_verified + assert device.update(advertisement) == SensorUpdate( + title="Occupancy Sensor B1BD (XMOSB01XS)", + devices={ + None: SensorDeviceInfo( + name="Occupancy Sensor B1BD", + manufacturer="Xiaomi", + model="XMOSB01XS", + hw_version=None, + sw_version="Xiaomi (MiBeacon V5 encrypted)", + ) + }, + entity_descriptions={ + KEY_SIGNAL_STRENGTH: SensorDescription( + device_key=KEY_SIGNAL_STRENGTH, + device_class=DeviceClass.SIGNAL_STRENGTH, + native_unit_of_measurement="dBm", + ), + }, + entity_values={ + KEY_SIGNAL_STRENGTH: SensorValue( + name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60 + ), + }, + binary_entity_descriptions={ + KEY_BINARY_OCCUPANCY: BinarySensorDescription( + device_key=KEY_BINARY_OCCUPANCY, + device_class=BinarySensorDeviceClass.OCCUPANCY, + ), + }, + binary_entity_values={ + KEY_BINARY_OCCUPANCY: BinarySensorValue( + device_key=KEY_BINARY_OCCUPANCY, name="Occupancy", native_value=False + ), + }, + ) + + def test_Xiaomi_PTX_press(): """Test Xiaomi parser for Xiaomi PTX YK1 QMIMB.""" bindkey = "a74510b40386d35ae6227a7451efc76e"