Fix Hue events for relative_rotary devices (such as Hue Tap Dial) (#76758)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Marcel van der Veldt 2022-08-15 19:32:44 +02:00 committed by GitHub
parent 453cbc3e14
commit 223ea03492
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 66 deletions

View File

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

View File

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

View File

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

View File

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

View File

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