Homee add button_state to event entities (#146860)

* use entityDescription

* Add new event and adapt tests

* change translation

* use references in strings
This commit is contained in:
Markus Adrario 2025-06-15 18:17:52 +02:00 committed by GitHub
parent 1361d10cd7
commit fdf4ed2aa5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 280 additions and 39 deletions

View File

@ -1,9 +1,13 @@
"""The homee event platform.""" """The homee event platform."""
from pyHomee.const import AttributeType from pyHomee.const import AttributeType, NodeProfile
from pyHomee.model import HomeeAttribute from pyHomee.model import HomeeAttribute
from homeassistant.components.event import EventDeviceClass, EventEntity from homeassistant.components.event import (
EventDeviceClass,
EventEntity,
EventEntityDescription,
)
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@ -13,6 +17,38 @@ from .entity import HomeeEntity
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
REMOTE_PROFILES = [
NodeProfile.REMOTE,
NodeProfile.TWO_BUTTON_REMOTE,
NodeProfile.THREE_BUTTON_REMOTE,
NodeProfile.FOUR_BUTTON_REMOTE,
]
EVENT_DESCRIPTIONS: dict[AttributeType, EventEntityDescription] = {
AttributeType.BUTTON_STATE: EventEntityDescription(
key="button_state",
device_class=EventDeviceClass.BUTTON,
event_types=["upper", "lower", "released"],
),
AttributeType.UP_DOWN_REMOTE: EventEntityDescription(
key="up_down_remote",
device_class=EventDeviceClass.BUTTON,
event_types=[
"released",
"up",
"down",
"stop",
"up_long",
"down_long",
"stop_long",
"c_button",
"b_button",
"a_button",
],
),
}
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: HomeeConfigEntry, config_entry: HomeeConfigEntry,
@ -21,30 +57,31 @@ async def async_setup_entry(
"""Add event entities for homee.""" """Add event entities for homee."""
async_add_entities( async_add_entities(
HomeeEvent(attribute, config_entry) HomeeEvent(attribute, config_entry, EVENT_DESCRIPTIONS[attribute.type])
for node in config_entry.runtime_data.nodes for node in config_entry.runtime_data.nodes
for attribute in node.attributes for attribute in node.attributes
if attribute.type == AttributeType.UP_DOWN_REMOTE if attribute.type in EVENT_DESCRIPTIONS
and node.profile in REMOTE_PROFILES
and not attribute.editable
) )
class HomeeEvent(HomeeEntity, EventEntity): class HomeeEvent(HomeeEntity, EventEntity):
"""Representation of a homee event.""" """Representation of a homee event."""
_attr_translation_key = "up_down_remote" def __init__(
_attr_event_types = [ self,
"released", attribute: HomeeAttribute,
"up", entry: HomeeConfigEntry,
"down", description: EventEntityDescription,
"stop", ) -> None:
"up_long", """Initialize the homee event entity."""
"down_long", super().__init__(attribute, entry)
"stop_long", self.entity_description = description
"c_button", self._attr_translation_key = description.key
"b_button", if attribute.instance > 0:
"a_button", self._attr_translation_key = f"{self._attr_translation_key}_instance"
] self._attr_translation_placeholders = {"instance": str(attribute.instance)}
_attr_device_class = EventDeviceClass.BUTTON
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Add the homee event entity to home assistant.""" """Add the homee event entity to home assistant."""
@ -56,6 +93,5 @@ class HomeeEvent(HomeeEntity, EventEntity):
@callback @callback
def _event_triggered(self, event: HomeeAttribute) -> None: def _event_triggered(self, event: HomeeAttribute) -> None:
"""Handle a homee event.""" """Handle a homee event."""
if event.type == AttributeType.UP_DOWN_REMOTE: self._trigger_event(self.event_types[int(event.current_value)])
self._trigger_event(self.event_types[int(event.current_value)]) self.schedule_update_ha_state()
self.schedule_update_ha_state()

View File

@ -160,12 +160,36 @@
} }
}, },
"event": { "event": {
"button_state": {
"name": "Switch",
"state_attributes": {
"event_type": {
"state": {
"upper": "Upper button",
"lower": "Lower button",
"released": "Released"
}
}
}
},
"button_state_instance": {
"name": "Switch {instance}",
"state_attributes": {
"event_type": {
"state": {
"upper": "[%key;component::homee::entity::event::button_state::state_attributes::event_type::state::upper%]",
"lower": "[%key;component::homee::entity::event::button_state::state_attributes::event_type::state::lower%]",
"released": "[%key;component::homee::entity::event::button_state::state_attributes::event_type::state::released%]"
}
}
}
},
"up_down_remote": { "up_down_remote": {
"name": "Up/down remote", "name": "Up/down remote",
"state_attributes": { "state_attributes": {
"event_type": { "event_type": {
"state": { "state": {
"release": "Released", "release": "[%key;component::homee::entity::event::button_state::state_attributes::event_type::state::released%]",
"up": "Up", "up": "Up",
"down": "Down", "down": "Down",
"stop": "Stop", "stop": "Stop",

View File

@ -41,6 +41,48 @@
"options": { "options": {
"observed_by": [145] "observed_by": [145]
} }
},
{
"id": 2,
"node_id": 1,
"instance": 1,
"minimum": 0,
"maximum": 3,
"current_value": 2.0,
"target_value": 2.0,
"last_value": 0.0,
"unit": "n/a",
"step_value": 1.0,
"editable": 0,
"type": 40,
"state": 1,
"last_changed": 1749885830,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
},
{
"id": 3,
"node_id": 1,
"instance": 2,
"minimum": 0,
"maximum": 3,
"current_value": 2.0,
"target_value": 2.0,
"last_value": 2.0,
"unit": "n/a",
"step_value": 1.0,
"editable": 0,
"type": 40,
"state": 1,
"last_changed": 1749885830,
"changed_by": 1,
"changed_by_id": 0,
"based_on": 1,
"data": "",
"name": ""
} }
] ]
} }

View File

@ -1,4 +1,126 @@
# serializer version: 1 # serializer version: 1
# name: test_event_snapshot[event.remote_control_switch_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'event_types': list([
'upper',
'lower',
'released',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'event',
'entity_category': None,
'entity_id': 'event.remote_control_switch_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
'original_icon': None,
'original_name': 'Switch 1',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'button_state_instance',
'unique_id': '00055511EECC-1-2',
'unit_of_measurement': None,
})
# ---
# name: test_event_snapshot[event.remote_control_switch_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'button',
'event_type': None,
'event_types': list([
'upper',
'lower',
'released',
]),
'friendly_name': 'Remote Control Switch 1',
}),
'context': <ANY>,
'entity_id': 'event.remote_control_switch_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_event_snapshot[event.remote_control_switch_2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'event_types': list([
'upper',
'lower',
'released',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'event',
'entity_category': None,
'entity_id': 'event.remote_control_switch_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
'original_icon': None,
'original_name': 'Switch 2',
'platform': 'homee',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'button_state_instance',
'unique_id': '00055511EECC-1-3',
'unit_of_measurement': None,
})
# ---
# name: test_event_snapshot[event.remote_control_switch_2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'button',
'event_type': None,
'event_types': list([
'upper',
'lower',
'released',
]),
'friendly_name': 'Remote Control Switch 2',
}),
'context': <ANY>,
'entity_id': 'event.remote_control_switch_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_event_snapshot[event.remote_control_up_down_remote-entry] # name: test_event_snapshot[event.remote_control_up_down_remote-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({

View File

@ -2,6 +2,7 @@
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.components.event import ATTR_EVENT_TYPE from homeassistant.components.event import ATTR_EVENT_TYPE
@ -14,38 +15,54 @@ from . import build_mock_node, setup_integration
from tests.common import MockConfigEntry, snapshot_platform from tests.common import MockConfigEntry, snapshot_platform
async def test_event_fires( @pytest.mark.parametrize(
("entity_id", "attribute_id", "expected_event_types"),
[
(
"event.remote_control_up_down_remote",
1,
[
"released",
"up",
"down",
"stop",
"up_long",
"down_long",
"stop_long",
"c_button",
"b_button",
"a_button",
],
),
(
"event.remote_control_switch_2",
3,
["upper", "lower", "released"],
),
],
)
async def test_event_triggers(
hass: HomeAssistant, hass: HomeAssistant,
mock_homee: MagicMock, mock_homee: MagicMock,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
entity_id: str,
attribute_id: int,
expected_event_types: list[str],
) -> None: ) -> None:
"""Test that the correct event fires when the attribute changes.""" """Test that the correct event fires when the attribute changes."""
EVENT_TYPES = [
"released",
"up",
"down",
"stop",
"up_long",
"down_long",
"stop_long",
"c_button",
"b_button",
"a_button",
]
mock_homee.nodes = [build_mock_node("events.json")] mock_homee.nodes = [build_mock_node("events.json")]
mock_homee.get_node_by_id.return_value = mock_homee.nodes[0] mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
# Simulate the event triggers. # Simulate the event triggers.
attribute = mock_homee.nodes[0].attributes[0] attribute = mock_homee.nodes[0].attributes[attribute_id - 1]
for i, event_type in enumerate(EVENT_TYPES): for i, event_type in enumerate(expected_event_types):
attribute.current_value = i attribute.current_value = i
attribute.add_on_changed_listener.call_args_list[1][0][0](attribute) attribute.add_on_changed_listener.call_args_list[1][0][0](attribute)
await hass.async_block_till_done() await hass.async_block_till_done()
# Check if the event was fired # Check if the event was fired
state = hass.states.get("event.remote_control_up_down_remote") state = hass.states.get(entity_id)
assert state.attributes[ATTR_EVENT_TYPE] == event_type assert state.attributes[ATTR_EVENT_TYPE] == event_type