Skip to content

Commit 20fe60f

Browse files
committed
feat: add ptx and xmpiro2sx
1 parent 7fb8287 commit 20fe60f

File tree

3 files changed

+208
-6
lines changed

3 files changed

+208
-6
lines changed

src/xiaomi_ble/devices.py

+8
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ class DeviceEntry:
153153
name="Motion Sensor",
154154
model="RTCGQ02LM",
155155
),
156+
0x3531: DeviceEntry(
157+
name="Motion Sensor",
158+
model="XMPIRO2SXS",
159+
),
156160
0x0863: DeviceEntry(
157161
name="Flood Detector",
158162
model="SJWS01LM",
@@ -197,6 +201,10 @@ class DeviceEntry:
197201
name="Wireless Switch",
198202
model="YLAI003",
199203
),
204+
0x38BB: DeviceEntry(
205+
name="Wireless Switch",
206+
model="PTX",
207+
),
200208
0x0153: DeviceEntry(
201209
name="Remote Control",
202210
model="YLYK01YL",

src/xiaomi_ble/parser.py

+60-6
Original file line numberDiff line numberDiff line change
@@ -990,8 +990,8 @@ def obj3003(
990990

991991

992992
# The following data objects are device specific. For now only added for
993-
# LYWSD02MMC, MJWSD05MMC, XMWSDJ04MMC, XMWXKG01YL, LINPTECH MS1BB(MI), HS1BB(MI), K9BB
994-
# https://miot-spec.org/miot-spec-v2/instances?status=all
993+
# LYWSD02MMC, MJWSD05MMC, XMWSDJ04MMC, XMWXKG01YL, LINPTECH MS1BB(MI), HS1BB(MI),
994+
# K9BB, PTX https://miot-spec.org/miot-spec-v2/instances?status=all
995995
def obj4803(
996996
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
997997
) -> dict[str, Any]:
@@ -1071,6 +1071,42 @@ def obj4a08(
10711071
return {}
10721072

10731073

1074+
def obj4a0c(
1075+
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
1076+
) -> dict[str, Any]:
1077+
"""Single press"""
1078+
device.fire_event(
1079+
key=EventDeviceKeys.BUTTON,
1080+
event_type="press",
1081+
event_properties=None,
1082+
)
1083+
return {}
1084+
1085+
1086+
def obj4a0d(
1087+
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
1088+
) -> dict[str, Any]:
1089+
"""Double press"""
1090+
device.fire_event(
1091+
key=EventDeviceKeys.BUTTON,
1092+
event_type="double_press",
1093+
event_properties=None,
1094+
)
1095+
return {}
1096+
1097+
1098+
def obj4a0e(
1099+
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
1100+
) -> dict[str, Any]:
1101+
"""Long press"""
1102+
device.fire_event(
1103+
key=EventDeviceKeys.BUTTON,
1104+
event_type="long_press",
1105+
event_properties=None,
1106+
)
1107+
return {}
1108+
1109+
10741110
def obj4a0f(
10751111
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
10761112
) -> dict[str, Any]:
@@ -1189,12 +1225,20 @@ def obj4c14(
11891225
return {"mode": mode}
11901226

11911227

1228+
def obj4e01(
1229+
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
1230+
) -> dict[str, Any]:
1231+
"""Low Battery"""
1232+
device.update_predefined_binary_sensor(BinarySensorDeviceClass.BATTERY, xobj[0])
1233+
return {}
1234+
1235+
11921236
def obj4e0c(
11931237
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
11941238
) -> dict[str, Any]:
11951239
"""Button press"""
1196-
press = xobj[0]
11971240
if device_type == "XMWXKG01YL":
1241+
press = xobj[0]
11981242
if press == 1:
11991243
# left button
12001244
device.fire_event(
@@ -1222,6 +1266,7 @@ def obj4e0c(
12221266
event_properties=None,
12231267
)
12241268
elif device_type == "K9BB-1BTN":
1269+
press = xobj[0]
12251270
if press == 1:
12261271
device.fire_event(
12271272
key=EventDeviceKeys.BUTTON,
@@ -1253,8 +1298,8 @@ def obj4e0d(
12531298
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
12541299
) -> dict[str, Any]:
12551300
"""Double Press"""
1256-
press = xobj[0]
12571301
if device_type == "XMWXKG01YL":
1302+
press = xobj[0]
12581303
if press == 1:
12591304
# left button
12601305
device.fire_event(
@@ -1294,8 +1339,8 @@ def obj4e0e(
12941339
xobj: bytes, device: XiaomiBluetoothDeviceData, device_type: str
12951340
) -> dict[str, Any]:
12961341
"""Long Press"""
1297-
press = xobj[0]
12981342
if device_type == "XMWXKG01YL":
1343+
press = xobj[0]
12991344
if press == 1:
13001345
# left button
13011346
device.fire_event(
@@ -1376,6 +1421,9 @@ def obj4e1c(
13761421
0x4818: obj4818,
13771422
0x4A01: obj4a01,
13781423
0x4A08: obj4a08,
1424+
0x4A0C: obj4a0c,
1425+
0x4A0D: obj4a0d,
1426+
0x4A0E: obj4a0e,
13791427
0x4A0F: obj4a0f,
13801428
0x4A12: obj4a12,
13811429
0x4A13: obj4a13,
@@ -1385,6 +1433,7 @@ def obj4e1c(
13851433
0x4C03: obj4c03,
13861434
0x4C08: obj4c08,
13871435
0x4C14: obj4c14,
1436+
0x4E01: obj4e01,
13881437
0x4E1C: obj4e1c,
13891438
0x4E0C: obj4e0c,
13901439
0x4E0D: obj4e0d,
@@ -1713,7 +1762,12 @@ def _parse_xiaomi(
17131762
break
17141763
this_start = payload_start + 3
17151764
dobject = payload[this_start:next_start]
1716-
if obj_length != 0:
1765+
if (
1766+
dobject
1767+
and obj_length != 0
1768+
or hex(obj_typecode)
1769+
in ["0x4a0c", "0x4a0d", "0x4a0e", "0x4e0c", "0x4e0d", "0x4e0e"]
1770+
):
17171771
resfunc = xiaomi_dataobject_dict.get(obj_typecode, None)
17181772
if resfunc:
17191773
self.unhandled.update(resfunc(dobject, self, device_type))

tests/test_parser.py

+140
Original file line numberDiff line numberDiff line change
@@ -2887,5 +2887,145 @@ def test_Xiaomi_RTCGQ02LM_timeout_motion():
28872887
)
28882888

28892889

2890+
def test_Xiaomi_XMPIRO2SXS():
2891+
"""Test Xiaomi parser for Xiaomi Human Body Sensor 2S XMPIRO2SXS."""
2892+
data_string = b"HY15\x0bdy\x91\x173\x1e\xf4\x02\x00\x00\xc5\xd2\xf6\xac"
2893+
advertisement = bytes_to_service_info(data_string, address="DC:8E:95:2D:EA:43")
2894+
bindkey = "685d647dc5e7bc9bcfcf5a1357bd2114"
2895+
2896+
device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
2897+
assert device.supported(advertisement)
2898+
assert device.bindkey_verified
2899+
assert device.update(advertisement) == SensorUpdate(
2900+
title="Motion Sensor EA43 (XMPIRO2SXS)",
2901+
devices={
2902+
None: SensorDeviceInfo(
2903+
name="Motion Sensor EA43",
2904+
manufacturer="Xiaomi",
2905+
model="XMPIRO2SXS",
2906+
hw_version=None,
2907+
sw_version="Xiaomi (MiBeacon V5 encrypted)",
2908+
)
2909+
},
2910+
entity_descriptions={
2911+
KEY_ILLUMINANCE: SensorDescription(
2912+
device_key=KEY_ILLUMINANCE,
2913+
device_class=DeviceClass.ILLUMINANCE,
2914+
native_unit_of_measurement=Units.LIGHT_LUX,
2915+
),
2916+
KEY_SIGNAL_STRENGTH: SensorDescription(
2917+
device_key=KEY_SIGNAL_STRENGTH,
2918+
device_class=DeviceClass.SIGNAL_STRENGTH,
2919+
native_unit_of_measurement="dBm",
2920+
),
2921+
},
2922+
entity_values={
2923+
KEY_ILLUMINANCE: SensorValue(
2924+
name="Illuminance", device_key=KEY_ILLUMINANCE, native_value=51
2925+
),
2926+
KEY_SIGNAL_STRENGTH: SensorValue(
2927+
name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60
2928+
),
2929+
},
2930+
binary_entity_descriptions={
2931+
KEY_BINARY_MOTION: BinarySensorDescription(
2932+
device_key=KEY_BINARY_MOTION,
2933+
device_class=BinarySensorDeviceClass.MOTION,
2934+
),
2935+
},
2936+
binary_entity_values={
2937+
KEY_BINARY_MOTION: BinarySensorValue(
2938+
device_key=KEY_BINARY_MOTION, name="Motion", native_value=True
2939+
),
2940+
},
2941+
)
2942+
2943+
2944+
def test_Xiaomi_PTX_press():
2945+
"""Test Xiaomi parser for Xiaomi PTX."""
2946+
bindkey = "a74510b40386d35ae6227a7451efc76e"
2947+
data_string = b"XY\xbb8\x04\xad\xb9\xa58\xc1\xa4\xdc\x10\xb5\x04\x00\x00,\x12/\xb6"
2948+
advertisement = bytes_to_service_info(data_string, address="A4:C1:38:A5:B9:AD")
2949+
2950+
device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
2951+
assert device.supported(advertisement)
2952+
assert device.bindkey_verified
2953+
assert device.update(advertisement) == SensorUpdate(
2954+
title="Wireless Switch B9AD (PTX)",
2955+
devices={
2956+
None: SensorDeviceInfo(
2957+
name="Wireless Switch B9AD",
2958+
manufacturer="Xiaomi",
2959+
model="PTX",
2960+
hw_version=None,
2961+
sw_version="Xiaomi (MiBeacon V5 encrypted)",
2962+
)
2963+
},
2964+
entity_descriptions={
2965+
KEY_SIGNAL_STRENGTH: SensorDescription(
2966+
device_key=KEY_SIGNAL_STRENGTH,
2967+
device_class=DeviceClass.SIGNAL_STRENGTH,
2968+
native_unit_of_measurement="dBm",
2969+
),
2970+
},
2971+
entity_values={
2972+
KEY_SIGNAL_STRENGTH: SensorValue(
2973+
name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60
2974+
),
2975+
},
2976+
events={
2977+
KEY_EVENT_BUTTON: Event(
2978+
device_key=KEY_EVENT_BUTTON,
2979+
name="Button",
2980+
event_type="press",
2981+
event_properties=None,
2982+
),
2983+
},
2984+
)
2985+
2986+
2987+
def test_Xiaomi_XMWXKG01LM_press():
2988+
"""Test Xiaomi parser for Xiaomi XMWXKG01LM ."""
2989+
bindkey = "7202a2d4201bbf82ea5bb3705657c32a"
2990+
data_string = b"XY\x87#5\x057$<\xc2\x18\xd6w\x94\x02\x00\x00\xcb-\xe3\t"
2991+
advertisement = bytes_to_service_info(data_string, address="18:C2:3C:24:37:05")
2992+
2993+
device = XiaomiBluetoothDeviceData(bindkey=bytes.fromhex(bindkey))
2994+
assert device.supported(advertisement)
2995+
assert device.bindkey_verified
2996+
assert device.update(advertisement) == SensorUpdate(
2997+
title="Button 3705 (XMWXKG01LM)",
2998+
devices={
2999+
None: SensorDeviceInfo(
3000+
name="Button 3705",
3001+
manufacturer="Xiaomi",
3002+
model="XMWXKG01LM",
3003+
hw_version=None,
3004+
sw_version="Xiaomi (MiBeacon V5 encrypted)",
3005+
)
3006+
},
3007+
entity_descriptions={
3008+
KEY_SIGNAL_STRENGTH: SensorDescription(
3009+
device_key=KEY_SIGNAL_STRENGTH,
3010+
device_class=DeviceClass.SIGNAL_STRENGTH,
3011+
native_unit_of_measurement="dBm",
3012+
),
3013+
},
3014+
entity_values={
3015+
KEY_SIGNAL_STRENGTH: SensorValue(
3016+
name="Signal Strength", device_key=KEY_SIGNAL_STRENGTH, native_value=-60
3017+
),
3018+
},
3019+
events={
3020+
KEY_EVENT_BUTTON: Event(
3021+
device_key=KEY_EVENT_BUTTON,
3022+
name="Button",
3023+
event_type="press",
3024+
event_properties=None,
3025+
),
3026+
},
3027+
)
3028+
3029+
28903030
def test_can_create():
28913031
XiaomiBluetoothDeviceData()

0 commit comments

Comments
 (0)