mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +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
|
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 (
|
from homeassistant.const import (
|
||||||
CONF_COMMAND_OFF,
|
CONF_COMMAND_OFF,
|
||||||
CONF_COMMAND_ON,
|
CONF_COMMAND_ON,
|
||||||
@ -34,6 +38,33 @@ from .const import (
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_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(
|
async def async_setup_entry(
|
||||||
hass, config_entry, async_add_entities,
|
hass, config_entry, async_add_entities,
|
||||||
):
|
):
|
||||||
@ -46,7 +77,14 @@ async def async_setup_entry(
|
|||||||
discovery_info = config_entry.data
|
discovery_info = config_entry.data
|
||||||
|
|
||||||
def supported(event):
|
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():
|
for packet_id, entity_info in discovery_info[CONF_DEVICES].items():
|
||||||
event = get_rfx_object(packet_id)
|
event = get_rfx_object(packet_id)
|
||||||
@ -70,7 +108,10 @@ async def async_setup_entry(
|
|||||||
device = RfxtrxBinarySensor(
|
device = RfxtrxBinarySensor(
|
||||||
event.device,
|
event.device,
|
||||||
device_id,
|
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_OFF_DELAY),
|
||||||
entity_info.get(CONF_DATA_BITS),
|
entity_info.get(CONF_DATA_BITS),
|
||||||
entity_info.get(CONF_COMMAND_ON),
|
entity_info.get(CONF_COMMAND_ON),
|
||||||
@ -97,7 +138,12 @@ async def async_setup_entry(
|
|||||||
event.device.subtype,
|
event.device.subtype,
|
||||||
"".join(f"{x:02x}" for x in event.data),
|
"".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])
|
async_add_entities([sensor])
|
||||||
|
|
||||||
# Subscribe to main RFXtrx events
|
# Subscribe to main RFXtrx events
|
||||||
@ -170,9 +216,13 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
|
|||||||
self._state = True
|
self._state = True
|
||||||
|
|
||||||
def _apply_event_standard(self, event):
|
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
|
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
|
self._state = False
|
||||||
|
|
||||||
def _apply_event(self, event):
|
def _apply_event(self, event):
|
||||||
@ -200,7 +250,11 @@ class RfxtrxBinarySensor(RfxtrxEntity, BinarySensorEntity):
|
|||||||
|
|
||||||
self.async_write_ha_state()
|
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
|
@callback
|
||||||
def off_delay_listener(now):
|
def off_delay_listener(now):
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
"""Common test tools."""
|
"""Common test tools."""
|
||||||
from unittest import mock
|
from datetime import timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import rfxtrx
|
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")
|
@pytest.fixture(autouse=True, name="rfxtrx")
|
||||||
async def rfxtrx_fixture(hass):
|
async def rfxtrx_fixture(hass):
|
||||||
"""Fixture that cleans up threads from integration."""
|
"""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
|
rfx = connect.return_value
|
||||||
|
|
||||||
async def _signal_event(packet_id):
|
async def _signal_event(packet_id):
|
||||||
@ -26,3 +31,31 @@ async def rfxtrx_fixture(hass):
|
|||||||
rfx.signal = _signal_event
|
rfx.signal = _signal_event
|
||||||
|
|
||||||
yield rfx
|
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."""
|
"""The tests for the Rfxtrx sensor platform."""
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.rfxtrx.const import ATTR_EVENT
|
from homeassistant.components.rfxtrx.const import ATTR_EVENT
|
||||||
from homeassistant.core import State
|
from homeassistant.core import State
|
||||||
from homeassistant.setup import async_setup_component
|
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):
|
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"
|
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."""
|
"""Test with discovery."""
|
||||||
assert await async_setup_component(
|
rfxtrx = rfxtrx_automatic
|
||||||
hass,
|
|
||||||
"rfxtrx",
|
|
||||||
{
|
|
||||||
"rfxtrx": {
|
|
||||||
"device": "abcd",
|
|
||||||
"automatic_add": True,
|
|
||||||
"devices": {
|
|
||||||
"0b1100cd0213c7f230010f71": {},
|
|
||||||
"0b1100100118cdea02010f70": {},
|
|
||||||
"0b1100101118cdea02010f70": {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_start()
|
|
||||||
|
|
||||||
await rfxtrx.signal("0b1100100118cdea02010f70")
|
await rfxtrx.signal("0b1100100118cdea02010f70")
|
||||||
state = hass.states.get("binary_sensor.ac_118cdea_2")
|
state = hass.states.get("binary_sensor.ac_118cdea_2")
|
||||||
@ -147,7 +137,7 @@ async def test_discover(hass, rfxtrx):
|
|||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
|
|
||||||
|
|
||||||
async def test_off_delay(hass, rfxtrx):
|
async def test_off_delay(hass, rfxtrx, timestep):
|
||||||
"""Test with discovery."""
|
"""Test with discovery."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
@ -171,16 +161,53 @@ async def test_off_delay(hass, rfxtrx):
|
|||||||
assert state
|
assert state
|
||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
|
|
||||||
base_time = utcnow()
|
await timestep(4)
|
||||||
|
|
||||||
async_fire_time_changed(hass, base_time + timedelta(seconds=4))
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
state = hass.states.get("binary_sensor.ac_118cdea_2")
|
state = hass.states.get("binary_sensor.ac_118cdea_2")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
|
|
||||||
async_fire_time_changed(hass, base_time + timedelta(seconds=6))
|
await timestep(4)
|
||||||
await hass.async_block_till_done()
|
|
||||||
state = hass.states.get("binary_sensor.ac_118cdea_2")
|
state = hass.states.get("binary_sensor.ac_118cdea_2")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == "off"
|
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"
|
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."""
|
"""Test with discovery of covers."""
|
||||||
assert await async_setup_component(
|
rfxtrx = rfxtrx_automatic
|
||||||
hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_start()
|
|
||||||
|
|
||||||
await rfxtrx.signal("0a140002f38cae010f0070")
|
await rfxtrx.signal("0a140002f38cae010f0070")
|
||||||
state = hass.states.get("cover.lightwaverf_siemens_f38cae_1")
|
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
|
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."""
|
"""Test with discovery of lights."""
|
||||||
assert await async_setup_component(
|
rfxtrx = rfxtrx_automatic
|
||||||
hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_start()
|
|
||||||
|
|
||||||
await rfxtrx.signal("0b11009e00e6116202020070")
|
await rfxtrx.signal("0b11009e00e6116202020070")
|
||||||
state = hass.states.get("light.ac_0e61162_2")
|
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
|
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."""
|
"""Test with discovery of sensor."""
|
||||||
assert await async_setup_component(
|
rfxtrx = rfxtrx_automatic
|
||||||
hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_start()
|
|
||||||
|
|
||||||
# 1
|
# 1
|
||||||
await rfxtrx.signal("0a520801070100b81b0279")
|
await rfxtrx.signal("0a520801070100b81b0279")
|
||||||
|
@ -120,13 +120,9 @@ async def test_repetitions(hass, rfxtrx, repetitions):
|
|||||||
assert rfxtrx.transport.send.call_count == 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."""
|
"""Test with discovery of switches."""
|
||||||
assert await async_setup_component(
|
rfxtrx = rfxtrx_automatic
|
||||||
hass, "rfxtrx", {"rfxtrx": {"device": "abcd", "automatic_add": True}},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_start()
|
|
||||||
|
|
||||||
await rfxtrx.signal("0b1100100118cdea02010f70")
|
await rfxtrx.signal("0b1100100118cdea02010f70")
|
||||||
state = hass.states.get("switch.ac_118cdea_2")
|
state = hass.states.get("switch.ac_118cdea_2")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user