diff --git a/homeassistant/components/hue/logbook.py b/homeassistant/components/hue/logbook.py index e98a99f1861..412ca044b58 100644 --- a/homeassistant/components/hue/logbook.py +++ b/homeassistant/components/hue/logbook.py @@ -28,6 +28,8 @@ TRIGGER_SUBTYPE = { "2": "second button", "3": "third button", "4": "fourth button", + "clock_wise": "Rotation clockwise", + "counter_clock_wise": "Rotation counter-clockwise", } TRIGGER_TYPE = { "remote_button_long_release": "{subtype} released after long press", @@ -40,6 +42,7 @@ TRIGGER_TYPE = { "short_release": "{subtype} released after short press", "long_release": "{subtype} released after long press", "double_short_release": "both {subtype} released", + "start": '"{subtype}" pressed initially', } UNKNOWN_TYPE = "unknown type" diff --git a/homeassistant/components/hue/strings.json b/homeassistant/components/hue/strings.json index 0d7c67ec84b..a44eea0fe33 100644 --- a/homeassistant/components/hue/strings.json +++ b/homeassistant/components/hue/strings.json @@ -49,20 +49,23 @@ "1": "First button", "2": "Second button", "3": "Third button", - "4": "Fourth button" + "4": "Fourth button", + "clock_wise": "Rotation clockwise", + "counter_clock_wise": "Rotation counter-clockwise" }, "trigger_type": { - "remote_button_long_release": "\"{subtype}\" button released after long press", - "remote_button_short_press": "\"{subtype}\" button pressed", - "remote_button_short_release": "\"{subtype}\" button released", + "remote_button_long_release": "\"{subtype}\" released after long press", + "remote_button_short_press": "\"{subtype}\" pressed", + "remote_button_short_release": "\"{subtype}\" released", "remote_double_button_long_press": "Both \"{subtype}\" released after long press", "remote_double_button_short_press": "Both \"{subtype}\" released", - "initial_press": "Button \"{subtype}\" pressed initially", - "repeat": "Button \"{subtype}\" held down", - "short_release": "Button \"{subtype}\" released after short press", - "long_release": "Button \"{subtype}\" released after long press", - "double_short_release": "Both \"{subtype}\" released" + "initial_press": "\"{subtype}\" pressed initially", + "repeat": "\"{subtype}\" held down", + "short_release": "\"{subtype}\" released after short press", + "long_release": "\"{subtype}\" released after long press", + "double_short_release": "Both \"{subtype}\" released", + "start": "\"{subtype}\" pressed initially" } }, "options": { diff --git a/homeassistant/components/hue/translations/en.json b/homeassistant/components/hue/translations/en.json index f617be431b9..7a54fc5ce03 100644 --- a/homeassistant/components/hue/translations/en.json +++ b/homeassistant/components/hue/translations/en.json @@ -49,19 +49,22 @@ "double_buttons_1_3": "First and Third buttons", "double_buttons_2_4": "Second and Fourth buttons", "turn_off": "Turn off", - "turn_on": "Turn on" + "turn_on": "Turn on", + "clock_wise": "Rotation clockwise", + "counter_clock_wise": "Rotation counter-clockwise" }, "trigger_type": { "double_short_release": "Both \"{subtype}\" released", - "initial_press": "Button \"{subtype}\" pressed initially", - "long_release": "Button \"{subtype}\" released after long press", - "remote_button_long_release": "\"{subtype}\" button released after long press", - "remote_button_short_press": "\"{subtype}\" button pressed", - "remote_button_short_release": "\"{subtype}\" button released", + "initial_press": "\"{subtype}\" pressed initially", + "long_release": "\"{subtype}\" released after long press", + "remote_button_long_release": "\"{subtype}\" released after long press", + "remote_button_short_press": "\"{subtype}\" pressed", + "remote_button_short_release": "\"{subtype}\" released", "remote_double_button_long_press": "Both \"{subtype}\" released after long press", "remote_double_button_short_press": "Both \"{subtype}\" released", - "repeat": "Button \"{subtype}\" held down", - "short_release": "Button \"{subtype}\" released after short press" + "repeat": "\"{subtype}\" held down", + "short_release": "\"{subtype}\" released after short press", + "start": "\"{subtype}\" pressed initially" } }, "options": { diff --git a/homeassistant/components/hue/v2/device_trigger.py b/homeassistant/components/hue/v2/device_trigger.py index 2b868a4685c..0fa9e37569d 100644 --- a/homeassistant/components/hue/v2/device_trigger.py +++ b/homeassistant/components/hue/v2/device_trigger.py @@ -4,10 +4,13 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any from aiohue.v2.models.button import ButtonEvent +from aiohue.v2.models.relative_rotary import ( + RelativeRotaryAction, + RelativeRotaryDirection, +) from aiohue.v2.models.resource import ResourceTypes import voluptuous as vol -from homeassistant.components import persistent_notification from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from homeassistant.components.homeassistant.triggers import event as event_trigger from homeassistant.const import ( @@ -49,48 +52,25 @@ DEFAULT_BUTTON_EVENT_TYPES = ( ButtonEvent.LONG_RELEASE, ) +DEFAULT_ROTARY_EVENT_TYPES = (RelativeRotaryAction.START, RelativeRotaryAction.REPEAT) +DEFAULT_ROTARY_EVENT_SUBTYPES = ( + RelativeRotaryDirection.CLOCK_WISE, + RelativeRotaryDirection.COUNTER_CLOCK_WISE, +) + DEVICE_SPECIFIC_EVENT_TYPES = { # device specific overrides of specific supported button events "Hue tap switch": (ButtonEvent.INITIAL_PRESS,), } -def check_invalid_device_trigger( - bridge: HueBridge, - config: ConfigType, - device_entry: DeviceEntry, - automation_info: AutomationTriggerInfo | None = None, -): - """Check automation config for deprecated format.""" - # NOTE: Remove this check after 2022.6 - if isinstance(config["subtype"], int): - return - # found deprecated V1 style trigger, notify the user that it should be adjusted - msg = ( - f"Incompatible device trigger detected for " - f"[{device_entry.name}](/config/devices/device/{device_entry.id}) " - "Please manually fix the outdated automation(s) once to fix this issue." - ) - if automation_info: - automation_id = automation_info["variables"]["this"]["attributes"]["id"] # type: ignore[index] - msg += f"\n\n[Check it out](/config/automation/edit/{automation_id})." - persistent_notification.async_create( - bridge.hass, - msg, - title="Outdated device trigger found", - notification_id=f"hue_trigger_{device_entry.id}", - ) - - async def async_validate_trigger_config( bridge: "HueBridge", device_entry: DeviceEntry, config: ConfigType, ) -> ConfigType: """Validate config.""" - config = TRIGGER_SCHEMA(config) - check_invalid_device_trigger(bridge, config, device_entry) - return config + return TRIGGER_SCHEMA(config) async def async_attach_trigger( @@ -113,7 +93,6 @@ async def async_attach_trigger( }, } ) - check_invalid_device_trigger(bridge, config, device_entry, automation_info) return await event_trigger.async_attach_trigger( hass, event_config, action, automation_info, platform_type="device" ) @@ -131,22 +110,37 @@ def async_get_triggers( # extract triggers from all button resources of this Hue device triggers = [] model_id = api.devices[hue_dev_id].product_data.product_name + for resource in api.devices.get_sensors(hue_dev_id): - if resource.type != ResourceTypes.BUTTON: - continue - for event_type in DEVICE_SPECIFIC_EVENT_TYPES.get( - model_id, DEFAULT_BUTTON_EVENT_TYPES - ): - triggers.append( - { - CONF_DEVICE_ID: device_entry.id, - CONF_DOMAIN: DOMAIN, - CONF_PLATFORM: "device", - CONF_TYPE: event_type.value, - CONF_SUBTYPE: resource.metadata.control_id, - CONF_UNIQUE_ID: resource.id, - } - ) + # button triggers + if resource.type == ResourceTypes.BUTTON: + for event_type in DEVICE_SPECIFIC_EVENT_TYPES.get( + model_id, DEFAULT_BUTTON_EVENT_TYPES + ): + triggers.append( + { + CONF_DEVICE_ID: device_entry.id, + CONF_DOMAIN: DOMAIN, + CONF_PLATFORM: "device", + CONF_TYPE: event_type.value, + CONF_SUBTYPE: resource.metadata.control_id, + CONF_UNIQUE_ID: resource.id, + } + ) + # relative_rotary triggers + elif resource.type == ResourceTypes.RELATIVE_ROTARY: + for event_type in DEFAULT_ROTARY_EVENT_TYPES: + for sub_type in DEFAULT_ROTARY_EVENT_SUBTYPES: + triggers.append( + { + CONF_DEVICE_ID: device_entry.id, + CONF_DOMAIN: DOMAIN, + CONF_PLATFORM: "device", + CONF_TYPE: event_type.value, + CONF_SUBTYPE: sub_type.value, + CONF_UNIQUE_ID: resource.id, + } + ) return triggers diff --git a/homeassistant/components/hue/v2/hue_event.py b/homeassistant/components/hue/v2/hue_event.py index 4b9adf16226..8e85288eee0 100644 --- a/homeassistant/components/hue/v2/hue_event.py +++ b/homeassistant/components/hue/v2/hue_event.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from aiohue.v2 import HueBridgeV2 from aiohue.v2.controllers.events import EventType from aiohue.v2.models.button import Button +from aiohue.v2.models.relative_rotary import RelativeRotary from homeassistant.const import CONF_DEVICE_ID, CONF_ID, CONF_TYPE, CONF_UNIQUE_ID from homeassistant.core import callback @@ -14,6 +15,8 @@ from homeassistant.util import slugify from ..const import ATTR_HUE_EVENT, CONF_SUBTYPE, DOMAIN as DOMAIN CONF_CONTROL_ID = "control_id" +CONF_DURATION = "duration" +CONF_STEPS = "steps" if TYPE_CHECKING: from ..bridge import HueBridge @@ -28,12 +31,12 @@ async def async_setup_hue_events(bridge: "HueBridge"): conf_entry = bridge.config_entry dev_reg = device_registry.async_get(hass) - # at this time the `button` resource is the only source of hue events btn_controller = api.sensors.button + rotary_controller = api.sensors.relative_rotary @callback def handle_button_event(evt_type: EventType, hue_resource: Button) -> None: - """Handle event from Hue devices controller.""" + """Handle event from Hue button resource controller.""" LOGGER.debug("Received button event: %s", hue_resource) # guard for missing button object on the resource @@ -60,3 +63,29 @@ async def async_setup_hue_events(bridge: "HueBridge"): handle_button_event, event_filter=EventType.RESOURCE_UPDATED ) ) + + @callback + def handle_rotary_event(evt_type: EventType, hue_resource: RelativeRotary) -> None: + """Handle event from Hue relative_rotary resource controller.""" + LOGGER.debug("Received relative_rotary event: %s", hue_resource) + + hue_device = btn_controller.get_device(hue_resource.id) + device = dev_reg.async_get_device({(DOMAIN, hue_device.id)}) + + # Fire event + data = { + CONF_DEVICE_ID: device.id, # type: ignore[union-attr] + CONF_UNIQUE_ID: hue_resource.id, + CONF_TYPE: hue_resource.relative_rotary.last_event.action.value, + CONF_SUBTYPE: hue_resource.relative_rotary.last_event.rotation.direction.value, + CONF_DURATION: hue_resource.relative_rotary.last_event.rotation.duration, + CONF_STEPS: hue_resource.relative_rotary.last_event.rotation.steps, + } + hass.bus.async_fire(ATTR_HUE_EVENT, data) + + # add listener for updates from `relative_rotary` resource + conf_entry.async_on_unload( + rotary_controller.subscribe( + handle_rotary_event, event_filter=EventType.RESOURCE_UPDATED + ) + )