diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py index 627e5a3dd5e..b8976454d68 100644 --- a/homeassistant/components/rfxtrx/binary_sensor.py +++ b/homeassistant/components/rfxtrx/binary_sensor.py @@ -3,7 +3,11 @@ import logging import RFXtrx as rfxtrxmod -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_SMOKE, + BinarySensorEntity, +) from homeassistant.const import ( CONF_COMMAND_OFF, CONF_COMMAND_ON, @@ -34,6 +38,33 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +SENSOR_STATUS_ON = [ + "Panic", + "Motion", + "Motion Tamper", + "Light Detected", + "Alarm", + "Alarm Tamper", +] + +SENSOR_STATUS_OFF = [ + "End Panic", + "No Motion", + "No Motion Tamper", + "Dark Detected", + "Normal", + "Normal Tamper", +] + +DEVICE_TYPE_DEVICE_CLASS = { + "X10 Security Motion Detector": DEVICE_CLASS_MOTION, + "KD101 Smoke Detector": DEVICE_CLASS_SMOKE, + "Visonic Powercode Motion Detector": DEVICE_CLASS_MOTION, + "Alecto SA30 Smoke Detector": DEVICE_CLASS_SMOKE, + "RM174RF Smoke Detector": DEVICE_CLASS_SMOKE, +} + + async def async_setup_entry( hass, config_entry, async_add_entities, ): @@ -46,7 +77,14 @@ async def async_setup_entry( discovery_info = config_entry.data def supported(event): - return isinstance(event, rfxtrxmod.ControlEvent) + if isinstance(event, rfxtrxmod.ControlEvent): + return True + if isinstance(event, rfxtrxmod.SensorEvent): + return event.values.get("Sensor Status") in [ + *SENSOR_STATUS_ON, + *SENSOR_STATUS_OFF, + ] + return False for packet_id, entity_info in discovery_info[CONF_DEVICES].items(): event = get_rfx_object(packet_id) @@ -70,7 +108,10 @@ async def async_setup_entry( device = RfxtrxBinarySensor( event.device, device_id, - entity_info.get(CONF_DEVICE_CLASS), + entity_info.get( + CONF_DEVICE_CLASS, + DEVICE_TYPE_DEVICE_CLASS.get(event.device.type_string), + ), entity_info.get(CONF_OFF_DELAY), entity_info.get(CONF_DATA_BITS), entity_info.get(CONF_COMMAND_ON), @@ -97,7 +138,12 @@ async def async_setup_entry( event.device.subtype, "".join(f"{x:02x}" for x in event.data), ) - sensor = RfxtrxBinarySensor(event.device, device_id, event=event) + sensor = RfxtrxBinarySensor( + event.device, + device_id, + event=event, + device_class=DEVICE_TYPE_DEVICE_CLASS.get(event.device.type_string), + ) async_add_entities([sensor]) # Subscribe to main RFXtrx events @@ -170,9 +216,13 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity): self._state = True def _apply_event_standard(self, event): - if event.values["Command"] in COMMAND_ON_LIST: + if event.values.get("Command") in COMMAND_ON_LIST: self._state = True - elif event.values["Command"] in COMMAND_OFF_LIST: + elif event.values.get("Command") in COMMAND_OFF_LIST: + self._state = False + elif event.values.get("Sensor Status") in SENSOR_STATUS_ON: + self._state = True + elif event.values.get("Sensor Status") in SENSOR_STATUS_OFF: self._state = False def _apply_event(self, event): @@ -200,7 +250,11 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity): self.async_write_ha_state() - if self.is_on and self._off_delay is not None and self._delay_listener is None: + if self._delay_listener: + self._delay_listener() + self._delay_listener = None + + if self.is_on and self._off_delay is not None: @callback def off_delay_listener(now): diff --git a/tests/components/rfxtrx/conftest.py b/tests/components/rfxtrx/conftest.py index 9ce7ec07f4b..94d440312b4 100644 --- a/tests/components/rfxtrx/conftest.py +++ b/tests/components/rfxtrx/conftest.py @@ -1,16 +1,21 @@ """Common test tools.""" -from unittest import mock +from datetime import timedelta import pytest from homeassistant.components import rfxtrx +from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow + +from tests.async_mock import patch +from tests.common import async_fire_time_changed @pytest.fixture(autouse=True, name="rfxtrx") async def rfxtrx_fixture(hass): """Fixture that cleans up threads from integration.""" - with mock.patch("RFXtrx.Connect") as connect, mock.patch("RFXtrx.DummyTransport2"): + with patch("RFXtrx.Connect") as connect, patch("RFXtrx.DummyTransport2"): rfx = connect.return_value async def _signal_event(packet_id): @@ -26,3 +31,31 @@ async def rfxtrx_fixture(hass): rfx.signal = _signal_event yield rfx + + +@pytest.fixture(name="rfxtrx_automatic") +async def rfxtrx_automatic_fixture(hass, rfxtrx): + """Fixture that starts up with automatic additions.""" + + assert await async_setup_component( + hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}}, + ) + await hass.async_block_till_done() + await hass.async_start() + yield rfxtrx + + +@pytest.fixture +async def timestep(hass): + """Step system time forward.""" + + with patch("homeassistant.core.dt_util.utcnow") as mock_utcnow: + mock_utcnow.return_value = utcnow() + + async def delay(seconds): + """Trigger delay in system.""" + mock_utcnow.return_value += timedelta(seconds=seconds) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + + yield delay diff --git a/tests/components/rfxtrx/test_binary_sensor.py b/tests/components/rfxtrx/test_binary_sensor.py index 50f9ccf0eca..8fbc06f6ddb 100644 --- a/tests/components/rfxtrx/test_binary_sensor.py +++ b/tests/components/rfxtrx/test_binary_sensor.py @@ -1,14 +1,20 @@ """The tests for the Rfxtrx sensor platform.""" -from datetime import timedelta - import pytest from homeassistant.components.rfxtrx.const import ATTR_EVENT from homeassistant.core import State from homeassistant.setup import async_setup_component -from homeassistant.util.dt import utcnow -from tests.common import async_fire_time_changed, mock_restore_cache +from tests.common import mock_restore_cache + +EVENT_SMOKE_DETECTOR_PANIC = "08200300a109000670" +EVENT_SMOKE_DETECTOR_NO_PANIC = "08200300a109000770" + +EVENT_MOTION_DETECTOR_MOTION = "08200100a109000470" +EVENT_MOTION_DETECTOR_NO_MOTION = "08200100a109000570" + +EVENT_LIGHT_DETECTOR_LIGHT = "08200100a109001570" +EVENT_LIGHT_DETECTOR_DARK = "08200100a109001470" async def test_one(hass, rfxtrx): @@ -116,25 +122,9 @@ async def test_several(hass, rfxtrx): assert state.attributes.get("friendly_name") == "AC 1118cdea:2" -async def test_discover(hass, rfxtrx): +async def test_discover(hass, rfxtrx_automatic): """Test with discovery.""" - assert await async_setup_component( - hass, - "rfxtrx", - { - "rfxtrx": { - "device": "abcd", - "automatic_add": True, - "devices": { - "0b1100cd0213c7f230010f71": {}, - "0b1100100118cdea02010f70": {}, - "0b1100101118cdea02010f70": {}, - }, - } - }, - ) - await hass.async_block_till_done() - await hass.async_start() + rfxtrx = rfxtrx_automatic await rfxtrx.signal("0b1100100118cdea02010f70") state = hass.states.get("binary_sensor.ac_118cdea_2") @@ -147,7 +137,7 @@ async def test_discover(hass, rfxtrx): assert state.state == "on" -async def test_off_delay(hass, rfxtrx): +async def test_off_delay(hass, rfxtrx, timestep): """Test with discovery.""" assert await async_setup_component( hass, @@ -171,16 +161,53 @@ async def test_off_delay(hass, rfxtrx): assert state assert state.state == "on" - base_time = utcnow() - - async_fire_time_changed(hass, base_time + timedelta(seconds=4)) - await hass.async_block_till_done() + await timestep(4) state = hass.states.get("binary_sensor.ac_118cdea_2") assert state assert state.state == "on" - async_fire_time_changed(hass, base_time + timedelta(seconds=6)) - await hass.async_block_till_done() + await timestep(4) state = hass.states.get("binary_sensor.ac_118cdea_2") assert state assert state.state == "off" + + +async def test_panic(hass, rfxtrx_automatic): + """Test panic entities.""" + rfxtrx = rfxtrx_automatic + + entity_id = "binary_sensor.kd101_smoke_detector_a10900_32" + + await rfxtrx.signal(EVENT_SMOKE_DETECTOR_PANIC) + assert hass.states.get(entity_id).state == "on" + assert hass.states.get(entity_id).attributes.get("device_class") == "smoke" + + await rfxtrx.signal(EVENT_SMOKE_DETECTOR_NO_PANIC) + assert hass.states.get(entity_id).state == "off" + + +async def test_motion(hass, rfxtrx_automatic): + """Test motion entities.""" + rfxtrx = rfxtrx_automatic + + entity_id = "binary_sensor.x10_security_motion_detector_a10900_32" + + await rfxtrx.signal(EVENT_MOTION_DETECTOR_MOTION) + assert hass.states.get(entity_id).state == "on" + assert hass.states.get(entity_id).attributes.get("device_class") == "motion" + + await rfxtrx.signal(EVENT_MOTION_DETECTOR_NO_MOTION) + assert hass.states.get(entity_id).state == "off" + + +async def test_light(hass, rfxtrx_automatic): + """Test light entities.""" + rfxtrx = rfxtrx_automatic + + entity_id = "binary_sensor.x10_security_motion_detector_a10900_32" + + await rfxtrx.signal(EVENT_LIGHT_DETECTOR_LIGHT) + assert hass.states.get(entity_id).state == "on" + + await rfxtrx.signal(EVENT_LIGHT_DETECTOR_DARK) + assert hass.states.get(entity_id).state == "off" diff --git a/tests/components/rfxtrx/test_cover.py b/tests/components/rfxtrx/test_cover.py index ffbef93daec..ce4838cebf1 100644 --- a/tests/components/rfxtrx/test_cover.py +++ b/tests/components/rfxtrx/test_cover.py @@ -101,13 +101,9 @@ async def test_several_covers(hass, rfxtrx): assert state.attributes.get("friendly_name") == "RollerTrol 009ba8:1" -async def test_discover_covers(hass, rfxtrx): +async def test_discover_covers(hass, rfxtrx_automatic): """Test with discovery of covers.""" - assert await async_setup_component( - hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}} - ) - await hass.async_block_till_done() - await hass.async_start() + rfxtrx = rfxtrx_automatic await rfxtrx.signal("0a140002f38cae010f0070") state = hass.states.get("cover.lightwaverf_siemens_f38cae_1") diff --git a/tests/components/rfxtrx/test_light.py b/tests/components/rfxtrx/test_light.py index ead6d638841..3ebd1bdef39 100644 --- a/tests/components/rfxtrx/test_light.py +++ b/tests/components/rfxtrx/test_light.py @@ -165,13 +165,9 @@ async def test_repetitions(hass, rfxtrx, repetitions): assert rfxtrx.transport.send.call_count == repetitions -async def test_discover_light(hass, rfxtrx): +async def test_discover_light(hass, rfxtrx_automatic): """Test with discovery of lights.""" - assert await async_setup_component( - hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}}, - ) - await hass.async_block_till_done() - await hass.async_start() + rfxtrx = rfxtrx_automatic await rfxtrx.signal("0b11009e00e6116202020070") state = hass.states.get("light.ac_0e61162_2") diff --git a/tests/components/rfxtrx/test_sensor.py b/tests/components/rfxtrx/test_sensor.py index 1d7b2de9a7a..d87cf1a71e2 100644 --- a/tests/components/rfxtrx/test_sensor.py +++ b/tests/components/rfxtrx/test_sensor.py @@ -148,13 +148,9 @@ async def test_several_sensors(hass, rfxtrx): assert state.attributes.get("unit_of_measurement") == UNIT_PERCENTAGE -async def test_discover_sensor(hass, rfxtrx): +async def test_discover_sensor(hass, rfxtrx_automatic): """Test with discovery of sensor.""" - assert await async_setup_component( - hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}}, - ) - await hass.async_block_till_done() - await hass.async_start() + rfxtrx = rfxtrx_automatic # 1 await rfxtrx.signal("0a520801070100b81b0279") diff --git a/tests/components/rfxtrx/test_switch.py b/tests/components/rfxtrx/test_switch.py index e4f7763c299..c163401142e 100644 --- a/tests/components/rfxtrx/test_switch.py +++ b/tests/components/rfxtrx/test_switch.py @@ -120,13 +120,9 @@ async def test_repetitions(hass, rfxtrx, repetitions): assert rfxtrx.transport.send.call_count == repetitions -async def test_discover_switch(hass, rfxtrx): +async def test_discover_switch(hass, rfxtrx_automatic): """Test with discovery of switches.""" - assert await async_setup_component( - hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}}, - ) - await hass.async_block_till_done() - await hass.async_start() + rfxtrx = rfxtrx_automatic await rfxtrx.signal("0b1100100118cdea02010f70") state = hass.states.get("switch.ac_118cdea_2")