2019-02-13 20:21:14 +00:00
|
|
|
"""Support for RFXtrx binary sensors."""
|
2017-06-22 05:48:45 +00:00
|
|
|
import logging
|
2019-10-14 15:38:34 +00:00
|
|
|
|
2019-10-12 19:37:59 +00:00
|
|
|
import RFXtrx as rfxtrxmod
|
2017-06-22 05:48:45 +00:00
|
|
|
import voluptuous as vol
|
2018-01-12 19:52:53 +00:00
|
|
|
|
|
|
|
from homeassistant.components.binary_sensor import (
|
2019-07-31 19:25:30 +00:00
|
|
|
DEVICE_CLASSES_SCHEMA,
|
|
|
|
PLATFORM_SCHEMA,
|
2020-04-23 19:57:07 +00:00
|
|
|
BinarySensorEntity,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-01-21 06:35:38 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_COMMAND_OFF,
|
|
|
|
CONF_COMMAND_ON,
|
|
|
|
CONF_DEVICE_CLASS,
|
2020-07-10 12:52:07 +00:00
|
|
|
CONF_DEVICES,
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_NAME,
|
|
|
|
)
|
2019-03-21 05:56:46 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv, event as evt
|
|
|
|
|
|
|
|
from . import (
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_AUTOMATIC_ADD,
|
|
|
|
CONF_DATA_BITS,
|
|
|
|
CONF_FIRE_EVENT,
|
|
|
|
CONF_OFF_DELAY,
|
2020-07-05 22:10:26 +00:00
|
|
|
SIGNAL_EVENT,
|
2019-12-11 14:58:49 +00:00
|
|
|
find_possible_pt2262_device,
|
2020-07-05 22:10:26 +00:00
|
|
|
fire_command_event,
|
2020-07-10 12:52:07 +00:00
|
|
|
get_device_id,
|
2019-12-11 14:58:49 +00:00
|
|
|
get_pt2262_cmd,
|
|
|
|
get_pt2262_deviceid,
|
|
|
|
get_rfx_object,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2020-07-10 12:52:07 +00:00
|
|
|
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST, DEVICE_PACKET_TYPE_LIGHTING4
|
2017-06-22 05:48:45 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2020-07-10 12:52:07 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
|
|
{
|
|
|
|
vol.Optional(CONF_DEVICES, default={}): {
|
|
|
|
cv.string: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Optional(CONF_NAME): cv.string,
|
|
|
|
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
|
|
|
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
|
|
|
|
vol.Optional(CONF_OFF_DELAY): vol.Any(
|
|
|
|
cv.time_period, cv.positive_timedelta
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_DATA_BITS): cv.positive_int,
|
|
|
|
vol.Optional(CONF_COMMAND_ON): cv.byte,
|
|
|
|
vol.Optional(CONF_COMMAND_OFF): cv.byte,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean,
|
2018-01-12 19:52:53 +00:00
|
|
|
},
|
2019-07-31 19:25:30 +00:00
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
2017-06-22 05:48:45 +00:00
|
|
|
|
|
|
|
|
2020-07-10 12:52:07 +00:00
|
|
|
def _get_device_data_bits(device, device_bits):
|
|
|
|
"""Deduce data bits for device based on a cache of device bits."""
|
|
|
|
data_bits = None
|
|
|
|
if device.packettype == DEVICE_PACKET_TYPE_LIGHTING4:
|
|
|
|
for id_masked, bits in device_bits.items():
|
|
|
|
if get_pt2262_deviceid(device.id_string, bits) == id_masked:
|
|
|
|
data_bits = bits
|
|
|
|
break
|
|
|
|
return data_bits
|
|
|
|
|
|
|
|
|
2018-08-24 14:37:30 +00:00
|
|
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Set up the Binary Sensor platform to RFXtrx."""
|
2017-06-22 05:48:45 +00:00
|
|
|
sensors = []
|
|
|
|
|
2020-07-10 12:52:07 +00:00
|
|
|
device_ids = set()
|
|
|
|
device_bits = {}
|
|
|
|
|
|
|
|
pt2262_devices = []
|
|
|
|
|
2018-02-17 09:29:14 +00:00
|
|
|
for packet_id, entity in config[CONF_DEVICES].items():
|
2019-12-11 14:58:49 +00:00
|
|
|
event = get_rfx_object(packet_id)
|
2020-07-10 12:52:07 +00:00
|
|
|
if event is None:
|
|
|
|
_LOGGER.error("Invalid device: %s", packet_id)
|
|
|
|
continue
|
2017-06-22 05:48:45 +00:00
|
|
|
|
2020-07-10 12:52:07 +00:00
|
|
|
device_id = get_device_id(event.device, data_bits=entity.get(CONF_DATA_BITS))
|
|
|
|
if device_id in device_ids:
|
2017-06-22 05:48:45 +00:00
|
|
|
continue
|
2020-07-10 12:52:07 +00:00
|
|
|
device_ids.add(device_id)
|
2017-06-22 05:48:45 +00:00
|
|
|
|
2020-07-10 12:52:07 +00:00
|
|
|
if event.device.packettype == DEVICE_PACKET_TYPE_LIGHTING4:
|
|
|
|
find_possible_pt2262_device(pt2262_devices, event.device.id_string)
|
|
|
|
pt2262_devices.append(event.device.id_string)
|
2017-06-22 05:48:45 +00:00
|
|
|
|
2018-01-21 06:35:38 +00:00
|
|
|
device = RfxtrxBinarySensor(
|
2020-07-09 09:40:37 +00:00
|
|
|
event.device,
|
2019-07-31 19:25:30 +00:00
|
|
|
entity.get(CONF_NAME),
|
|
|
|
entity.get(CONF_DEVICE_CLASS),
|
|
|
|
entity[CONF_FIRE_EVENT],
|
|
|
|
entity.get(CONF_OFF_DELAY),
|
|
|
|
entity.get(CONF_DATA_BITS),
|
|
|
|
entity.get(CONF_COMMAND_ON),
|
|
|
|
entity.get(CONF_COMMAND_OFF),
|
|
|
|
)
|
2017-06-22 05:48:45 +00:00
|
|
|
sensors.append(device)
|
|
|
|
|
2018-08-24 14:37:30 +00:00
|
|
|
add_entities(sensors)
|
2017-06-22 05:48:45 +00:00
|
|
|
|
|
|
|
def binary_sensor_update(event):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Call for control updates from the RFXtrx gateway."""
|
2017-06-22 05:48:45 +00:00
|
|
|
if not isinstance(event, rfxtrxmod.ControlEvent):
|
|
|
|
return
|
|
|
|
|
2020-07-10 12:52:07 +00:00
|
|
|
data_bits = _get_device_data_bits(event.device, device_bits)
|
|
|
|
|
|
|
|
device_id = get_device_id(event.device, data_bits=data_bits)
|
|
|
|
if device_id in device_ids:
|
|
|
|
return
|
|
|
|
device_ids.add(device_id)
|
|
|
|
|
|
|
|
_LOGGER.info(
|
|
|
|
"Added binary sensor (Device ID: %s Class: %s Sub: %s)",
|
|
|
|
event.device.id_string.lower(),
|
|
|
|
event.device.__class__.__name__,
|
|
|
|
event.device.subtype,
|
|
|
|
)
|
|
|
|
pkt_id = "".join(f"{x:02x}" for x in event.data)
|
|
|
|
sensor = RfxtrxBinarySensor(
|
|
|
|
event.device, pkt_id, data_bits=data_bits, event=event
|
|
|
|
)
|
|
|
|
add_entities([sensor])
|
2017-06-22 05:48:45 +00:00
|
|
|
|
2018-01-21 06:35:38 +00:00
|
|
|
# Subscribe to main RFXtrx events
|
2020-07-10 12:52:07 +00:00
|
|
|
if config[CONF_AUTOMATIC_ADD]:
|
|
|
|
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, binary_sensor_update)
|
2017-06-22 05:48:45 +00:00
|
|
|
|
|
|
|
|
2020-04-23 19:57:07 +00:00
|
|
|
class RfxtrxBinarySensor(BinarySensorEntity):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""A representation of a RFXtrx binary sensor."""
|
2017-06-22 05:48:45 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
2020-07-09 09:40:37 +00:00
|
|
|
device,
|
2019-07-31 19:25:30 +00:00
|
|
|
name,
|
|
|
|
device_class=None,
|
|
|
|
should_fire=False,
|
|
|
|
off_delay=None,
|
|
|
|
data_bits=None,
|
|
|
|
cmd_on=None,
|
|
|
|
cmd_off=None,
|
2020-07-09 09:40:37 +00:00
|
|
|
event=None,
|
2019-07-31 19:25:30 +00:00
|
|
|
):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Initialize the RFXtrx sensor."""
|
2020-07-09 09:40:37 +00:00
|
|
|
self.event = None
|
|
|
|
self._device = device
|
2017-06-22 05:48:45 +00:00
|
|
|
self._name = name
|
|
|
|
self._should_fire_event = should_fire
|
2017-06-22 21:00:44 +00:00
|
|
|
self._device_class = device_class
|
2017-06-22 05:48:45 +00:00
|
|
|
self._off_delay = off_delay
|
|
|
|
self._state = False
|
|
|
|
self.delay_listener = None
|
|
|
|
self._data_bits = data_bits
|
|
|
|
self._cmd_on = cmd_on
|
|
|
|
self._cmd_off = cmd_off
|
|
|
|
|
2020-07-10 12:52:07 +00:00
|
|
|
self._device_id = get_device_id(device, data_bits=data_bits)
|
|
|
|
self._unique_id = "_".join(x for x in self._device_id)
|
2020-07-09 09:40:37 +00:00
|
|
|
|
|
|
|
if event:
|
|
|
|
self._apply_event(event)
|
2017-06-22 05:48:45 +00:00
|
|
|
|
2020-07-05 22:10:26 +00:00
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""Restore RFXtrx switch device state (ON/OFF)."""
|
|
|
|
await super().async_added_to_hass()
|
|
|
|
|
|
|
|
self.async_on_remove(
|
|
|
|
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
2020-07-09 09:40:37 +00:00
|
|
|
SIGNAL_EVENT, self._handle_event
|
2020-07-05 22:10:26 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2017-06-22 05:48:45 +00:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the device name."""
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def data_bits(self):
|
|
|
|
"""Return the number of data bits."""
|
|
|
|
return self._data_bits
|
|
|
|
|
|
|
|
@property
|
|
|
|
def cmd_on(self):
|
|
|
|
"""Return the value of the 'On' command."""
|
|
|
|
return self._cmd_on
|
|
|
|
|
|
|
|
@property
|
|
|
|
def cmd_off(self):
|
|
|
|
"""Return the value of the 'Off' command."""
|
|
|
|
return self._cmd_off
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
|
|
|
"""No polling needed."""
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_fire_event(self):
|
|
|
|
"""Return is the device must fire event."""
|
|
|
|
return self._should_fire_event
|
|
|
|
|
|
|
|
@property
|
2017-06-22 21:00:44 +00:00
|
|
|
def device_class(self):
|
2017-06-22 05:48:45 +00:00
|
|
|
"""Return the sensor class."""
|
2017-06-22 21:00:44 +00:00
|
|
|
return self._device_class
|
2017-06-22 05:48:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def off_delay(self):
|
|
|
|
"""Return the off_delay attribute value."""
|
|
|
|
return self._off_delay
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_on(self):
|
|
|
|
"""Return true if the sensor state is True."""
|
|
|
|
return self._state
|
|
|
|
|
2020-06-28 04:59:42 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self):
|
|
|
|
"""Return unique identifier of remote device."""
|
|
|
|
return self._unique_id
|
|
|
|
|
2020-07-05 22:10:26 +00:00
|
|
|
def _apply_event_lighting4(self, event):
|
|
|
|
"""Apply event for a lighting 4 device."""
|
|
|
|
if self.data_bits is not None:
|
|
|
|
cmd = get_pt2262_cmd(event.device.id_string, self.data_bits)
|
|
|
|
cmd = int(cmd, 16)
|
|
|
|
if cmd == self.cmd_on:
|
|
|
|
self._state = True
|
|
|
|
elif cmd == self.cmd_off:
|
|
|
|
self._state = False
|
|
|
|
else:
|
|
|
|
self._state = True
|
|
|
|
|
|
|
|
def _apply_event_standard(self, event):
|
|
|
|
if event.values["Command"] in COMMAND_ON_LIST:
|
|
|
|
self._state = True
|
|
|
|
elif event.values["Command"] in COMMAND_OFF_LIST:
|
|
|
|
self._state = False
|
|
|
|
|
2020-07-09 09:40:37 +00:00
|
|
|
def _apply_event(self, event):
|
2020-07-05 22:10:26 +00:00
|
|
|
"""Apply command from rfxtrx."""
|
2020-07-10 12:52:07 +00:00
|
|
|
if event.device.packettype == DEVICE_PACKET_TYPE_LIGHTING4:
|
2020-07-05 22:10:26 +00:00
|
|
|
self._apply_event_lighting4(event)
|
|
|
|
else:
|
|
|
|
self._apply_event_standard(event)
|
2017-06-22 05:48:45 +00:00
|
|
|
|
2020-07-09 09:40:37 +00:00
|
|
|
def _handle_event(self, event):
|
|
|
|
"""Check if event applies to me and update."""
|
2020-07-10 12:52:07 +00:00
|
|
|
if get_device_id(event.device, data_bits=self._data_bits) != self._device_id:
|
|
|
|
return
|
2020-07-09 09:40:37 +00:00
|
|
|
|
|
|
|
_LOGGER.debug(
|
|
|
|
"Binary sensor update (Device ID: %s Class: %s Sub: %s)",
|
|
|
|
event.device.id_string,
|
|
|
|
event.device.__class__.__name__,
|
|
|
|
event.device.subtype,
|
|
|
|
)
|
|
|
|
|
|
|
|
self._apply_event(event)
|
|
|
|
|
|
|
|
self.schedule_update_ha_state()
|
|
|
|
if self.should_fire_event:
|
|
|
|
fire_command_event(self.hass, self.entity_id, event.values["Command"])
|
2020-07-05 22:10:26 +00:00
|
|
|
|
|
|
|
if self.is_on and self.off_delay is not None and self.delay_listener is None:
|
|
|
|
|
|
|
|
def off_delay_listener(now):
|
|
|
|
"""Switch device off after a delay."""
|
|
|
|
self.delay_listener = None
|
|
|
|
self._state = False
|
2020-07-09 09:40:37 +00:00
|
|
|
self.schedule_update_ha_state()
|
2020-07-05 22:10:26 +00:00
|
|
|
|
|
|
|
self.delay_listener = evt.call_later(
|
|
|
|
self.hass, self.off_delay.total_seconds(), off_delay_listener
|
|
|
|
)
|