diff --git a/homeassistant/components/xiaomi_ble/binary_sensor.py b/homeassistant/components/xiaomi_ble/binary_sensor.py index 2e2c3251add..c8d4666e482 100644 --- a/homeassistant/components/xiaomi_ble/binary_sensor.py +++ b/homeassistant/components/xiaomi_ble/binary_sensor.py @@ -42,6 +42,10 @@ BINARY_SENSOR_DESCRIPTIONS = { key=XiaomiBinarySensorDeviceClass.LIGHT, device_class=BinarySensorDeviceClass.LIGHT, ), + XiaomiBinarySensorDeviceClass.LOCK: BinarySensorEntityDescription( + key=XiaomiBinarySensorDeviceClass.LOCK, + device_class=BinarySensorDeviceClass.LOCK, + ), XiaomiBinarySensorDeviceClass.MOISTURE: BinarySensorEntityDescription( key=XiaomiBinarySensorDeviceClass.MOISTURE, device_class=BinarySensorDeviceClass.MOISTURE, @@ -62,6 +66,16 @@ BINARY_SENSOR_DESCRIPTIONS = { key=XiaomiBinarySensorDeviceClass.SMOKE, device_class=BinarySensorDeviceClass.SMOKE, ), + ExtendedBinarySensorDeviceClass.ANTILOCK: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.ANTILOCK, + ), + ExtendedBinarySensorDeviceClass.ARMED: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.ARMED, + icon="mdi:shield-check", + ), + ExtendedBinarySensorDeviceClass.CHILDLOCK: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.CHILDLOCK, + ), ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED: BinarySensorEntityDescription( key=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, device_class=BinarySensorDeviceClass.PROBLEM, @@ -74,6 +88,10 @@ BINARY_SENSOR_DESCRIPTIONS = { key=ExtendedBinarySensorDeviceClass.DOOR_STUCK, device_class=BinarySensorDeviceClass.PROBLEM, ), + ExtendedBinarySensorDeviceClass.FINGERPRINT: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.FINGERPRINT, + icon="mdi:fingerprint", + ), ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR: BinarySensorEntityDescription( key=ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR, ), diff --git a/homeassistant/components/xiaomi_ble/const.py b/homeassistant/components/xiaomi_ble/const.py index a421dda7195..8ea99cf1f84 100644 --- a/homeassistant/components/xiaomi_ble/const.py +++ b/homeassistant/components/xiaomi_ble/const.py @@ -20,13 +20,21 @@ EVENT_PROPERTIES: Final = "event_properties" XIAOMI_BLE_EVENT: Final = "xiaomi_ble_event" EVENT_CLASS_BUTTON: Final = "button" -EVENT_CLASS_DIMMER: Final = "dimmer" -EVENT_CLASS_MOTION: Final = "motion" EVENT_CLASS_CUBE: Final = "cube" +EVENT_CLASS_DIMMER: Final = "dimmer" +EVENT_CLASS_ERROR: Final = "error" +EVENT_CLASS_FINGERPRINT: Final = "fingerprint" +EVENT_CLASS_LOCK: Final = "lock" +EVENT_CLASS_MOTION: Final = "motion" BUTTON: Final = "button" CUBE: Final = "cube" DIMMER: Final = "dimmer" +ERROR: Final = "error" +FINGERPRINT: Final = "fingerprint" +LOCK: Final = "lock" +LOCK_FINGERPRINT = "lock_fingerprint" +MOTION_DEVICE: Final = "motion_device" DOUBLE_BUTTON: Final = "double_button" TRIPPLE_BUTTON: Final = "tripple_button" REMOTE: Final = "remote" @@ -40,7 +48,6 @@ BUTTON_PRESS_LONG: Final = "button_press_long" BUTTON_PRESS_DOUBLE_LONG: Final = "button_press_double_long" DOUBLE_BUTTON_PRESS_DOUBLE_LONG: Final = "double_button_press_double_long" TRIPPLE_BUTTON_PRESS_DOUBLE_LONG: Final = "tripple_button_press_double_long" -MOTION_DEVICE: Final = "motion_device" class XiaomiBleEvent(TypedDict): diff --git a/homeassistant/components/xiaomi_ble/device_trigger.py b/homeassistant/components/xiaomi_ble/device_trigger.py index eb345e91094..86749bd8dd3 100644 --- a/homeassistant/components/xiaomi_ble/device_trigger.py +++ b/homeassistant/components/xiaomi_ble/device_trigger.py @@ -32,12 +32,19 @@ from .const import ( DOMAIN, DOUBLE_BUTTON, DOUBLE_BUTTON_PRESS_DOUBLE_LONG, + ERROR, EVENT_CLASS, EVENT_CLASS_BUTTON, EVENT_CLASS_CUBE, EVENT_CLASS_DIMMER, + EVENT_CLASS_ERROR, + EVENT_CLASS_FINGERPRINT, + EVENT_CLASS_LOCK, EVENT_CLASS_MOTION, EVENT_TYPE, + FINGERPRINT, + LOCK, + LOCK_FINGERPRINT, MOTION, MOTION_DEVICE, REMOTE, @@ -62,6 +69,51 @@ TRIGGERS_BY_TYPE = { "rotate_left_pressed", "rotate_right_pressed", ], + ERROR: [ + "frequent_unlocking_with_incorrect_password", + "frequent_unlocking_with_wrong_fingerprints", + "operation_timeout_password_input_timeout", + "lock_picking", + "reset_button_is_pressed", + "the_wrong_key_is_frequently_unlocked", + "foreign_body_in_the_keyhole", + "the_key_has_not_been_taken_out", + "error_nfc_frequently_unlocks", + "timeout_is_not_locked_as_required", + "failure_to_unlock_frequently_in_multiple_ways", + "unlocking_the_face_frequently_fails", + "failure_to_unlock_the_vein_frequently", + "hijacking_alarm", + "unlock_inside_the_door_after_arming", + "palmprints_frequently_fail_to_unlock", + "the_safe_was_moved", + "the_battery_level_is_less_than_10_percent", + "the_battery_level_is_less_than_5_percent", + "the_fingerprint_sensor_is_abnormal", + "the_accessory_battery_is_low", + "mechanical_failure", + "the_lock_sensor_is_faulty", + ], + FINGERPRINT: [ + "match_successful", + "match_failed", + "low_quality_too_light_fuzzy", + "insufficient_area", + "skin_is_too_dry", + "skin_is_too_wet", + ], + LOCK: [ + "lock_outside_the_door", + "unlock_outside_the_door", + "lock_inside_the_door", + "unlock_inside_the_door", + "locked", + "turn_on_antilock", + "release_the_antilock", + "turn_on_child_lock", + "turn_off_child_lock", + "abnormal", + ], MOTION_DEVICE: ["motion_detected"], } @@ -71,6 +123,10 @@ EVENT_TYPES = { DIMMER: ["dimmer"], DOUBLE_BUTTON: ["button_left", "button_right"], TRIPPLE_BUTTON: ["button_left", "button_middle", "button_right"], + ERROR: ["error"], + FINGERPRINT: ["fingerprint"], + LOCK: ["lock"], + MOTION: ["motion"], REMOTE: [ "button_on", "button_off", @@ -106,7 +162,6 @@ EVENT_TYPES = { "button_increase_wind_speed", "button_decrease_wind_speed", ], - MOTION: ["motion"], } @@ -150,6 +205,26 @@ TRIGGER_MODEL_DATA = { event_types=EVENT_TYPES[TRIPPLE_BUTTON], triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG], ), + ERROR: TriggerModelData( + event_class=EVENT_CLASS_ERROR, + event_types=EVENT_TYPES[ERROR], + triggers=TRIGGERS_BY_TYPE[ERROR], + ), + LOCK_FINGERPRINT: TriggerModelData( + event_class=EVENT_CLASS_FINGERPRINT, + event_types=EVENT_TYPES[LOCK] + EVENT_TYPES[FINGERPRINT], + triggers=TRIGGERS_BY_TYPE[LOCK] + TRIGGERS_BY_TYPE[FINGERPRINT], + ), + LOCK: TriggerModelData( + event_class=EVENT_CLASS_LOCK, + event_types=EVENT_TYPES[LOCK], + triggers=TRIGGERS_BY_TYPE[LOCK], + ), + MOTION_DEVICE: TriggerModelData( + event_class=EVENT_CLASS_MOTION, + event_types=EVENT_TYPES[MOTION], + triggers=TRIGGERS_BY_TYPE[MOTION_DEVICE], + ), REMOTE: TriggerModelData( event_class=EVENT_CLASS_BUTTON, event_types=EVENT_TYPES[REMOTE], @@ -170,11 +245,6 @@ TRIGGER_MODEL_DATA = { event_types=EVENT_TYPES[REMOTE_VENFAN], triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG], ), - MOTION_DEVICE: TriggerModelData( - event_class=EVENT_CLASS_MOTION, - event_types=EVENT_TYPES[MOTION], - triggers=TRIGGERS_BY_TYPE[MOTION_DEVICE], - ), } @@ -197,6 +267,13 @@ MODEL_DATA = { "MUE4094RT": TRIGGER_MODEL_DATA[MOTION_DEVICE], "XMMF01JQD": TRIGGER_MODEL_DATA[CUBE], "YLKG07YL/YLKG08YL": TRIGGER_MODEL_DATA[DIMMER], + "DSL-C08": TRIGGER_MODEL_DATA[LOCK], + "XMZNMS08LM": TRIGGER_MODEL_DATA[LOCK], + "Lockin-SV40": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], + "MJZNMSQ01YD": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], + "XMZNMS04LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], + "ZNMS16LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], + "ZNMS17LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], } diff --git a/homeassistant/components/xiaomi_ble/event.py b/homeassistant/components/xiaomi_ble/event.py index 4b0f9a61433..e39a4adb3c7 100644 --- a/homeassistant/components/xiaomi_ble/event.py +++ b/homeassistant/components/xiaomi_ble/event.py @@ -21,6 +21,9 @@ from .const import ( EVENT_CLASS_BUTTON, EVENT_CLASS_CUBE, EVENT_CLASS_DIMMER, + EVENT_CLASS_ERROR, + EVENT_CLASS_FINGERPRINT, + EVENT_CLASS_LOCK, EVENT_CLASS_MOTION, EVENT_PROPERTIES, EVENT_TYPE, @@ -59,6 +62,63 @@ DESCRIPTIONS_BY_EVENT_CLASS = { "rotate_right_pressed", ], ), + EVENT_CLASS_ERROR: EventEntityDescription( + key=EVENT_CLASS_ERROR, + translation_key="error", + event_types=[ + "frequent_unlocking_with_incorrect_password", + "frequent_unlocking_with_wrong_fingerprints", + "operation_timeout_password_input_timeout", + "lock_picking", + "reset_button_is_pressed", + "the_wrong_key_is_frequently_unlocked", + "foreign_body_in_the_keyhole", + "the_key_has_not_been_taken_out", + "error_nfc_frequently_unlocks", + "timeout_is_not_locked_as_required", + "failure_to_unlock_frequently_in_multiple_ways", + "unlocking_the_face_frequently_fails", + "failure_to_unlock_the_vein_frequently", + "hijacking_alarm", + "unlock_inside_the_door_after_arming", + "palmprints_frequently_fail_to_unlock", + "the_safe_was_moved", + "the_battery_level_is_less_than_10_percent", + "the_battery_is_less_than_5_percent", + "the_fingerprint_sensor_is_abnormal", + "the_accessory_battery_is_low", + "mechanical_failure", + "the_lock_sensor_is_faulty", + ], + ), + EVENT_CLASS_FINGERPRINT: EventEntityDescription( + key=EVENT_CLASS_FINGERPRINT, + translation_key="fingerprint", + event_types=[ + "match_successful", + "match_failed", + "low_quality_too_light_fuzzy", + "insufficient_area", + "skin_is_too_dry", + "skin_is_too_wet", + ], + ), + EVENT_CLASS_LOCK: EventEntityDescription( + key=EVENT_CLASS_LOCK, + translation_key="lock", + event_types=[ + "lock_outside_the_door", + "unlock_outside_the_door", + "lock_inside_the_door", + "unlock_inside_the_door", + "locked", + "turn_on_antilock", + "release_the_antilock", + "turn_on_child_lock", + "turn_off_child_lock", + "abnormal", + ], + ), EVENT_CLASS_MOTION: EventEntityDescription( key=EVENT_CLASS_MOTION, translation_key="motion", diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index a380ecb8e94..603a2dc2c71 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "iot_class": "local_push", - "requirements": ["xiaomi-ble==0.25.2"] + "requirements": ["xiaomi-ble==0.26.1"] } diff --git a/homeassistant/components/xiaomi_ble/sensor.py b/homeassistant/components/xiaomi_ble/sensor.py index 2bcc67e2668..c5354a54394 100644 --- a/homeassistant/components/xiaomi_ble/sensor.py +++ b/homeassistant/components/xiaomi_ble/sensor.py @@ -132,23 +132,31 @@ SENSOR_DESCRIPTIONS = { state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, ), - # Used for e.g. consumable sensor on WX08ZM and M1S-T500 + # E.g. consumable sensor on WX08ZM and M1S-T500 (ExtendedSensorDeviceClass.CONSUMABLE, Units.PERCENTAGE): SensorEntityDescription( key=str(ExtendedSensorDeviceClass.CONSUMABLE), native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), - # Used for score after brushing with a toothbrush + # Score after brushing with a toothbrush (ExtendedSensorDeviceClass.SCORE, None): SensorEntityDescription( key=str(ExtendedSensorDeviceClass.SCORE), state_class=SensorStateClass.MEASUREMENT, ), - # Used for counting during brushing + # Counting during brushing (ExtendedSensorDeviceClass.COUNTER, Units.TIME_SECONDS): SensorEntityDescription( key=str(ExtendedSensorDeviceClass.COUNTER), native_unit_of_measurement=UnitOfTime.SECONDS, state_class=SensorStateClass.MEASUREMENT, ), + # Key id for locks and fingerprint readers + (ExtendedSensorDeviceClass.KEY_ID, None): SensorEntityDescription( + key=str(ExtendedSensorDeviceClass.KEY_ID), icon="mdi:identifier" + ), + # Lock method for locks + (ExtendedSensorDeviceClass.LOCK_METHOD, None): SensorEntityDescription( + key=str(ExtendedSensorDeviceClass.LOCK_METHOD), icon="mdi:key-variant" + ), } diff --git a/homeassistant/components/xiaomi_ble/strings.json b/homeassistant/components/xiaomi_ble/strings.json index d764a436f4c..2f2b705ff60 100644 --- a/homeassistant/components/xiaomi_ble/strings.json +++ b/homeassistant/components/xiaomi_ble/strings.json @@ -48,7 +48,23 @@ "rotate_left": "Rotate Left", "rotate_right": "Rotate Right", "rotate_left_pressed": "Rotate Left (Pressed)", - "rotate_right_pressed": "Rotate Right (Pressed)" + "rotate_right_pressed": "Rotate Right (Pressed)", + "match_successful": "Match successful", + "match_failed": "Match failed", + "low_quality_too_light_fuzzy": "Low quality (too light, fuzzy)", + "insufficient_area": "Insufficient area", + "skin_is_too_dry": "Skin is too dry", + "skin_is_too_wet": "Skin is too wet", + "lock_outside_the_door": "Lock outside the door", + "unlock_outside_the_door": "Unlock outside the door", + "lock_inside_the_door": "Lock inside the door", + "unlock_inside_the_door": "Unlock outside the door", + "locked": "Locked", + "turn_on_antilock": "Turn on antilock", + "release_the_antilock": "Release antilock", + "turn_on_child_lock": "Turn on child lock", + "turn_off_child_lock": "Turn off child lock", + "abnormal": "Abnormal" }, "trigger_type": { "button": "Button \"{subtype}\"", @@ -79,6 +95,8 @@ "button_increase_wind_speed": "Button Increase Wind Speed \"{subtype}\"", "button_decrease_wind_speed": "Button Decrease Wind Speed \"{subtype}\"", "dimmer": "{subtype}", + "fingerprint": "{subtype}", + "lock": "{subtype}", "motion": "{subtype}", "cube": "{subtype}" } @@ -120,6 +138,69 @@ } } }, + "error": { + "state_attributes": { + "event_type": { + "state": { + "frequent_unlocking_with_incorrect_password": "Frequent unlocking with incorrect password", + "frequent_unlocking_with_wrong_fingerprints": "Frequent unlocking with wrong fingerprints", + "operation_timeout_password_input_timeout": "Operation timeout password input timeout", + "lock_picking": "Lock picking", + "reset_button_is_pressed": "Reset button is pressed", + "the_wrong_key_is_frequently_unlocked": "The wrong key is frequently unlocked", + "foreign_body_in_the_keyhole": "Foreign body in the keyhole", + "the_key_has_not_been_taken_out": "The key has not been taken out", + "error_nfc_frequently_unlocks": "Error NFC frequently unlocks", + "timeout_is_not_locked_as_required": "Timeout is not locked as required", + "failure_to_unlock_frequently_in_multiple_ways": "Failure to unlock frequently in multiple ways", + "unlocking_the_face_frequently_fails": "Unlocking the face frequently fails", + "failure_to_unlock_the_vein_frequently": "Failure to unlock the vein frequently", + "hijacking_alarm": "Hijacking alarm", + "unlock_inside_the_door_after_arming": "Unlock inside the door after arming", + "palmprints_frequently_fail_to_unlock": "Palmprints frequently fail to unlock", + "the_safe_was_moved": "The safe was moved", + "the_battery_level_is_less_than_10_percent": "The battery level is less than 10%", + "the_battery_level_is_less_than_5_percent": "The battery level is less than 5%", + "the_fingerprint_sensor_is_abnormal": "The fingerprint sensor is abnormal", + "the_accessory_battery_is_low": "The accessory battery is low", + "mechanical_failure": "Mechanical failure", + "the_lock_sensor_is_faulty": "The lock sensor is faulty" + } + } + } + }, + "fingerprint": { + "state_attributes": { + "event_type": { + "state": { + "match_successful": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::match_successful%]", + "match_failed": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::match_failed%]", + "low_quality_too_light_fuzzy": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::low_quality_too_light_fuzzy%]", + "insufficient_area": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::insufficient_area%]", + "skin_is_too_dry": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::skin_is_too_dry%]", + "skin_is_too_wet": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::skin_is_too_wet%]" + } + } + } + }, + "lock": { + "state_attributes": { + "event_type": { + "state": { + "lock_outside_the_door": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::lock_outside_the_door%]", + "unlock_outside_the_door": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::unlock_outside_the_door%]", + "lock_inside_the_door": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::lock_inside_the_door%]", + "unlock_inside_the_door": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::unlock_inside_the_door%]", + "locked": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::locked%]", + "turn_on_antilock": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::turn_on_antilock%]", + "release_the_antilock": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::release_the_antilock%]", + "turn_on_child_lock": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::turn_on_child_lock%]", + "turn_off_child_lock": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::turn_off_child_lock%]", + "abnormal": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::abnormal%]" + } + } + } + }, "motion": { "state_attributes": { "event_type": { diff --git a/requirements_all.txt b/requirements_all.txt index 73440b45d50..db14d8f521d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2866,7 +2866,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.25.2 +xiaomi-ble==0.26.1 # homeassistant.components.knx xknx==2.12.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8fc51989fbf..a3d4c19621b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2204,7 +2204,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.25.2 +xiaomi-ble==0.26.1 # homeassistant.components.knx xknx==2.12.2 diff --git a/tests/components/xiaomi_ble/test_device_trigger.py b/tests/components/xiaomi_ble/test_device_trigger.py index 6c6ad1c0f94..49619af618e 100644 --- a/tests/components/xiaomi_ble/test_device_trigger.py +++ b/tests/components/xiaomi_ble/test_device_trigger.py @@ -75,6 +75,58 @@ async def test_event_button_press(hass: HomeAssistant) -> None: await hass.async_block_till_done() +async def test_event_unlock_outside_the_door(hass: HomeAssistant) -> None: + """Make sure that a unlock outside the door event is fired.""" + mac = "D7:1F:44:EB:8A:91" + entry = await _async_setup_xiaomi_device(hass, mac) + events = async_capture_events(hass, "xiaomi_ble_event") + + # Emit button press event + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + mac, + b"PD\x9e\x06C\x91\x8a\xebD\x1f\xd7\x0b\x00\t" b" \x02\x00\x01\x80|D/a", + ), + ) + + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["address"] == "D7:1F:44:EB:8A:91" + assert events[0].data["event_type"] == "unlock_outside_the_door" + assert events[0].data["event_properties"] is None + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + +async def test_event_successful_fingerprint_match_the_door(hass: HomeAssistant) -> None: + """Make sure that a successful fingerprint match event is fired.""" + mac = "D7:1F:44:EB:8A:91" + entry = await _async_setup_xiaomi_device(hass, mac) + events = async_capture_events(hass, "xiaomi_ble_event") + + # Emit button press event + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + mac, + b"PD\x9e\x06B\x91\x8a\xebD\x1f\xd7" b"\x06\x00\x05\xff\xff\xff\xff\x00", + ), + ) + + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["address"] == "D7:1F:44:EB:8A:91" + assert events[0].data["event_type"] == "match_successful" + assert events[0].data["event_properties"] is None + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + async def test_event_motion_detected(hass: HomeAssistant) -> None: """Make sure that a motion detected event is fired.""" mac = "DE:70:E8:B2:39:0C" @@ -204,6 +256,47 @@ async def test_get_triggers_double_button(hass: HomeAssistant) -> None: await hass.async_block_till_done() +async def test_get_triggers_lock(hass: HomeAssistant) -> None: + """Test that we get the expected triggers from a Xiaomi BLE lock with fingerprint scanner.""" + mac = "98:0C:33:A3:04:3D" + data = {"bindkey": "54d84797cb77f9538b224b305c877d1e"} + entry = await _async_setup_xiaomi_device(hass, mac, data) + events = async_capture_events(hass, "xiaomi_ble_event") + + # Emit unlock inside the door event so it creates the device in the registry + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + mac, + b"\x48\x55\xc2\x11\x16\x50\x68\xb6\xfe\x3c\x87" + b"\x80\x95\xc8\xa5\x83\x4f\x00\x00\x00\x46\x32\x21\xc6", + ), + ) + + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + + dev_reg = async_get_dev_reg(hass) + device = dev_reg.async_get_device(identifiers={get_device_id(mac)}) + assert device + expected_trigger = { + CONF_PLATFORM: "device", + CONF_DOMAIN: DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: "fingerprint", + CONF_SUBTYPE: "skin_is_too_dry", + "metadata": {}, + } + triggers = await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, device.id + ) + assert expected_trigger in triggers + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + async def test_get_triggers_motion(hass: HomeAssistant) -> None: """Test that we get the expected triggers from a Xiaomi BLE motion sensor.""" mac = "DE:70:E8:B2:39:0C" diff --git a/tests/components/xiaomi_ble/test_event.py b/tests/components/xiaomi_ble/test_event.py index 785c336106e..1de5859c35e 100644 --- a/tests/components/xiaomi_ble/test_event.py +++ b/tests/components/xiaomi_ble/test_event.py @@ -4,7 +4,7 @@ import pytest from homeassistant.components.event import ATTR_EVENT_TYPE from homeassistant.components.xiaomi_ble.const import DOMAIN -from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE +from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from . import make_advertisement @@ -202,3 +202,94 @@ async def test_events( assert attributes[ATTR_EVENT_TYPE] == meas[ATTR_EVENT_TYPE] assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() + + +async def test_xiaomi_fingerprint(hass: HomeAssistant) -> None: + """Make sure that fingerprint reader events are correctly mapped.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="D7:1F:44:EB:8A:91", + ) + 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( + hass, + make_advertisement( + "D7:1F:44:EB:8A:91", + b"PD\x9e\x06B\x91\x8a\xebD\x1f\xd7" b"\x06\x00\x05\xff\xff\xff\xff\x00", + ), + ) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 3 + + sensor = hass.states.get("sensor.door_lock_8a91_key_id") + sensor_attr = sensor.attributes + assert sensor.state == "unknown operator" + assert sensor_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Key id" + + binary_sensor = hass.states.get("binary_sensor.door_lock_8a91_fingerprint") + binary_sensor_attribtes = binary_sensor.attributes + assert binary_sensor.state == STATE_ON + assert binary_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Fingerprint" + + event = hass.states.get("event.door_lock_8a91_fingerprint") + event_attr = event.attributes + assert event_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Fingerprint" + assert event_attr[ATTR_EVENT_TYPE] == "match_successful" + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + +async def test_xiaomi_lock(hass: HomeAssistant) -> None: + """Make sure that lock events are correctly mapped.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="D7:1F:44:EB:8A:91", + ) + 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( + hass, + make_advertisement( + "D7:1F:44:EB:8A:91", + b"PD\x9e\x06C\x91\x8a\xebD\x1f\xd7\x0b\x00\t" b" \x02\x00\x01\x80|D/a", + ), + ) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 4 + + event = hass.states.get("event.door_lock_8a91_lock") + event_attr = event.attributes + assert event_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Lock" + assert event_attr[ATTR_EVENT_TYPE] == "unlock_outside_the_door" + + sensor = hass.states.get("sensor.door_lock_8a91_lock_method") + sensor_attr = sensor.attributes + assert sensor.state == "biometrics" + assert sensor_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Lock method" + + sensor = hass.states.get("sensor.door_lock_8a91_key_id") + sensor_attr = sensor.attributes + assert sensor.state == "Fingerprint key id 2" + assert sensor_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Key id" + + binary_sensor = hass.states.get("binary_sensor.door_lock_8a91_lock") + binary_sensor_attribtes = binary_sensor.attributes + assert binary_sensor.state == STATE_ON + assert binary_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Lock" + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done()