mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Support rfxtrx smoke detectors, motion sensors as binary_sensors (#38000)
* Add binary sensor support to motion sensors and smoke detectors * Add support for new sensor events as binary sensors Adds a default device_class for motion sensors and smoke detector * Use device type instead of event to set class * Add some additional binary values
This commit is contained in:
parent
84df0efb5e
commit
632a36d819
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user