Add Locks to Xiaomi-BLE (#111156)

This commit is contained in:
Ernst Klamer 2024-03-10 09:29:48 +01:00 committed by GitHub
parent e631224372
commit e91a38eede
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 452 additions and 17 deletions

View File

@ -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,
),

View File

@ -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):

View File

@ -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],
}

View File

@ -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",

View File

@ -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"]
}

View File

@ -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"
),
}

View File

@ -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": {

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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()