mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 19:57:07 +00:00
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:
parent
453cbc3e14
commit
223ea03492
@ -28,6 +28,8 @@ TRIGGER_SUBTYPE = {
|
|||||||
"2": "second button",
|
"2": "second button",
|
||||||
"3": "third button",
|
"3": "third button",
|
||||||
"4": "fourth button",
|
"4": "fourth button",
|
||||||
|
"clock_wise": "Rotation clockwise",
|
||||||
|
"counter_clock_wise": "Rotation counter-clockwise",
|
||||||
}
|
}
|
||||||
TRIGGER_TYPE = {
|
TRIGGER_TYPE = {
|
||||||
"remote_button_long_release": "{subtype} released after long press",
|
"remote_button_long_release": "{subtype} released after long press",
|
||||||
@ -40,6 +42,7 @@ TRIGGER_TYPE = {
|
|||||||
"short_release": "{subtype} released after short press",
|
"short_release": "{subtype} released after short press",
|
||||||
"long_release": "{subtype} released after long press",
|
"long_release": "{subtype} released after long press",
|
||||||
"double_short_release": "both {subtype} released",
|
"double_short_release": "both {subtype} released",
|
||||||
|
"start": '"{subtype}" pressed initially',
|
||||||
}
|
}
|
||||||
|
|
||||||
UNKNOWN_TYPE = "unknown type"
|
UNKNOWN_TYPE = "unknown type"
|
||||||
|
@ -49,20 +49,23 @@
|
|||||||
"1": "First button",
|
"1": "First button",
|
||||||
"2": "Second button",
|
"2": "Second button",
|
||||||
"3": "Third button",
|
"3": "Third button",
|
||||||
"4": "Fourth button"
|
"4": "Fourth button",
|
||||||
|
"clock_wise": "Rotation clockwise",
|
||||||
|
"counter_clock_wise": "Rotation counter-clockwise"
|
||||||
},
|
},
|
||||||
"trigger_type": {
|
"trigger_type": {
|
||||||
"remote_button_long_release": "\"{subtype}\" button released after long press",
|
"remote_button_long_release": "\"{subtype}\" released after long press",
|
||||||
"remote_button_short_press": "\"{subtype}\" button pressed",
|
"remote_button_short_press": "\"{subtype}\" pressed",
|
||||||
"remote_button_short_release": "\"{subtype}\" button released",
|
"remote_button_short_release": "\"{subtype}\" released",
|
||||||
"remote_double_button_long_press": "Both \"{subtype}\" released after long press",
|
"remote_double_button_long_press": "Both \"{subtype}\" released after long press",
|
||||||
"remote_double_button_short_press": "Both \"{subtype}\" released",
|
"remote_double_button_short_press": "Both \"{subtype}\" released",
|
||||||
|
|
||||||
"initial_press": "Button \"{subtype}\" pressed initially",
|
"initial_press": "\"{subtype}\" pressed initially",
|
||||||
"repeat": "Button \"{subtype}\" held down",
|
"repeat": "\"{subtype}\" held down",
|
||||||
"short_release": "Button \"{subtype}\" released after short press",
|
"short_release": "\"{subtype}\" released after short press",
|
||||||
"long_release": "Button \"{subtype}\" released after long press",
|
"long_release": "\"{subtype}\" released after long press",
|
||||||
"double_short_release": "Both \"{subtype}\" released"
|
"double_short_release": "Both \"{subtype}\" released",
|
||||||
|
"start": "\"{subtype}\" pressed initially"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
|
@ -49,19 +49,22 @@
|
|||||||
"double_buttons_1_3": "First and Third buttons",
|
"double_buttons_1_3": "First and Third buttons",
|
||||||
"double_buttons_2_4": "Second and Fourth buttons",
|
"double_buttons_2_4": "Second and Fourth buttons",
|
||||||
"turn_off": "Turn off",
|
"turn_off": "Turn off",
|
||||||
"turn_on": "Turn on"
|
"turn_on": "Turn on",
|
||||||
|
"clock_wise": "Rotation clockwise",
|
||||||
|
"counter_clock_wise": "Rotation counter-clockwise"
|
||||||
},
|
},
|
||||||
"trigger_type": {
|
"trigger_type": {
|
||||||
"double_short_release": "Both \"{subtype}\" released",
|
"double_short_release": "Both \"{subtype}\" released",
|
||||||
"initial_press": "Button \"{subtype}\" pressed initially",
|
"initial_press": "\"{subtype}\" pressed initially",
|
||||||
"long_release": "Button \"{subtype}\" released after long press",
|
"long_release": "\"{subtype}\" released after long press",
|
||||||
"remote_button_long_release": "\"{subtype}\" button released after long press",
|
"remote_button_long_release": "\"{subtype}\" released after long press",
|
||||||
"remote_button_short_press": "\"{subtype}\" button pressed",
|
"remote_button_short_press": "\"{subtype}\" pressed",
|
||||||
"remote_button_short_release": "\"{subtype}\" button released",
|
"remote_button_short_release": "\"{subtype}\" released",
|
||||||
"remote_double_button_long_press": "Both \"{subtype}\" released after long press",
|
"remote_double_button_long_press": "Both \"{subtype}\" released after long press",
|
||||||
"remote_double_button_short_press": "Both \"{subtype}\" released",
|
"remote_double_button_short_press": "Both \"{subtype}\" released",
|
||||||
"repeat": "Button \"{subtype}\" held down",
|
"repeat": "\"{subtype}\" held down",
|
||||||
"short_release": "Button \"{subtype}\" released after short press"
|
"short_release": "\"{subtype}\" released after short press",
|
||||||
|
"start": "\"{subtype}\" pressed initially"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
|
@ -4,10 +4,13 @@ from __future__ import annotations
|
|||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from aiohue.v2.models.button import ButtonEvent
|
from aiohue.v2.models.button import ButtonEvent
|
||||||
|
from aiohue.v2.models.relative_rotary import (
|
||||||
|
RelativeRotaryAction,
|
||||||
|
RelativeRotaryDirection,
|
||||||
|
)
|
||||||
from aiohue.v2.models.resource import ResourceTypes
|
from aiohue.v2.models.resource import ResourceTypes
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import persistent_notification
|
|
||||||
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
|
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
|
||||||
from homeassistant.components.homeassistant.triggers import event as event_trigger
|
from homeassistant.components.homeassistant.triggers import event as event_trigger
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -49,48 +52,25 @@ DEFAULT_BUTTON_EVENT_TYPES = (
|
|||||||
ButtonEvent.LONG_RELEASE,
|
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_EVENT_TYPES = {
|
||||||
# device specific overrides of specific supported button events
|
# device specific overrides of specific supported button events
|
||||||
"Hue tap switch": (ButtonEvent.INITIAL_PRESS,),
|
"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(
|
async def async_validate_trigger_config(
|
||||||
bridge: "HueBridge",
|
bridge: "HueBridge",
|
||||||
device_entry: DeviceEntry,
|
device_entry: DeviceEntry,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
) -> ConfigType:
|
) -> ConfigType:
|
||||||
"""Validate config."""
|
"""Validate config."""
|
||||||
config = TRIGGER_SCHEMA(config)
|
return TRIGGER_SCHEMA(config)
|
||||||
check_invalid_device_trigger(bridge, config, device_entry)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
async def async_attach_trigger(
|
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(
|
return await event_trigger.async_attach_trigger(
|
||||||
hass, event_config, action, automation_info, platform_type="device"
|
hass, event_config, action, automation_info, platform_type="device"
|
||||||
)
|
)
|
||||||
@ -131,9 +110,10 @@ def async_get_triggers(
|
|||||||
# extract triggers from all button resources of this Hue device
|
# extract triggers from all button resources of this Hue device
|
||||||
triggers = []
|
triggers = []
|
||||||
model_id = api.devices[hue_dev_id].product_data.product_name
|
model_id = api.devices[hue_dev_id].product_data.product_name
|
||||||
|
|
||||||
for resource in api.devices.get_sensors(hue_dev_id):
|
for resource in api.devices.get_sensors(hue_dev_id):
|
||||||
if resource.type != ResourceTypes.BUTTON:
|
# button triggers
|
||||||
continue
|
if resource.type == ResourceTypes.BUTTON:
|
||||||
for event_type in DEVICE_SPECIFIC_EVENT_TYPES.get(
|
for event_type in DEVICE_SPECIFIC_EVENT_TYPES.get(
|
||||||
model_id, DEFAULT_BUTTON_EVENT_TYPES
|
model_id, DEFAULT_BUTTON_EVENT_TYPES
|
||||||
):
|
):
|
||||||
@ -147,6 +127,20 @@ def async_get_triggers(
|
|||||||
CONF_UNIQUE_ID: resource.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
|
return triggers
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
|
|||||||
from aiohue.v2 import HueBridgeV2
|
from aiohue.v2 import HueBridgeV2
|
||||||
from aiohue.v2.controllers.events import EventType
|
from aiohue.v2.controllers.events import EventType
|
||||||
from aiohue.v2.models.button import Button
|
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.const import CONF_DEVICE_ID, CONF_ID, CONF_TYPE, CONF_UNIQUE_ID
|
||||||
from homeassistant.core import callback
|
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
|
from ..const import ATTR_HUE_EVENT, CONF_SUBTYPE, DOMAIN as DOMAIN
|
||||||
|
|
||||||
CONF_CONTROL_ID = "control_id"
|
CONF_CONTROL_ID = "control_id"
|
||||||
|
CONF_DURATION = "duration"
|
||||||
|
CONF_STEPS = "steps"
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..bridge import HueBridge
|
from ..bridge import HueBridge
|
||||||
@ -28,12 +31,12 @@ async def async_setup_hue_events(bridge: "HueBridge"):
|
|||||||
conf_entry = bridge.config_entry
|
conf_entry = bridge.config_entry
|
||||||
dev_reg = device_registry.async_get(hass)
|
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
|
btn_controller = api.sensors.button
|
||||||
|
rotary_controller = api.sensors.relative_rotary
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def handle_button_event(evt_type: EventType, hue_resource: Button) -> None:
|
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)
|
LOGGER.debug("Received button event: %s", hue_resource)
|
||||||
|
|
||||||
# guard for missing button object on the 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
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user