Add event platform to rfxtrx (#111526)
This commit is contained in:
parent
9fff638311
commit
13653be09b
5 changed files with 406 additions and 0 deletions
|
@ -79,6 +79,7 @@ SERVICE_SEND_SCHEMA = vol.Schema({ATTR_EVENT: _bytearray_string})
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
Platform.COVER,
|
Platform.COVER,
|
||||||
|
Platform.EVENT,
|
||||||
Platform.LIGHT,
|
Platform.LIGHT,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
Platform.SIREN,
|
Platform.SIREN,
|
||||||
|
|
94
homeassistant/components/rfxtrx/event.py
Normal file
94
homeassistant/components/rfxtrx/event.py
Normal 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()
|
|
@ -83,6 +83,94 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"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": {
|
"sensor": {
|
||||||
"current_ch_1": {
|
"current_ch_1": {
|
||||||
"name": "Current Ch. 1"
|
"name": "Current Ch. 1"
|
||||||
|
|
121
tests/components/rfxtrx/snapshots/test_event.ambr
Normal file
121
tests/components/rfxtrx/snapshots/test_event.ambr
Normal 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',
|
||||||
|
})
|
||||||
|
# ---
|
102
tests/components/rfxtrx/test_event.py
Normal file
102
tests/components/rfxtrx/test_event.py
Normal 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
|
Loading…
Add table
Reference in a new issue