Add Xiaomi-ble remotes and dimmers (#109327)

This commit is contained in:
Ernst Klamer 2024-02-05 00:30:47 +01:00 committed by GitHub
parent 3def42726a
commit a95a51da05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 324 additions and 5 deletions

View file

@ -29,6 +29,10 @@ from .coordinator import (
from .device import device_key_to_bluetooth_entity_key
BINARY_SENSOR_DESCRIPTIONS = {
XiaomiBinarySensorDeviceClass.BATTERY: BinarySensorEntityDescription(
key=XiaomiBinarySensorDeviceClass.BATTERY,
device_class=BinarySensorDeviceClass.BATTERY,
),
XiaomiBinarySensorDeviceClass.DOOR: BinarySensorEntityDescription(
key=XiaomiBinarySensorDeviceClass.DOOR,
device_class=BinarySensorDeviceClass.DOOR,
@ -49,6 +53,10 @@ BINARY_SENSOR_DESCRIPTIONS = {
key=XiaomiBinarySensorDeviceClass.OPENING,
device_class=BinarySensorDeviceClass.OPENING,
),
XiaomiBinarySensorDeviceClass.POWER: BinarySensorEntityDescription(
key=XiaomiBinarySensorDeviceClass.POWER,
device_class=BinarySensorDeviceClass.POWER,
),
XiaomiBinarySensorDeviceClass.SMOKE: BinarySensorEntityDescription(
key=XiaomiBinarySensorDeviceClass.SMOKE,
device_class=BinarySensorDeviceClass.SMOKE,

View file

@ -19,14 +19,23 @@ 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"
BUTTON: Final = "button"
CUBE: Final = "cube"
DIMMER: Final = "dimmer"
DOUBLE_BUTTON: Final = "double_button"
TRIPPLE_BUTTON: Final = "tripple_button"
REMOTE: Final = "remote"
REMOTE_FAN: Final = "remote_fan"
REMOTE_VENFAN: Final = "remote_ventilator_fan"
REMOTE_BATHROOM: Final = "remote_bathroom"
MOTION: Final = "motion"
BUTTON_PRESS: Final = "button_press"
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"

View file

@ -24,16 +24,25 @@ from .const import (
BUTTON,
BUTTON_PRESS,
BUTTON_PRESS_DOUBLE_LONG,
BUTTON_PRESS_LONG,
CONF_SUBTYPE,
CUBE,
DIMMER,
DOMAIN,
DOUBLE_BUTTON,
DOUBLE_BUTTON_PRESS_DOUBLE_LONG,
EVENT_CLASS,
EVENT_CLASS_BUTTON,
EVENT_CLASS_CUBE,
EVENT_CLASS_DIMMER,
EVENT_CLASS_MOTION,
EVENT_TYPE,
MOTION,
MOTION_DEVICE,
REMOTE,
REMOTE_BATHROOM,
REMOTE_FAN,
REMOTE_VENFAN,
TRIPPLE_BUTTON,
TRIPPLE_BUTTON_PRESS_DOUBLE_LONG,
XIAOMI_BLE_EVENT,
@ -41,14 +50,61 @@ from .const import (
TRIGGERS_BY_TYPE = {
BUTTON_PRESS: ["press"],
BUTTON_PRESS_LONG: ["press", "long_press"],
BUTTON_PRESS_DOUBLE_LONG: ["press", "double_press", "long_press"],
CUBE: ["rotate_left", "rotate_right"],
DIMMER: [
"press",
"long_press",
"rotate_left",
"rotate_right",
"rotate_left_pressed",
"rotate_right_pressed",
],
MOTION_DEVICE: ["motion_detected"],
}
EVENT_TYPES = {
BUTTON: ["button"],
CUBE: ["cube"],
DIMMER: ["dimmer"],
DOUBLE_BUTTON: ["button_left", "button_right"],
TRIPPLE_BUTTON: ["button_left", "button_middle", "button_right"],
REMOTE: [
"button_on",
"button_off",
"button_brightness",
"button_plus",
"button_min",
"button_m",
],
REMOTE_BATHROOM: [
"button_heat",
"button_air_exchange",
"button_dry",
"button_fan",
"button_swing",
"button_decrease_speed",
"button_increase_speed",
"button_stop",
"button_light",
],
REMOTE_FAN: [
"button_fan",
"button_light",
"button_wind_speed",
"button_wind_mode",
"button_brightness",
"button_color_temperature",
],
REMOTE_VENFAN: [
"button_swing",
"button_power",
"button_timer_30_minutes",
"button_timer_60_minutes",
"button_increase_wind_speed",
"button_decrease_wind_speed",
],
MOTION: ["motion"],
}
@ -78,11 +134,41 @@ TRIGGER_MODEL_DATA = {
event_types=EVENT_TYPES[DOUBLE_BUTTON],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG],
),
CUBE: TriggerModelData(
event_class=EVENT_CLASS_CUBE,
event_types=EVENT_TYPES[CUBE],
triggers=TRIGGERS_BY_TYPE[CUBE],
),
DIMMER: TriggerModelData(
event_class=EVENT_CLASS_DIMMER,
event_types=EVENT_TYPES[DIMMER],
triggers=TRIGGERS_BY_TYPE[DIMMER],
),
TRIPPLE_BUTTON_PRESS_DOUBLE_LONG: TriggerModelData(
event_class=EVENT_CLASS_BUTTON,
event_types=EVENT_TYPES[TRIPPLE_BUTTON],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG],
),
REMOTE: TriggerModelData(
event_class=EVENT_CLASS_BUTTON,
event_types=EVENT_TYPES[REMOTE],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG],
),
REMOTE_BATHROOM: TriggerModelData(
event_class=EVENT_CLASS_BUTTON,
event_types=EVENT_TYPES[REMOTE_BATHROOM],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG],
),
REMOTE_FAN: TriggerModelData(
event_class=EVENT_CLASS_BUTTON,
event_types=EVENT_TYPES[REMOTE_FAN],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG],
),
REMOTE_VENFAN: TriggerModelData(
event_class=EVENT_CLASS_BUTTON,
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],
@ -103,7 +189,13 @@ MODEL_DATA = {
"XMWXKG01YL": TRIGGER_MODEL_DATA[DOUBLE_BUTTON_PRESS_DOUBLE_LONG],
"K9B-2BTN": TRIGGER_MODEL_DATA[DOUBLE_BUTTON_PRESS_DOUBLE_LONG],
"K9B-3BTN": TRIGGER_MODEL_DATA[TRIPPLE_BUTTON_PRESS_DOUBLE_LONG],
"YLYK01YL": TRIGGER_MODEL_DATA[REMOTE],
"YLYK01YL-FANRC": TRIGGER_MODEL_DATA[REMOTE_FAN],
"YLYK01YL-VENFAN": TRIGGER_MODEL_DATA[REMOTE_VENFAN],
"YLYK01YL-BHFRC": TRIGGER_MODEL_DATA[REMOTE_BATHROOM],
"MUE4094RT": TRIGGER_MODEL_DATA[MOTION_DEVICE],
"XMMF01JQD": TRIGGER_MODEL_DATA[CUBE],
"YLKG07YL/YLKG08YL": TRIGGER_MODEL_DATA[DIMMER],
}

View file

@ -18,6 +18,8 @@ from . import format_discovered_event_class, format_event_dispatcher_name
from .const import (
DOMAIN,
EVENT_CLASS_BUTTON,
EVENT_CLASS_CUBE,
EVENT_CLASS_DIMMER,
EVENT_CLASS_MOTION,
EVENT_PROPERTIES,
EVENT_TYPE,
@ -36,10 +38,31 @@ DESCRIPTIONS_BY_EVENT_CLASS = {
],
device_class=EventDeviceClass.BUTTON,
),
EVENT_CLASS_CUBE: EventEntityDescription(
key=EVENT_CLASS_CUBE,
translation_key="cube",
event_types=[
"rotate_left",
"rotate_right",
],
),
EVENT_CLASS_DIMMER: EventEntityDescription(
key=EVENT_CLASS_DIMMER,
translation_key="dimmer",
event_types=[
"press",
"long_press",
"rotate_left",
"rotate_right",
"rotate_left_pressed",
"rotate_right_pressed",
],
),
EVENT_CLASS_MOTION: EventEntityDescription(
key=EVENT_CLASS_MOTION,
translation_key="motion",
event_types=["motion_detected"],
device_class=EventDeviceClass.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.23.1"]
"requirements": ["xiaomi-ble==0.25.2"]
}

View file

@ -44,14 +44,43 @@
"press": "Press",
"double_press": "Double Press",
"long_press": "Long Press",
"motion_detected": "Motion Detected"
"motion_detected": "Motion Detected",
"rotate_left": "Rotate Left",
"rotate_right": "Rotate Right",
"rotate_left_pressed": "Rotate Left (Pressed)",
"rotate_right_pressed": "Rotate Right (Pressed)"
},
"trigger_type": {
"button": "Button \"{subtype}\"",
"button_left": "Button Left \"{subtype}\"",
"button_middle": "Button Middle \"{subtype}\"",
"button_right": "Button Right \"{subtype}\"",
"motion": "{subtype}"
"button_on": "Button On \"{subtype}\"",
"button_off": "Button Off \"{subtype}\"",
"button_brightness": "Button Brightness \"{subtype}\"",
"button_plus": "Button Plus \"{subtype}\"",
"button_min": "Button Min \"{subtype}\"",
"button_m": "Button M \"{subtype}\"",
"button_heat": "Button Heat \"{subtype}\"",
"button_air_exchange": "Button Air Exchange \"{subtype}\"",
"button_dry": "Button Dry \"{subtype}\"",
"button_fan": "Button Fan \"{subtype}\"",
"button_swing": "Button Swing \"{subtype}\"",
"button_decrease_speed": "Button Decrease Speed \"{subtype}\"",
"button_increase_speed": "Button Inrease Speed \"{subtype}\"",
"button_stop": "Button Stop \"{subtype}\"",
"button_light": "Button Light \"{subtype}\"",
"button_wind_speed": "Button Wind Speed \"{subtype}\"",
"button_wind_mode": "Button Wind Mode \"{subtype}\"",
"button_color_temperature": "Button Color Temperature \"{subtype}\"",
"button_power": "Button Power \"{subtype}\"",
"button_timer_30_minutes": "Button Timer 30 Minutes \"{subtype}\"",
"button_timer_60_minutes": "Button Timer 30 Minutes \"{subtype}\"",
"button_increase_wind_speed": "Button Increase Wind Speed \"{subtype}\"",
"button_decrease_wind_speed": "Button Decrease Wind Speed \"{subtype}\"",
"dimmer": "{subtype}",
"motion": "{subtype}",
"cube": "{subtype}"
}
},
"entity": {
@ -67,6 +96,30 @@
}
}
},
"cube": {
"state_attributes": {
"event_type": {
"state": {
"rotate_left": "Rotate left",
"rotate_right": "Rotate right"
}
}
}
},
"dimmer": {
"state_attributes": {
"event_type": {
"state": {
"press": "Press",
"long_press": "Long press",
"rotate_left": "Rotate left",
"rotate_right": "Rotate right",
"rotate_left_pressed": "Rotate left (pressed)",
"rotate_right_pressed": "Rotate left (pressed)"
}
}
}
},
"motion": {
"state_attributes": {
"event_type": {

View file

@ -2853,7 +2853,7 @@ wyoming==1.5.2
xbox-webapi==2.0.11
# homeassistant.components.xiaomi_ble
xiaomi-ble==0.23.1
xiaomi-ble==0.25.2
# homeassistant.components.knx
xknx==2.11.2

View file

@ -2182,7 +2182,7 @@ wyoming==1.5.2
xbox-webapi==2.0.11
# homeassistant.components.xiaomi_ble
xiaomi-ble==0.23.1
xiaomi-ble==0.25.2
# homeassistant.components.knx
xknx==2.11.2

View file

@ -262,6 +262,37 @@ async def test_smoke(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
async def test_power(hass: HomeAssistant) -> None:
"""Test setting up a power binary sensor."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="F8:24:41:E9:50:74",
)
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(
"F8:24:41:E9:50:74",
b"P0S\x01?tP\xe9A$\xf8\x01\x10\x03\x01\x00\x00",
),
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
power_sensor = hass.states.get("binary_sensor.remote_control_5074_power")
power_sensor_attribtes = power_sensor.attributes
assert power_sensor.state == STATE_OFF
assert power_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Remote Control 5074 Power"
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
async def test_unavailable(hass: HomeAssistant) -> None:
"""Test normal device goes to unavailable after 60 minutes."""
start_monotonic = time.monotonic()

View file

@ -97,6 +97,32 @@ async def test_event_motion_detected(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
async def test_event_dimmer_rotate(hass: HomeAssistant) -> None:
"""Make sure that a dimmer rotate event is fired."""
mac = "F8:24:41:C5:98:8B"
data = {"bindkey": "b853075158487ca39a5b5ea9"}
entry = await _async_setup_xiaomi_device(hass, mac, data)
events = async_capture_events(hass, "xiaomi_ble_event")
# Emit dimmer rotate left with 3 steps event
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
mac, b"X0\xb6\x036\x8b\x98\xc5A$\xf8\x8b\xb8\xf2f" b"\x13Q\x00\x00\x00\xd6"
),
)
# wait for the event
await hass.async_block_till_done()
assert len(events) == 1
assert events[0].data["address"] == "F8:24:41:C5:98:8B"
assert events[0].data["event_type"] == "rotate_left"
assert events[0].data["event_properties"] == {"steps": 1}
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
async def test_get_triggers_button(hass: HomeAssistant) -> None:
"""Test that we get the expected triggers from a Xiaomi BLE button sensor."""
mac = "54:EF:44:E3:9C:BC"

View file

@ -49,6 +49,36 @@ from tests.components.bluetooth import (
}
],
),
(
"F8:24:41:E9:50:74",
make_advertisement(
"F8:24:41:E9:50:74",
b"P0S\x01?tP\xe9A$\xf8\x01\x10\x03\x04\x00\x02",
),
None,
[
{
"entity": "event.remote_control_5074_button_m",
ATTR_FRIENDLY_NAME: "Remote Control 5074 Button M",
ATTR_EVENT_TYPE: "long_press",
}
],
),
(
"F8:24:41:E9:50:74",
make_advertisement(
"F8:24:41:E9:50:74",
b"P0S\x01?tP\xe9A$\xf8\x01\x10\x03\x03\x00\x00",
),
None,
[
{
"entity": "event.remote_control_5074_button_plus",
ATTR_FRIENDLY_NAME: "Remote Control 5074 Button plus",
ATTR_EVENT_TYPE: "press",
}
],
),
(
"DE:70:E8:B2:39:0C",
make_advertisement(
@ -64,6 +94,53 @@ from tests.components.bluetooth import (
}
],
),
(
"E2:53:30:E6:D3:54",
make_advertisement(
"E2:53:30:E6:D3:54",
b"P0\xe1\x04\x8eT\xd3\xe60S\xe2\x01\x10\x03\x01\x00\x00",
),
None,
[
{
"entity": "event.magic_cube_d354_cube",
ATTR_FRIENDLY_NAME: "Magic Cube D354 Cube",
ATTR_EVENT_TYPE: "rotate_left",
}
],
),
(
"F8:24:41:C5:98:8B",
make_advertisement(
"F8:24:41:C5:98:8B",
b"X0\xb6\x036\x8b\x98\xc5A$\xf8\x8b\xb8\xf2f" b"\x13Q\x00\x00\x00\xd6",
),
"b853075158487ca39a5b5ea9",
[
{
"entity": "event.dimmer_switch_988b_dimmer",
ATTR_FRIENDLY_NAME: "Dimmer Switch 988B Dimmer",
ATTR_EVENT_TYPE: "rotate_left",
"event_properties": {"steps": 1},
}
],
),
(
"F8:24:41:C5:98:8B",
make_advertisement(
"F8:24:41:C5:98:8B",
b"X0\xb6\x03\xd2\x8b\x98\xc5A$\xf8\xc3I\x14vu~\x00\x00\x00\x99",
),
"b853075158487ca39a5b5ea9",
[
{
"entity": "event.dimmer_switch_988b_dimmer",
ATTR_FRIENDLY_NAME: "Dimmer Switch 988B Dimmer",
ATTR_EVENT_TYPE: "press",
"event_properties": {"duration": 2},
}
],
),
],
)
async def test_events(