Skip to content

Commit 342f406

Browse files
committed
support Xiaomi Occupancy(Human Presence) Sensor XMOSB01XS
1 parent cf115f6 commit 342f406

File tree

4 files changed

+148
-0
lines changed

4 files changed

+148
-0
lines changed

src/xiaomi_ble/const.py

+6
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,9 @@ class ExtendedSensorDeviceClass(BaseDeviceClass):
8080

8181
# Toothbrush score
8282
SCORE = "score"
83+
84+
# Has-Someone-Duration
85+
HAS_SOMEONE_DURATION = "has_someone_duration"
86+
87+
# No-One-Duration
88+
NO_ONE_DURATION = "no_one_duration"

src/xiaomi_ble/devices.py

+4
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ class DeviceEntry:
158158
name="Motion Sensor",
159159
model="XMPIRO2SXS",
160160
),
161+
0x4683: DeviceEntry(
162+
name="Occupancy Sensor",
163+
model="XMOSB01XS",
164+
),
161165
0x0863: DeviceEntry(
162166
name="Flood Detector",
163167
model="SJWS01LM",

src/xiaomi_ble/parser.py

+41
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,44 @@ def obj4818(
10741074
return {}
10751075

10761076

1077+
def obj484e(
1078+
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
1079+
) -> dict[str, Any]:
1080+
"""Occupancy-status: uint8: 0 - No One, 1 - Has One"""
1081+
device.update_predefined_binary_sensor(
1082+
BinarySensorDeviceClass.OCCUPANCY, xobj[0] > 0
1083+
)
1084+
return {}
1085+
1086+
1087+
def obj4851(
1088+
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
1089+
) -> dict[str, Any]:
1090+
"""has-someone-duration: uint8: 2 - 2 minutes, 5 - 5 minutes"""
1091+
device.update_sensor(
1092+
key=ExtendedSensorDeviceClass.HAS_SOMEONE_DURATION,
1093+
name="Has someone duration",
1094+
native_unit_of_measurement=Units.TIME_MINUTES,
1095+
device_class=ExtendedSensorDeviceClass.HAS_SOMEONE_DURATION,
1096+
native_value=xobj[0],
1097+
)
1098+
return {}
1099+
1100+
1101+
def obj4852(
1102+
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
1103+
) -> dict[str, Any]:
1104+
"""no-one-duration: uint8: 2/5/10/30 - 2/5/10/30 minutes"""
1105+
device.update_sensor(
1106+
key=ExtendedSensorDeviceClass.NO_ONE_DURATION,
1107+
name="No one duration",
1108+
native_unit_of_measurement=Units.TIME_MINUTES,
1109+
device_class=ExtendedSensorDeviceClass.NO_ONE_DURATION,
1110+
native_value=xobj[0],
1111+
)
1112+
return {}
1113+
1114+
10771115
def obj4a01(
10781116
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
10791117
) -> dict[str, Any]:
@@ -1441,6 +1479,9 @@ def obj4e1c(
14411479
0x4805: obj4805,
14421480
0x4806: obj4806,
14431481
0x4818: obj4818,
1482+
0x484E: obj484e,
1483+
0x4851: obj4851,
1484+
0x4852: obj4852,
14441485
0x4A01: obj4a01,
14451486
0x4A08: obj4a08,
14461487
0x4A0C: obj4a0c,

tests/test_parser.py

+97
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
KEY_BINARY_DOOR = DeviceKey(key="door", device_id=None)
3232
KEY_BINARY_FINGERPRINT = DeviceKey(key="fingerprint", device_id=None)
3333
KEY_BINARY_MOTION = DeviceKey(key="motion", device_id=None)
34+
KEY_BINARY_OCCUPANCY = DeviceKey(key="occupancy", device_id=None)
3435
KEY_BINARY_LIGHT = DeviceKey(key="light", device_id=None)
3536
KEY_BINARY_LOCK = DeviceKey(key="lock", device_id=None)
3637
KEY_BINARY_OPENING = DeviceKey(key="opening", device_id=None)
@@ -3009,6 +3010,102 @@ def test_Xiaomi_XMPIRO2SXS():
30093010
)
30103011

30113012

3013+
def test_Xiaomi_XMOSB01XS_ILLUMINANCE():
3014+
"""Test Xiaomi parser for Xiaomi Occupancy(Human Presence) Sensor XMOSB01XS."""
3015+
data_string = (
3016+
b"\x48\x59\x83\x46\x0D\xDC\x21\x3C\xE9\x81\xDA\x7A\xE2\x02\x00\x44\x41\xF8\x8C"
3017+
)
3018+
advertisement = bytes_to_service_info(data_string, address="0C:43:14:A1:41:1E")
3019+
bindkey = "0a4552cb19a639b72b8ed09bde6d5bfa"
3020+
3021+
device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
3022+
assert device.supported(advertisement)
3023+
assert device.bindkey_verified
3024+
assert device.update(advertisement) == SensorUpdate(
3025+
title="Occupancy Sensor 411E (XMOSB01XS)",
3026+
devices={
3027+
None: SensorDeviceInfo(
3028+
name="Occupancy Sensor 411E",
3029+
manufacturer="Xiaomi",
3030+
model="XMOSB01XS",
3031+
hw_version=None,
3032+
sw_version="Xiaomi (MiBeacon V5 encrypted)",
3033+
)
3034+
},
3035+
entity_descriptions={
3036+
KEY_ILLUMINANCE: SensorDescription(
3037+
device_key=KEY_ILLUMINANCE,
3038+
device_class=DeviceClass.ILLUMINANCE,
3039+
native_unit_of_measurement=Units.LIGHT_LUX,
3040+
),
3041+
KEY_SIGNAL_STRENGTH: SensorDescription(
3042+
device_key=KEY_SIGNAL_STRENGTH,
3043+
device_class=DeviceClass.SIGNAL_STRENGTH,
3044+
native_unit_of_measurement="dBm",
3045+
),
3046+
},
3047+
entity_values={
3048+
KEY_ILLUMINANCE: SensorValue(
3049+
name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=38
3050+
),
3051+
KEY_SIGNAL_STRENGTH: SensorValue(
3052+
name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60
3053+
),
3054+
},
3055+
binary_entity_descriptions={},
3056+
binary_entity_values={},
3057+
)
3058+
3059+
3060+
def test_Xiaomi_XMOSB01XS_OCCUPANCY():
3061+
"""Test Xiaomi parser for Xiaomi Occupancy(Human Presence) Sensor XMOSB01XS."""
3062+
data_string = (
3063+
b"\x58\x59\x83\x46\x1F\xBD\xB1\xC4\x67\x48\xD4"
3064+
b"\x9D\x1E\xFD\x8C\x04\x00\x00\xE5\x7E\x87\x3A"
3065+
)
3066+
advertisement = bytes_to_service_info(data_string, address="D4:48:67:C4:B1:BD")
3067+
bindkey = "920ce119b34410d38251ccea54c0f915"
3068+
3069+
device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
3070+
assert device.supported(advertisement)
3071+
assert device.bindkey_verified
3072+
assert device.update(advertisement) == SensorUpdate(
3073+
title="Occupancy Sensor B1BD (XMOSB01XS)",
3074+
devices={
3075+
None: SensorDeviceInfo(
3076+
name="Occupancy Sensor B1BD",
3077+
manufacturer="Xiaomi",
3078+
model="XMOSB01XS",
3079+
hw_version=None,
3080+
sw_version="Xiaomi (MiBeacon V5 encrypted)",
3081+
)
3082+
},
3083+
entity_descriptions={
3084+
KEY_SIGNAL_STRENGTH: SensorDescription(
3085+
device_key=KEY_SIGNAL_STRENGTH,
3086+
device_class=DeviceClass.SIGNAL_STRENGTH,
3087+
native_unit_of_measurement="dBm",
3088+
),
3089+
},
3090+
entity_values={
3091+
KEY_SIGNAL_STRENGTH: SensorValue(
3092+
name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60
3093+
),
3094+
},
3095+
binary_entity_descriptions={
3096+
KEY_BINARY_OCCUPANCY: BinarySensorDescription(
3097+
device_key=KEY_BINARY_OCCUPANCY,
3098+
device_class=BinarySensorDeviceClass.OCCUPANCY,
3099+
),
3100+
},
3101+
binary_entity_values={
3102+
KEY_BINARY_OCCUPANCY: BinarySensorValue(
3103+
device_key=KEY_BINARY_OCCUPANCY, name="Occupancy", native_value=False
3104+
),
3105+
},
3106+
)
3107+
3108+
30123109
def test_Xiaomi_PTX_press():
30133110
"""Test Xiaomi parser for Xiaomi PTX YK1 QMIMB."""
30143111
bindkey = "a74510b40386d35ae6227a7451efc76e"

0 commit comments

Comments
 (0)