Add event platform to rfxtrx (#111526)

This commit is contained in:
Joakim Plate 2024-03-03 17:15:54 +01:00 committed by GitHub
parent 9fff638311
commit 13653be09b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 406 additions and 0 deletions

View File

@ -79,6 +79,7 @@ SERVICE_SEND_SCHEMA = vol.Schema({ATTR_EVENT: _bytearray_string})
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.COVER,
Platform.EVENT,
Platform.LIGHT,
Platform.SENSOR,
Platform.SIREN,

View File

@ -0,0 +1,94 @@
"""Support for RFXtrx sensors."""
from __future__ import annotations
import logging
from typing import Any
from RFXtrx import ControlEvent, RFXtrxDevice, RFXtrxEvent, SensorEvent
from homeassistant.components.event import EventEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import slugify
from . import DeviceTuple, RfxtrxEntity, async_setup_platform_entry
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up config entry."""
def _supported(event: RFXtrxEvent) -> bool:
return isinstance(event, (ControlEvent, SensorEvent))
def _constructor(
event: RFXtrxEvent,
auto: RFXtrxEvent | None,
device_id: DeviceTuple,
entity_info: dict[str, Any],
) -> list[Entity]:
entities: list[Entity] = []
if hasattr(event.device, "COMMANDS"):
entities.append(
RfxtrxEventEntity(
event.device, device_id, "COMMANDS", "Command", "command"
)
)
if hasattr(event.device, "STATUS"):
entities.append(
RfxtrxEventEntity(
event.device, device_id, "STATUS", "Sensor Status", "status"
)
)
return entities
await async_setup_platform_entry(
hass, config_entry, async_add_entities, _supported, _constructor
)
class RfxtrxEventEntity(RfxtrxEntity, EventEntity):
"""Representation of a RFXtrx event."""
def __init__(
self,
device: RFXtrxDevice,
device_id: DeviceTuple,
device_attribute: str,
value_attribute: str,
translation_key: str,
) -> None:
"""Initialize the sensor."""
super().__init__(device, device_id)
commands: dict[int, str] = getattr(device, device_attribute)
self._attr_name = None
self._attr_unique_id = "_".join(x for x in device_id)
self._attr_event_types = [slugify(command) for command in commands.values()]
self._attr_translation_key = translation_key
self._value_attribute = value_attribute
@callback
def _handle_event(self, event: RFXtrxEvent, device_id: DeviceTuple) -> None:
"""Check if event applies to me and update."""
if not self._event_applies(event, device_id):
return
assert isinstance(event, (ControlEvent, SensorEvent))
event_type = slugify(event.values[self._value_attribute])
if event_type not in self._attr_event_types:
_LOGGER.warning("Event type %s is not known", event_type)
return
self._trigger_event(event_type, event.values)
self.async_write_ha_state()

View File

@ -83,6 +83,94 @@
}
},
"entity": {
"event": {
"command": {
"state_attributes": {
"event_type": {
"state": {
"sound_0": "Sound 0",
"sound_1": "Sound 1",
"sound_2": "Sound 2",
"sound_3": "Sound 3",
"sound_4": "Sound 4",
"sound_5": "Sound 5",
"sound_6": "Sound 6",
"sound_7": "Sound 7",
"sound_8": "Sound 8",
"sound_9": "Sound 9",
"sound_10": "Sound 10",
"sound_11": "Sound 11",
"sound_12": "Sound 12",
"sound_13": "Sound 13",
"sound_14": "Sound 14",
"sound_15": "Sound 15",
"down": "Down",
"up": "Up",
"all_off": "All Off",
"all_on": "All On",
"scene": "Scene",
"off": "Off",
"on": "On",
"dim": "Dim",
"bright": "Bright",
"all_group_off": "All/group Off",
"all_group_on": "All/group On",
"chime": "Chime",
"illegal_command": "Illegal command",
"set_level": "Set level",
"group_off": "Group off",
"group_on": "Group on",
"set_group_level": "Set group level",
"level_1": "Level 1",
"level_2": "Level 2",
"level_3": "Level 3",
"level_4": "Level 4",
"level_5": "Level 5",
"level_6": "Level 6",
"level_7": "Level 7",
"level_8": "Level 8",
"level_9": "Level 9",
"program": "Program",
"stop": "Stop",
"0_5_seconds_up": "0.5 Seconds Up",
"0_5_seconds_down": "0.5 Seconds Down",
"2_seconds_up": "2 Seconds Up",
"2_seconds_down": "2 Seconds Down",
"enable_sun_automation": "Enable sun automation",
"disable_sun_automation": "Disable sun automation",
"normal": "Normal",
"normal_delayed": "Normal Delayed",
"alarm": "Alarm",
"alarm_delayed": "Alarm Delayed",
"motion": "Motion",
"no_motion": "No Motion",
"panic": "Panic",
"end_panic": "End Panic",
"ir": "IR",
"arm_away": "Arm Away",
"arm_away_delayed": "Arm Away Delayed",
"arm_home": "Arm Home",
"arm_home_delayed": "Arm Home Delayed",
"disarm": "Disarm",
"light_1_off": "Light 1 Off",
"light_1_on": "Light 1 On",
"light_2_off": "Light 2 Off",
"light_2_on": "Light 2 On",
"dark_detected": "Dark Detected",
"light_detected": "Light Detected",
"battery_low": "Battery low",
"pairing_kd101": "Pairing KD101",
"normal_tamper": "Normal Tamper",
"normal_delayed_tamper": "Normal Delayed Tamper",
"alarm_tamper": "Alarm Tamper",
"alarm_delayed_tamper": "Alarm Delayed Tamper",
"motion_tamper": "Motion Tamper",
"no_motion_tamper": "No Motion Tamper"
}
}
}
}
},
"sensor": {
"current_ch_1": {
"name": "Current Ch. 1"

View File

@ -0,0 +1,121 @@
# serializer version: 1
# name: test_control_event.2
...
# ---
# name: test_control_event.3
...
+ 'Command': 'On',
+ 'Rssi numeric': 5,
...
- 'event_type': None,
+ 'event_type': 'on',
...
- 'state': 'unknown',
+ 'state': '2021-01-09T12:00:00.000+00:00',
...
# ---
# name: test_control_event[1]
StateSnapshot({
'attributes': ReadOnlyDict({
'assumed_state': True,
'event_type': None,
'event_types': list([
'off',
'on',
'dim',
'bright',
'all_group_off',
'all_group_on',
'chime',
'illegal_command',
]),
'friendly_name': 'ARC C1',
}),
'context': <ANY>,
'entity_id': 'event.arc_c1',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_control_event[2]
StateSnapshot({
'attributes': ReadOnlyDict({
'assumed_state': True,
'event_type': None,
'event_types': list([
'off',
'on',
'dim',
'bright',
'all_group_off',
'all_group_on',
'chime',
'illegal_command',
]),
'friendly_name': 'ARC D1',
}),
'context': <ANY>,
'entity_id': 'event.arc_d1',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_status_event.1
...
+ 'Battery numeric': 9,
+ 'Rssi numeric': 8,
+ 'Sensor Status': 'Normal',
...
- 'event_type': None,
+ 'event_type': 'normal',
...
- 'state': 'unknown',
+ 'state': '2021-01-09T12:00:00.000+00:00',
...
# ---
# name: test_status_event[1]
StateSnapshot({
'attributes': ReadOnlyDict({
'assumed_state': True,
'event_type': None,
'event_types': list([
'normal',
'normal_delayed',
'alarm',
'alarm_delayed',
'motion',
'no_motion',
'panic',
'end_panic',
'ir',
'arm_away',
'arm_away_delayed',
'arm_home',
'arm_home_delayed',
'disarm',
'light_1_off',
'light_1_on',
'light_2_off',
'light_2_on',
'dark_detected',
'light_detected',
'battery_low',
'pairing_kd101',
'normal_tamper',
'normal_delayed_tamper',
'alarm_tamper',
'alarm_delayed_tamper',
'motion_tamper',
'no_motion_tamper',
]),
'friendly_name': 'X10 Security d3dc54:32',
}),
'context': <ANY>,
'entity_id': 'event.x10_security_d3dc54_32',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@ -0,0 +1,102 @@
"""The tests for the Rfxtrx sensor platform."""
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from RFXtrx import ControlEvent
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.rfxtrx import get_rfx_object
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .conftest import setup_rfx_test_cfg
@pytest.fixture(autouse=True)
def required_platforms_only():
"""Only set up the required platform and required base platforms to speed up tests."""
with patch(
"homeassistant.components.rfxtrx.PLATFORMS",
(Platform.EVENT,),
):
yield
async def test_control_event(
hass: HomeAssistant,
rfxtrx,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None:
"""Test event update updates correct event object."""
hass.config.set_time_zone("UTC")
freezer.move_to("2021-01-09 12:00:00+00:00")
await setup_rfx_test_cfg(
hass,
devices={
"0710013d43010150": {},
"0710013d44010150": {},
},
)
assert hass.states.get("event.arc_c1") == snapshot(name="1")
assert hass.states.get("event.arc_d1") == snapshot(name="2")
# only signal one, to make sure we have no overhearing
await rfxtrx.signal("0710013d44010150")
assert hass.states.get("event.arc_c1") == snapshot(diff="1")
assert hass.states.get("event.arc_d1") == snapshot(diff="2")
async def test_status_event(
hass: HomeAssistant,
rfxtrx,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None:
"""Test event update updates correct event object."""
hass.config.set_time_zone("UTC")
freezer.move_to("2021-01-09 12:00:00+00:00")
await setup_rfx_test_cfg(
hass,
devices={
"0820004dd3dc540089": {},
},
)
assert hass.states.get("event.x10_security_d3dc54_32") == snapshot(name="1")
await rfxtrx.signal("0820004dd3dc540089")
assert hass.states.get("event.x10_security_d3dc54_32") == snapshot(diff="1")
async def test_invalid_event_type(
hass: HomeAssistant,
rfxtrx,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None:
"""Test with 1 sensor."""
await setup_rfx_test_cfg(
hass,
devices={
"0710013d43010150": {},
},
)
state = hass.states.get("event.arc_c1")
# Invalid event type should not trigger change
event = get_rfx_object("0710013d43010150")
assert isinstance(event, ControlEvent)
event.values["Command"] = "invalid_command"
rfxtrx.event_callback(event)
await hass.async_block_till_done()
assert hass.states.get("event.arc_c1") == state