Add event platform to rfxtrx (#111526)
parent
9fff638311
commit
13653be09b
|
@ -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,
|
||||
|
|
|
@ -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": {
|
||||
"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"
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
# ---
|
|
@ -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…
Reference in New Issue