diff --git a/homeassistant/components/xiaomi_ble/binary_sensor.py b/homeassistant/components/xiaomi_ble/binary_sensor.py index 2894b8d2f3f..cd6f7b453bb 100644 --- a/homeassistant/components/xiaomi_ble/binary_sensor.py +++ b/homeassistant/components/xiaomi_ble/binary_sensor.py @@ -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, diff --git a/homeassistant/components/xiaomi_ble/const.py b/homeassistant/components/xiaomi_ble/const.py index 1accfd9dc55..5f9dea9eb45 100644 --- a/homeassistant/components/xiaomi_ble/const.py +++ b/homeassistant/components/xiaomi_ble/const.py @@ -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" diff --git a/homeassistant/components/xiaomi_ble/device_trigger.py b/homeassistant/components/xiaomi_ble/device_trigger.py index 6d29af9ac11..8d281ddc8a9 100644 --- a/homeassistant/components/xiaomi_ble/device_trigger.py +++ b/homeassistant/components/xiaomi_ble/device_trigger.py @@ -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], } diff --git a/homeassistant/components/xiaomi_ble/event.py b/homeassistant/components/xiaomi_ble/event.py index 1d5b08fb8f9..2c1550dc5d7 100644 --- a/homeassistant/components/xiaomi_ble/event.py +++ b/homeassistant/components/xiaomi_ble/event.py @@ -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, ), } diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index f11b2426f96..a380ecb8e94 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.23.1"] + "requirements": ["xiaomi-ble==0.25.2"] } diff --git a/homeassistant/components/xiaomi_ble/strings.json b/homeassistant/components/xiaomi_ble/strings.json index c7cbe43bd94..d2511869580 100644 --- a/homeassistant/components/xiaomi_ble/strings.json +++ b/homeassistant/components/xiaomi_ble/strings.json @@ -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": { diff --git a/requirements_all.txt b/requirements_all.txt index dbcf81495e7..e8762b76c30 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 851db4ed511..a6057f4bf7f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -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 diff --git a/tests/components/xiaomi_ble/test_binary_sensor.py b/tests/components/xiaomi_ble/test_binary_sensor.py index 14ea3e44af8..b9e0b24a3cf 100644 --- a/tests/components/xiaomi_ble/test_binary_sensor.py +++ b/tests/components/xiaomi_ble/test_binary_sensor.py @@ -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() diff --git a/tests/components/xiaomi_ble/test_device_trigger.py b/tests/components/xiaomi_ble/test_device_trigger.py index 31f896680bf..28195f784e8 100644 --- a/tests/components/xiaomi_ble/test_device_trigger.py +++ b/tests/components/xiaomi_ble/test_device_trigger.py @@ -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" diff --git a/tests/components/xiaomi_ble/test_event.py b/tests/components/xiaomi_ble/test_event.py index 1d2cf5fb3fc..a0e84c0ac2e 100644 --- a/tests/components/xiaomi_ble/test_event.py +++ b/tests/components/xiaomi_ble/test_event.py @@ -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(