diff --git a/homeassistant/components/xiaomi_ble/binary_sensor.py b/homeassistant/components/xiaomi_ble/binary_sensor.py index 6fc6c3c2761..58d4787bd4a 100644 --- a/homeassistant/components/xiaomi_ble/binary_sensor.py +++ b/homeassistant/components/xiaomi_ble/binary_sensor.py @@ -5,6 +5,7 @@ from typing import Optional from xiaomi_ble.parser import ( BinarySensorDeviceClass as XiaomiBinarySensorDeviceClass, + ExtendedBinarySensorDeviceClass, SensorUpdate, ) @@ -28,20 +29,48 @@ from .const import DOMAIN from .device import device_key_to_bluetooth_entity_key BINARY_SENSOR_DESCRIPTIONS = { - XiaomiBinarySensorDeviceClass.MOTION: BinarySensorEntityDescription( - key=XiaomiBinarySensorDeviceClass.MOTION, - device_class=BinarySensorDeviceClass.MOTION, + XiaomiBinarySensorDeviceClass.DOOR: BinarySensorEntityDescription( + key=XiaomiBinarySensorDeviceClass.DOOR, + device_class=BinarySensorDeviceClass.DOOR, ), XiaomiBinarySensorDeviceClass.LIGHT: BinarySensorEntityDescription( key=XiaomiBinarySensorDeviceClass.LIGHT, device_class=BinarySensorDeviceClass.LIGHT, ), + XiaomiBinarySensorDeviceClass.MOISTURE: BinarySensorEntityDescription( + key=XiaomiBinarySensorDeviceClass.MOISTURE, + device_class=BinarySensorDeviceClass.MOISTURE, + ), + XiaomiBinarySensorDeviceClass.MOTION: BinarySensorEntityDescription( + key=XiaomiBinarySensorDeviceClass.MOTION, + device_class=BinarySensorDeviceClass.MOTION, + ), + XiaomiBinarySensorDeviceClass.OPENING: BinarySensorEntityDescription( + key=XiaomiBinarySensorDeviceClass.OPENING, + device_class=BinarySensorDeviceClass.OPENING, + ), XiaomiBinarySensorDeviceClass.SMOKE: BinarySensorEntityDescription( key=XiaomiBinarySensorDeviceClass.SMOKE, device_class=BinarySensorDeviceClass.SMOKE, ), - XiaomiBinarySensorDeviceClass.MOISTURE: BinarySensorEntityDescription( - key=XiaomiBinarySensorDeviceClass.MOISTURE, + ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, + device_class=BinarySensorDeviceClass.PROBLEM, + ), + ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.DOOR_LEFT_OPEN, + device_class=BinarySensorDeviceClass.PROBLEM, + ), + ExtendedBinarySensorDeviceClass.DOOR_STUCK: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.DOOR_STUCK, + device_class=BinarySensorDeviceClass.PROBLEM, + ), + ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR, + ), + ExtendedBinarySensorDeviceClass.PRY_THE_DOOR: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.PRY_THE_DOOR, + device_class=BinarySensorDeviceClass.TAMPER, ), } diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 3f02c4e8767..93c27027511 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -13,7 +13,7 @@ "service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["xiaomi-ble==0.12.2"], + "requirements": ["xiaomi-ble==0.14.3"], "dependencies": ["bluetooth"], "codeowners": ["@Jc2k", "@Ernst79"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 164c7e0ffed..00ecac68b38 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2597,7 +2597,7 @@ xbox-webapi==2.0.11 xboxapi==2.0.1 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.12.2 +xiaomi-ble==0.14.3 # homeassistant.components.knx xknx==2.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 719ad0921c0..ce743482454 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1816,7 +1816,7 @@ wolf_smartset==0.1.11 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.12.2 +xiaomi-ble==0.14.3 # homeassistant.components.knx xknx==2.2.0 diff --git a/tests/components/xiaomi_ble/test_binary_sensor.py b/tests/components/xiaomi_ble/test_binary_sensor.py index eb369f20268..5389a2987f2 100644 --- a/tests/components/xiaomi_ble/test_binary_sensor.py +++ b/tests/components/xiaomi_ble/test_binary_sensor.py @@ -9,12 +9,12 @@ from tests.common import MockConfigEntry from tests.components.bluetooth import inject_bluetooth_service_info_bleak -async def test_smoke_sensor(hass): - """Test setting up a smoke sensor.""" +async def test_door_problem_sensors(hass): + """Test setting up a door binary sensor with additional problem sensors.""" entry = MockConfigEntry( domain=DOMAIN, - unique_id="54:EF:44:E3:9C:BC", - data={"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}, + unique_id="EE:89:73:44:BE:98", + data={"bindkey": "2c3795afa33019a8afdc17ba99e6f217"}, ) entry.add_to_hass(hass) @@ -25,24 +25,72 @@ async def test_smoke_sensor(hass): inject_bluetooth_service_info_bleak( hass, make_advertisement( - "54:EF:44:E3:9C:BC", - b"XY\x97\tf\xbc\x9c\xe3D\xefT\x01" b"\x08\x12\x05\x00\x00\x00q^\xbe\x90", + "EE:89:73:44:BE:98", + b"HU9\x0e3\x9cq\xc0$\x1f\xff\xee\x80S\x00\x00\x02\xb4\xc59", ), ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 1 + assert len(hass.states.async_all()) == 3 - smoke_sensor = hass.states.get("binary_sensor.thermometer_9cbc_smoke") - smoke_sensor_attribtes = smoke_sensor.attributes - assert smoke_sensor.state == "on" - assert smoke_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Thermometer 9CBC Smoke" + door_sensor = hass.states.get("binary_sensor.door_lock_be98_door") + door_sensor_attribtes = door_sensor.attributes + assert door_sensor.state == "off" + assert door_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Door Lock BE98 Door" + + door_left_open = hass.states.get("binary_sensor.door_lock_be98_door_left_open") + door_left_open_attribtes = door_left_open.attributes + assert door_left_open.state == "off" + assert ( + door_left_open_attribtes[ATTR_FRIENDLY_NAME] == "Door Lock BE98 Door left open" + ) + + pry_the_door = hass.states.get("binary_sensor.door_lock_be98_pry_the_door") + pry_the_door_attribtes = pry_the_door.attributes + assert pry_the_door.state == "off" + assert pry_the_door_attribtes[ATTR_FRIENDLY_NAME] == "Door Lock BE98 Pry the door" + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + +async def test_light_motion(hass): + """Test setting up a light and motion binary sensor.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="58:2D:34:35:93:21", + ) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + "58:2D:34:35:93:21", + b"P \xf6\x07\xda!\x9354-X\x0f\x00\x03\x01\x00\x00", + ), + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 2 + + motion_sensor = hass.states.get("binary_sensor.nightlight_9321_motion") + motion_sensor_attribtes = motion_sensor.attributes + assert motion_sensor.state == "on" + assert motion_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Nightlight 9321 Motion" + + light_sensor = hass.states.get("binary_sensor.nightlight_9321_light") + light_sensor_attribtes = light_sensor.attributes + assert light_sensor.state == "off" + assert light_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Nightlight 9321 Light" assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() async def test_moisture(hass): - """Make sure that formldehyde sensors are correctly mapped.""" + """Test setting up a moisture binary sensor.""" entry = MockConfigEntry( domain=DOMAIN, unique_id="C4:7C:8D:6A:3E:7A", @@ -73,3 +121,125 @@ async def test_moisture(hass): assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() + + +async def test_opening(hass): + """Test setting up a opening binary sensor.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="A4:C1:38:66:E5:67", + data={"bindkey": "0fdcc30fe9289254876b5ef7c11ef1f0"}, + ) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + "A4:C1:38:66:E5:67", + b"XY\x89\x18\x9ag\xe5f8\xc1\xa4\x9d\xd9z\xf3&\x00\x00\xc8\xa6\x0b\xd5", + ), + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 1 + + opening_sensor = hass.states.get("binary_sensor.door_window_sensor_e567_opening") + opening_sensor_attribtes = opening_sensor.attributes + assert opening_sensor.state == "on" + assert ( + opening_sensor_attribtes[ATTR_FRIENDLY_NAME] + == "Door/Window Sensor E567 Opening" + ) + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + +async def test_opening_problem_sensors(hass): + """Test setting up a opening binary sensor with additional problem sensors.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="A4:C1:38:66:E5:67", + data={"bindkey": "0fdcc30fe9289254876b5ef7c11ef1f0"}, + ) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + "A4:C1:38:66:E5:67", + b"XY\x89\x18ug\xe5f8\xc1\xa4i\xdd\xf3\xa1&\x00\x00\xa2J\x1bE", + ), + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 3 + + opening_sensor = hass.states.get("binary_sensor.door_window_sensor_e567_opening") + opening_sensor_attribtes = opening_sensor.attributes + assert opening_sensor.state == "off" + assert ( + opening_sensor_attribtes[ATTR_FRIENDLY_NAME] + == "Door/Window Sensor E567 Opening" + ) + + door_left_open = hass.states.get( + "binary_sensor.door_window_sensor_e567_door_left_open" + ) + door_left_open_attribtes = door_left_open.attributes + assert door_left_open.state == "off" + assert ( + door_left_open_attribtes[ATTR_FRIENDLY_NAME] + == "Door/Window Sensor E567 Door left open" + ) + + device_forcibly_removed = hass.states.get( + "binary_sensor.door_window_sensor_e567_device_forcibly_removed" + ) + device_forcibly_removed_attribtes = device_forcibly_removed.attributes + assert device_forcibly_removed.state == "off" + assert ( + device_forcibly_removed_attribtes[ATTR_FRIENDLY_NAME] + == "Door/Window Sensor E567 Device forcibly removed" + ) + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + +async def test_smoke(hass): + """Test setting up a smoke binary sensor.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="54:EF:44:E3:9C:BC", + data={"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}, + ) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + "54:EF:44:E3:9C:BC", + b"XY\x97\tf\xbc\x9c\xe3D\xefT\x01" b"\x08\x12\x05\x00\x00\x00q^\xbe\x90", + ), + ) + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 1 + + smoke_sensor = hass.states.get("binary_sensor.thermometer_9cbc_smoke") + smoke_sensor_attribtes = smoke_sensor.attributes + assert smoke_sensor.state == "on" + assert smoke_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Thermometer 9CBC Smoke" + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done()