Rewrite rfxtrx init logic to do away with global object (#37699)
* Rewrite init logic to do away with global object * Put constant at end * Use a set instead of list for device_ids
This commit is contained in:
parent
b45a952d61
commit
67038c6ba8
11 changed files with 365 additions and 334 deletions
|
@ -8,10 +8,8 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_NAME,
|
||||
ATTR_STATE,
|
||||
CONF_DEVICE,
|
||||
CONF_DEVICES,
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
|
@ -25,6 +23,8 @@ import homeassistant.helpers.config_validation as cv
|
|||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import DEVICE_PACKET_TYPE_LIGHTING4
|
||||
|
||||
DOMAIN = "rfxtrx"
|
||||
|
||||
DEFAULT_SIGNAL_REPETITIONS = 1
|
||||
|
@ -80,7 +80,6 @@ DATA_TYPES = OrderedDict(
|
|||
]
|
||||
)
|
||||
|
||||
RFX_DEVICES = {}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DATA_RFXOBJECT = "rfxobject"
|
||||
|
||||
|
@ -207,31 +206,15 @@ def get_pt2262_cmd(device_id, data_bits):
|
|||
return hex(data[-1] & mask)
|
||||
|
||||
|
||||
def get_pt2262_device(device_id):
|
||||
def find_possible_pt2262_device(device_ids, device_id):
|
||||
"""Look for the device which id matches the given device_id parameter."""
|
||||
for device in RFX_DEVICES.values():
|
||||
if (
|
||||
hasattr(device, "is_lighting4")
|
||||
and device.masked_id is not None
|
||||
and device.masked_id == get_pt2262_deviceid(device_id, device.data_bits)
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"rfxtrx: found matching device %s for %s", device_id, device.masked_id,
|
||||
)
|
||||
return device
|
||||
return None
|
||||
|
||||
|
||||
def find_possible_pt2262_device(device_id):
|
||||
"""Look for the device which id matches the given device_id parameter."""
|
||||
for dev_id, device in RFX_DEVICES.items():
|
||||
if hasattr(device, "is_lighting4") and len(dev_id) == len(device_id):
|
||||
for dev_id in device_ids:
|
||||
if len(dev_id) == len(device_id):
|
||||
size = None
|
||||
for i, (char1, char2) in enumerate(zip(dev_id, device_id)):
|
||||
if char1 != char2:
|
||||
break
|
||||
size = i
|
||||
|
||||
if size is not None:
|
||||
size = len(dev_id) - size - 1
|
||||
_LOGGER.info(
|
||||
|
@ -246,60 +229,19 @@ def find_possible_pt2262_device(device_id):
|
|||
dev_id[-size:],
|
||||
device_id[-size:],
|
||||
)
|
||||
return device
|
||||
|
||||
return dev_id
|
||||
return None
|
||||
|
||||
|
||||
def get_devices_from_config(config, device):
|
||||
"""Read rfxtrx configuration."""
|
||||
signal_repetitions = config[CONF_SIGNAL_REPETITIONS]
|
||||
def get_device_id(device, data_bits=None):
|
||||
"""Calculate a device id for device."""
|
||||
id_string = device.id_string
|
||||
if data_bits and device.packettype == DEVICE_PACKET_TYPE_LIGHTING4:
|
||||
masked_id = get_pt2262_deviceid(id_string, data_bits)
|
||||
if masked_id:
|
||||
id_string = str(masked_id)
|
||||
|
||||
devices = []
|
||||
for packet_id, entity_info in config[CONF_DEVICES].items():
|
||||
event = get_rfx_object(packet_id)
|
||||
if event is None:
|
||||
_LOGGER.error("Invalid device: %s", packet_id)
|
||||
continue
|
||||
device_id = slugify(event.device.id_string.lower())
|
||||
if device_id in RFX_DEVICES:
|
||||
continue
|
||||
_LOGGER.debug("Add %s rfxtrx", entity_info[ATTR_NAME])
|
||||
|
||||
# Check if i must fire event
|
||||
fire_event = entity_info[ATTR_FIRE_EVENT]
|
||||
datas = {ATTR_STATE: False, ATTR_FIRE_EVENT: fire_event}
|
||||
|
||||
new_device = device(
|
||||
entity_info[ATTR_NAME], event.device, datas, signal_repetitions
|
||||
)
|
||||
RFX_DEVICES[device_id] = new_device
|
||||
devices.append(new_device)
|
||||
return devices
|
||||
|
||||
|
||||
def get_new_device(event, config, device):
|
||||
"""Add entity if not exist and the automatic_add is True."""
|
||||
device_id = slugify(event.device.id_string.lower())
|
||||
if device_id in RFX_DEVICES:
|
||||
return
|
||||
|
||||
if not config[ATTR_AUTOMATIC_ADD]:
|
||||
return
|
||||
|
||||
pkt_id = "".join(f"{x:02x}" for x in event.data)
|
||||
_LOGGER.debug(
|
||||
"Automatic add %s rfxtrx device (Class: %s Sub: %s Packet_id: %s)",
|
||||
device_id,
|
||||
event.device.__class__.__name__,
|
||||
event.device.subtype,
|
||||
pkt_id,
|
||||
)
|
||||
datas = {ATTR_STATE: False, ATTR_FIRE_EVENT: False}
|
||||
signal_repetitions = config[CONF_SIGNAL_REPETITIONS]
|
||||
new_device = device(pkt_id, event.device, datas, signal_repetitions, event=event)
|
||||
RFX_DEVICES[device_id] = new_device
|
||||
return new_device
|
||||
return (f"{device.packettype:x}", f"{device.subtype:x}", id_string)
|
||||
|
||||
|
||||
def fire_command_event(hass, entity_id, command):
|
||||
|
@ -330,7 +272,8 @@ class RfxtrxDevice(Entity):
|
|||
self._device = device
|
||||
self._state = datas[ATTR_STATE]
|
||||
self._should_fire_event = datas[ATTR_FIRE_EVENT]
|
||||
self._unique_id = f"{device.packettype:x}_{device.subtype:x}_{device.id_string}"
|
||||
self._device_id = get_device_id(device)
|
||||
self._unique_id = "_".join(x for x in self._device_id)
|
||||
|
||||
if event:
|
||||
self._apply_event(event)
|
||||
|
|
|
@ -13,31 +13,29 @@ from homeassistant.const import (
|
|||
CONF_COMMAND_OFF,
|
||||
CONF_COMMAND_ON,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_DEVICES,
|
||||
CONF_NAME,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv, event as evt
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from . import (
|
||||
ATTR_NAME,
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DATA_BITS,
|
||||
CONF_DEVICES,
|
||||
CONF_FIRE_EVENT,
|
||||
CONF_OFF_DELAY,
|
||||
RFX_DEVICES,
|
||||
SIGNAL_EVENT,
|
||||
find_possible_pt2262_device,
|
||||
fire_command_event,
|
||||
get_device_id,
|
||||
get_pt2262_cmd,
|
||||
get_pt2262_device,
|
||||
get_pt2262_deviceid,
|
||||
get_rfx_object,
|
||||
)
|
||||
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST
|
||||
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST, DEVICE_PACKET_TYPE_LIGHTING4
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_DEVICES, default={}): {
|
||||
|
@ -61,28 +59,40 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Binary Sensor platform to RFXtrx."""
|
||||
sensors = []
|
||||
|
||||
device_ids = set()
|
||||
device_bits = {}
|
||||
|
||||
pt2262_devices = []
|
||||
|
||||
for packet_id, entity in config[CONF_DEVICES].items():
|
||||
event = get_rfx_object(packet_id)
|
||||
device_id = slugify(event.device.id_string.lower())
|
||||
|
||||
if device_id in RFX_DEVICES:
|
||||
if event is None:
|
||||
_LOGGER.error("Invalid device: %s", packet_id)
|
||||
continue
|
||||
|
||||
if entity.get(CONF_DATA_BITS) is not None:
|
||||
_LOGGER.debug(
|
||||
"Masked device id: %s",
|
||||
get_pt2262_deviceid(device_id, entity.get(CONF_DATA_BITS)),
|
||||
)
|
||||
device_id = get_device_id(event.device, data_bits=entity.get(CONF_DATA_BITS))
|
||||
if device_id in device_ids:
|
||||
continue
|
||||
device_ids.add(device_id)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Add %s rfxtrx.binary_sensor (class %s)",
|
||||
entity[ATTR_NAME],
|
||||
entity.get(CONF_DEVICE_CLASS),
|
||||
)
|
||||
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)
|
||||
|
||||
device = RfxtrxBinarySensor(
|
||||
event.device,
|
||||
|
@ -95,7 +105,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
entity.get(CONF_COMMAND_OFF),
|
||||
)
|
||||
sensors.append(device)
|
||||
RFX_DEVICES[device_id] = device
|
||||
|
||||
add_entities(sensors)
|
||||
|
||||
|
@ -104,35 +113,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
if not isinstance(event, rfxtrxmod.ControlEvent):
|
||||
return
|
||||
|
||||
device_id = slugify(event.device.id_string.lower())
|
||||
data_bits = _get_device_data_bits(event.device, device_bits)
|
||||
|
||||
sensor = RFX_DEVICES.get(device_id, get_pt2262_device(device_id))
|
||||
device_id = get_device_id(event.device, data_bits=data_bits)
|
||||
if device_id in device_ids:
|
||||
return
|
||||
device_ids.add(device_id)
|
||||
|
||||
if sensor is None:
|
||||
# Add the entity if not exists and automatic_add is True
|
||||
if not config[CONF_AUTOMATIC_ADD]:
|
||||
return
|
||||
|
||||
if event.device.packettype == 0x13:
|
||||
poss_dev = find_possible_pt2262_device(device_id)
|
||||
if poss_dev is not None:
|
||||
poss_id = slugify(poss_dev.event.device.id_string.lower())
|
||||
_LOGGER.debug("Found possible matching device ID: %s", poss_id)
|
||||
|
||||
pkt_id = "".join(f"{x:02x}" for x in event.data)
|
||||
sensor = RfxtrxBinarySensor(event.device, pkt_id, event=event)
|
||||
RFX_DEVICES[device_id] = sensor
|
||||
add_entities([sensor])
|
||||
_LOGGER.info(
|
||||
"Added binary sensor %s (Device ID: %s Class: %s Sub: %s)",
|
||||
pkt_id,
|
||||
slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__,
|
||||
event.device.subtype,
|
||||
)
|
||||
_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])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, binary_sensor_update)
|
||||
if config[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, binary_sensor_update)
|
||||
|
||||
|
||||
class RfxtrxBinarySensor(BinarySensorEntity):
|
||||
|
@ -158,22 +160,13 @@ class RfxtrxBinarySensor(BinarySensorEntity):
|
|||
self._device_class = device_class
|
||||
self._off_delay = off_delay
|
||||
self._state = False
|
||||
self.is_lighting4 = device.packettype == 0x13
|
||||
self.delay_listener = None
|
||||
self._data_bits = data_bits
|
||||
self._cmd_on = cmd_on
|
||||
self._cmd_off = cmd_off
|
||||
|
||||
if data_bits is not None:
|
||||
self._masked_id = get_pt2262_deviceid(device.id_string.lower(), data_bits)
|
||||
self._unique_id = (
|
||||
f"{device.packettype:x}_{device.subtype:x}_{self._masked_id}"
|
||||
)
|
||||
else:
|
||||
self._masked_id = None
|
||||
self._unique_id = (
|
||||
f"{device.packettype:x}_{device.subtype:x}_{device.id_string}"
|
||||
)
|
||||
self._device_id = get_device_id(device, data_bits=data_bits)
|
||||
self._unique_id = "_".join(x for x in self._device_id)
|
||||
|
||||
if event:
|
||||
self._apply_event(event)
|
||||
|
@ -193,11 +186,6 @@ class RfxtrxBinarySensor(BinarySensorEntity):
|
|||
"""Return the device name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def masked_id(self):
|
||||
"""Return the masked device id (isolated address bits)."""
|
||||
return self._masked_id
|
||||
|
||||
@property
|
||||
def data_bits(self):
|
||||
"""Return the number of data bits."""
|
||||
|
@ -263,20 +251,15 @@ class RfxtrxBinarySensor(BinarySensorEntity):
|
|||
|
||||
def _apply_event(self, event):
|
||||
"""Apply command from rfxtrx."""
|
||||
if self.is_lighting4:
|
||||
if event.device.packettype == DEVICE_PACKET_TYPE_LIGHTING4:
|
||||
self._apply_event_lighting4(event)
|
||||
else:
|
||||
self._apply_event_standard(event)
|
||||
|
||||
def _handle_event(self, event):
|
||||
"""Check if event applies to me and update."""
|
||||
if self._masked_id:
|
||||
masked_id = get_pt2262_deviceid(event.device.id_string, self._data_bits)
|
||||
if masked_id != self._masked_id:
|
||||
return
|
||||
else:
|
||||
if event.device.id_string != self._device.id_string:
|
||||
return
|
||||
if get_device_id(event.device, data_bits=self._data_bits) != self._device_id:
|
||||
return
|
||||
|
||||
_LOGGER.debug(
|
||||
"Binary sensor update (Device ID: %s Class: %s Sub: %s)",
|
||||
|
|
|
@ -14,3 +14,4 @@ COMMAND_OFF_LIST = [
|
|||
"Down",
|
||||
"Close (inline relay)",
|
||||
]
|
||||
DEVICE_PACKET_TYPE_LIGHTING4 = 0x13
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
"""Support for RFXtrx covers."""
|
||||
import logging
|
||||
|
||||
import RFXtrx as rfxtrxmod
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity
|
||||
from homeassistant.const import CONF_NAME, STATE_OPEN
|
||||
from homeassistant.const import ATTR_STATE, CONF_DEVICES, CONF_NAME, STATE_OPEN
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from . import (
|
||||
ATTR_FIRE_EVENT,
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DEVICES,
|
||||
CONF_FIRE_EVENT,
|
||||
CONF_SIGNAL_REPETITIONS,
|
||||
DEFAULT_SIGNAL_REPETITIONS,
|
||||
SIGNAL_EVENT,
|
||||
RfxtrxDevice,
|
||||
fire_command_event,
|
||||
get_devices_from_config,
|
||||
get_new_device,
|
||||
get_device_id,
|
||||
get_rfx_object,
|
||||
)
|
||||
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST
|
||||
|
||||
|
@ -38,11 +40,32 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
}
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the RFXtrx cover."""
|
||||
covers = get_devices_from_config(config, RfxtrxCover)
|
||||
add_entities(covers)
|
||||
device_ids = set()
|
||||
|
||||
entities = []
|
||||
for packet_id, entity_info in config[CONF_DEVICES].items():
|
||||
event = get_rfx_object(packet_id)
|
||||
if event is None:
|
||||
_LOGGER.error("Invalid device: %s", packet_id)
|
||||
continue
|
||||
|
||||
device_id = get_device_id(event.device)
|
||||
if device_id in device_ids:
|
||||
continue
|
||||
device_ids.add(device_id)
|
||||
|
||||
datas = {ATTR_STATE: None, ATTR_FIRE_EVENT: entity_info[CONF_FIRE_EVENT]}
|
||||
entity = RfxtrxCover(
|
||||
entity_info[CONF_NAME], event.device, datas, config[CONF_SIGNAL_REPETITIONS]
|
||||
)
|
||||
entities.append(entity)
|
||||
|
||||
add_entities(entities)
|
||||
|
||||
def cover_update(event):
|
||||
"""Handle cover updates from the RFXtrx gateway."""
|
||||
|
@ -53,12 +76,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
):
|
||||
return
|
||||
|
||||
new_device = get_new_device(event, config, RfxtrxCover)
|
||||
if new_device:
|
||||
add_entities([new_device])
|
||||
device_id = get_device_id(event.device)
|
||||
if device_id in device_ids:
|
||||
return
|
||||
device_ids.add(device_id)
|
||||
|
||||
_LOGGER.info(
|
||||
"Added cover (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)
|
||||
datas = {ATTR_STATE: False, ATTR_FIRE_EVENT: False}
|
||||
entity = RfxtrxCover(
|
||||
pkt_id, event.device, datas, DEFAULT_SIGNAL_REPETITIONS, event=event
|
||||
)
|
||||
add_entities([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, cover_update)
|
||||
if config[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, cover_update)
|
||||
|
||||
|
||||
class RfxtrxCover(RfxtrxDevice, CoverEntity, RestoreEntity):
|
||||
|
|
|
@ -10,21 +10,21 @@ from homeassistant.components.light import (
|
|||
SUPPORT_BRIGHTNESS,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME, STATE_ON
|
||||
from homeassistant.const import ATTR_STATE, CONF_DEVICES, CONF_NAME, STATE_ON
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from . import (
|
||||
ATTR_FIRE_EVENT,
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DEVICES,
|
||||
CONF_FIRE_EVENT,
|
||||
CONF_SIGNAL_REPETITIONS,
|
||||
DEFAULT_SIGNAL_REPETITIONS,
|
||||
SIGNAL_EVENT,
|
||||
RfxtrxDevice,
|
||||
fire_command_event,
|
||||
get_devices_from_config,
|
||||
get_new_device,
|
||||
get_device_id,
|
||||
get_rfx_object,
|
||||
)
|
||||
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST
|
||||
|
||||
|
@ -52,8 +52,29 @@ SUPPORT_RFXTRX = SUPPORT_BRIGHTNESS
|
|||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the RFXtrx platform."""
|
||||
lights = get_devices_from_config(config, RfxtrxLight)
|
||||
add_entities(lights)
|
||||
device_ids = set()
|
||||
|
||||
# Add switch from config file
|
||||
entities = []
|
||||
for packet_id, entity_info in config[CONF_DEVICES].items():
|
||||
event = get_rfx_object(packet_id)
|
||||
if event is None:
|
||||
_LOGGER.error("Invalid device: %s", packet_id)
|
||||
continue
|
||||
|
||||
device_id = get_device_id(event.device)
|
||||
if device_id in device_ids:
|
||||
continue
|
||||
device_ids.add(device_id)
|
||||
|
||||
datas = {ATTR_STATE: None, ATTR_FIRE_EVENT: entity_info[CONF_FIRE_EVENT]}
|
||||
entity = RfxtrxLight(
|
||||
entity_info[CONF_NAME], event.device, datas, config[CONF_SIGNAL_REPETITIONS]
|
||||
)
|
||||
|
||||
entities.append(entity)
|
||||
|
||||
add_entities(entities)
|
||||
|
||||
def light_update(event):
|
||||
"""Handle light updates from the RFXtrx gateway."""
|
||||
|
@ -63,12 +84,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||
):
|
||||
return
|
||||
|
||||
new_device = get_new_device(event, config, RfxtrxLight)
|
||||
if new_device:
|
||||
add_entities([new_device])
|
||||
device_id = get_device_id(event.device)
|
||||
if device_id in device_ids:
|
||||
return
|
||||
device_ids.add(device_id)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Added light (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)
|
||||
datas = {ATTR_STATE: None, ATTR_FIRE_EVENT: False}
|
||||
entity = RfxtrxLight(
|
||||
pkt_id, event.device, datas, DEFAULT_SIGNAL_REPETITIONS, event=event
|
||||
)
|
||||
|
||||
add_entities([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, light_update)
|
||||
if config[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, light_update)
|
||||
|
||||
|
||||
class RfxtrxLight(RfxtrxDevice, LightEntity, RestoreEntity):
|
||||
|
|
|
@ -5,21 +5,17 @@ from RFXtrx import SensorEvent
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME, CONF_NAME
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_DEVICES, CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from . import (
|
||||
ATTR_DATA_TYPE,
|
||||
ATTR_FIRE_EVENT,
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DATA_TYPE,
|
||||
CONF_DEVICES,
|
||||
CONF_FIRE_EVENT,
|
||||
DATA_TYPES,
|
||||
RFX_DEVICES,
|
||||
SIGNAL_EVENT,
|
||||
get_device_id,
|
||||
get_rfx_object,
|
||||
)
|
||||
|
||||
|
@ -46,64 +42,63 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the RFXtrx platform."""
|
||||
sensors = []
|
||||
data_ids = set()
|
||||
|
||||
entities = []
|
||||
for packet_id, entity_info in config[CONF_DEVICES].items():
|
||||
event = get_rfx_object(packet_id)
|
||||
device_id = "sensor_{}".format(slugify(event.device.id_string.lower()))
|
||||
if device_id in RFX_DEVICES:
|
||||
if event is None:
|
||||
_LOGGER.error("Invalid device: %s", packet_id)
|
||||
continue
|
||||
_LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME])
|
||||
|
||||
sub_sensors = {}
|
||||
data_types = entity_info[ATTR_DATA_TYPE]
|
||||
if not data_types:
|
||||
data_types = [""]
|
||||
for data_type in DATA_TYPES:
|
||||
if data_type in event.values:
|
||||
data_types = [data_type]
|
||||
break
|
||||
for _data_type in data_types:
|
||||
new_sensor = RfxtrxSensor(
|
||||
if entity_info[CONF_DATA_TYPE]:
|
||||
data_types = entity_info[CONF_DATA_TYPE]
|
||||
else:
|
||||
data_types = list(set(event.values) & set(DATA_TYPES))
|
||||
|
||||
device_id = get_device_id(event.device)
|
||||
for data_type in data_types:
|
||||
data_id = (*device_id, data_type)
|
||||
if data_id in data_ids:
|
||||
continue
|
||||
data_ids.add(data_id)
|
||||
|
||||
entity = RfxtrxSensor(
|
||||
event.device,
|
||||
entity_info[ATTR_NAME],
|
||||
_data_type,
|
||||
entity_info[ATTR_FIRE_EVENT],
|
||||
entity_info[CONF_NAME],
|
||||
data_type,
|
||||
entity_info[CONF_FIRE_EVENT],
|
||||
)
|
||||
sensors.append(new_sensor)
|
||||
sub_sensors[_data_type] = new_sensor
|
||||
RFX_DEVICES[device_id] = sub_sensors
|
||||
add_entities(sensors)
|
||||
entities.append(entity)
|
||||
|
||||
add_entities(entities)
|
||||
|
||||
def sensor_update(event):
|
||||
"""Handle sensor updates from the RFXtrx gateway."""
|
||||
if not isinstance(event, SensorEvent):
|
||||
return
|
||||
|
||||
device_id = f"sensor_{slugify(event.device.id_string.lower())}"
|
||||
|
||||
if device_id in RFX_DEVICES:
|
||||
return
|
||||
|
||||
# Add entity if not exist and the automatic_add is True
|
||||
if not config[CONF_AUTOMATIC_ADD]:
|
||||
return
|
||||
|
||||
pkt_id = "".join(f"{x:02x}" for x in event.data)
|
||||
_LOGGER.info("Automatic add rfxtrx.sensor: %s", pkt_id)
|
||||
device_id = get_device_id(event.device)
|
||||
for data_type in set(event.values) & set(DATA_TYPES):
|
||||
data_id = (*device_id, data_type)
|
||||
if data_id in data_ids:
|
||||
continue
|
||||
data_ids.add(data_id)
|
||||
|
||||
data_type = ""
|
||||
for _data_type in DATA_TYPES:
|
||||
if _data_type in event.values:
|
||||
data_type = _data_type
|
||||
break
|
||||
new_sensor = RfxtrxSensor(event.device, pkt_id, data_type, event=event)
|
||||
sub_sensors = {}
|
||||
sub_sensors[new_sensor.data_type] = new_sensor
|
||||
RFX_DEVICES[device_id] = sub_sensors
|
||||
add_entities([new_sensor])
|
||||
_LOGGER.debug(
|
||||
"Added sensor (Device ID: %s Class: %s Sub: %s)",
|
||||
event.device.id_string.lower(),
|
||||
event.device.__class__.__name__,
|
||||
event.device.subtype,
|
||||
)
|
||||
|
||||
entity = RfxtrxSensor(event.device, pkt_id, data_type, event=event)
|
||||
add_entities([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, sensor_update)
|
||||
if config[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, sensor_update)
|
||||
|
||||
|
||||
class RfxtrxSensor(Entity):
|
||||
|
@ -117,9 +112,8 @@ class RfxtrxSensor(Entity):
|
|||
self.should_fire_event = should_fire_event
|
||||
self.data_type = data_type
|
||||
self._unit_of_measurement = DATA_TYPES.get(data_type, "")
|
||||
self._unique_id = (
|
||||
f"{device.packettype:x}_{device.subtype:x}_{device.id_string}_{data_type}"
|
||||
)
|
||||
self._device_id = get_device_id(device)
|
||||
self._unique_id = "_".join(x for x in (*self._device_id, data_type))
|
||||
|
||||
if event:
|
||||
self._apply_event(event)
|
||||
|
|
|
@ -5,21 +5,21 @@ import RFXtrx as rfxtrxmod
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity
|
||||
from homeassistant.const import CONF_NAME, STATE_ON
|
||||
from homeassistant.const import ATTR_STATE, CONF_DEVICES, CONF_NAME, STATE_ON
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from . import (
|
||||
ATTR_FIRE_EVENT,
|
||||
CONF_AUTOMATIC_ADD,
|
||||
CONF_DEVICES,
|
||||
CONF_FIRE_EVENT,
|
||||
CONF_SIGNAL_REPETITIONS,
|
||||
DEFAULT_SIGNAL_REPETITIONS,
|
||||
SIGNAL_EVENT,
|
||||
RfxtrxDevice,
|
||||
fire_command_event,
|
||||
get_devices_from_config,
|
||||
get_new_device,
|
||||
get_device_id,
|
||||
get_rfx_object,
|
||||
)
|
||||
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST
|
||||
|
||||
|
@ -45,9 +45,28 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
|
||||
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
|
||||
"""Set up the RFXtrx platform."""
|
||||
device_ids = set()
|
||||
|
||||
# Add switch from config file
|
||||
switches = get_devices_from_config(config, RfxtrxSwitch)
|
||||
add_entities_callback(switches)
|
||||
entities = []
|
||||
for packet_id, entity_info in config[CONF_DEVICES].items():
|
||||
event = get_rfx_object(packet_id)
|
||||
if event is None:
|
||||
_LOGGER.error("Invalid device: %s", packet_id)
|
||||
continue
|
||||
|
||||
device_id = get_device_id(event.device)
|
||||
if device_id in device_ids:
|
||||
continue
|
||||
device_ids.add(device_id)
|
||||
|
||||
datas = {ATTR_STATE: None, ATTR_FIRE_EVENT: entity_info[CONF_FIRE_EVENT]}
|
||||
entity = RfxtrxSwitch(
|
||||
entity_info[CONF_NAME], event.device, datas, config[CONF_SIGNAL_REPETITIONS]
|
||||
)
|
||||
entities.append(entity)
|
||||
|
||||
add_entities_callback(entities)
|
||||
|
||||
def switch_update(event):
|
||||
"""Handle sensor updates from the RFXtrx gateway."""
|
||||
|
@ -58,12 +77,28 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None):
|
|||
):
|
||||
return
|
||||
|
||||
new_device = get_new_device(event, config, RfxtrxSwitch)
|
||||
if new_device:
|
||||
add_entities_callback([new_device])
|
||||
device_id = get_device_id(event.device)
|
||||
if device_id in device_ids:
|
||||
return
|
||||
device_ids.add(device_id)
|
||||
|
||||
_LOGGER.info(
|
||||
"Added switch (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)
|
||||
datas = {ATTR_STATE: None, ATTR_FIRE_EVENT: False}
|
||||
entity = RfxtrxSwitch(
|
||||
pkt_id, event.device, datas, DEFAULT_SIGNAL_REPETITIONS, event=event
|
||||
)
|
||||
add_entities_callback([entity])
|
||||
|
||||
# Subscribe to main RFXtrx events
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, switch_update)
|
||||
if config[CONF_AUTOMATIC_ADD]:
|
||||
hass.helpers.dispatcher.dispatcher_connect(SIGNAL_EVENT, switch_update)
|
||||
|
||||
|
||||
class RfxtrxSwitch(RfxtrxDevice, SwitchEntity, RestoreEntity):
|
||||
|
|
|
@ -59,8 +59,6 @@ async def rfxtrx_cleanup():
|
|||
):
|
||||
yield
|
||||
|
||||
rfxtrx_core.RFX_DEVICES.clear()
|
||||
|
||||
|
||||
@pytest.fixture(name="rfxtrx")
|
||||
async def rfxtrx_fixture(hass):
|
||||
|
|
|
@ -141,7 +141,7 @@ async def test_fire_event(hass):
|
|||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
assert 1 == len(calls)
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == {"entity_id": "switch.test", "state": "on"}
|
||||
|
||||
|
||||
|
@ -187,5 +187,5 @@ async def test_fire_event_sensor(hass):
|
|||
hass.bus.async_listen("signal_received", record_event)
|
||||
|
||||
await _signal_event(hass, "0a520802060101ff0f0269")
|
||||
assert 1 == len(calls)
|
||||
assert calls[0].data == {"entity_id": "sensor.test_temperature"}
|
||||
assert len(calls) == 5
|
||||
assert any(call.data == {"entity_id": "sensor.test_temperature"} for call in calls)
|
||||
|
|
|
@ -57,7 +57,7 @@ async def test_default_config(hass, rfxtrx):
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert 0 == len(rfxtrx_core.RFX_DEVICES)
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
async def test_one_light(hass, rfxtrx):
|
||||
|
|
|
@ -55,12 +55,39 @@ async def test_one_sensor_no_datatype(hass, rfxtrx):
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.test_temperature")
|
||||
base_id = "sensor.test"
|
||||
base_name = "Test"
|
||||
|
||||
state = hass.states.get(f"{base_id}_temperature")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert state.attributes.get("friendly_name") == "Test Temperature"
|
||||
assert state.attributes.get("friendly_name") == f"{base_name} Temperature"
|
||||
assert state.attributes.get("unit_of_measurement") == TEMP_CELSIUS
|
||||
|
||||
state = hass.states.get(f"{base_id}_humidity")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert state.attributes.get("friendly_name") == f"{base_name} Humidity"
|
||||
assert state.attributes.get("unit_of_measurement") == UNIT_PERCENTAGE
|
||||
|
||||
state = hass.states.get(f"{base_id}_humidity_status")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert state.attributes.get("friendly_name") == f"{base_name} Humidity status"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
state = hass.states.get(f"{base_id}_rssi_numeric")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert state.attributes.get("friendly_name") == f"{base_name} Rssi numeric"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
state = hass.states.get(f"{base_id}_battery_numeric")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert state.attributes.get("friendly_name") == f"{base_name} Battery numeric"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
|
||||
async def test_several_sensors(hass, rfxtrx):
|
||||
"""Test with 3 sensors."""
|
||||
|
@ -113,61 +140,94 @@ async def test_discover_sensor(hass, rfxtrx):
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# 1
|
||||
await _signal_event(hass, "0a520801070100b81b0279")
|
||||
state = hass.states.get("sensor.0a520801070100b81b0279_temperature")
|
||||
base_id = "sensor.0a520801070100b81b0279"
|
||||
|
||||
state = hass.states.get(f"{base_id}_humidity")
|
||||
assert state
|
||||
assert state.state == "27"
|
||||
assert state.attributes.get("unit_of_measurement") == UNIT_PERCENTAGE
|
||||
|
||||
state = hass.states.get(f"{base_id}_humidity_status")
|
||||
assert state
|
||||
assert state.state == "normal"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
state = hass.states.get(f"{base_id}_rssi_numeric")
|
||||
assert state
|
||||
assert state.state == "7"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
state = hass.states.get(f"{base_id}_temperature")
|
||||
assert state
|
||||
assert state.state == "18.4"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "0a520801070100b81b0279 Temperature",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
"Humidity status": "normal",
|
||||
"Temperature": 18.4,
|
||||
"Rssi numeric": 7,
|
||||
"Humidity": 27,
|
||||
"Battery numeric": 9,
|
||||
"Humidity status numeric": 2,
|
||||
}.items()
|
||||
)
|
||||
assert state.attributes.get("unit_of_measurement") == TEMP_CELSIUS
|
||||
|
||||
state = hass.states.get(f"{base_id}_battery_numeric")
|
||||
assert state
|
||||
assert state.state == "9"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
# 2
|
||||
await _signal_event(hass, "0a52080405020095240279")
|
||||
state = hass.states.get("sensor.0a52080405020095240279_temperature")
|
||||
base_id = "sensor.0a52080405020095240279"
|
||||
state = hass.states.get(f"{base_id}_humidity")
|
||||
|
||||
assert state
|
||||
assert state.state == "36"
|
||||
assert state.attributes.get("unit_of_measurement") == UNIT_PERCENTAGE
|
||||
|
||||
state = hass.states.get(f"{base_id}_humidity_status")
|
||||
assert state
|
||||
assert state.state == "normal"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
state = hass.states.get(f"{base_id}_rssi_numeric")
|
||||
assert state
|
||||
assert state.state == "7"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
state = hass.states.get(f"{base_id}_temperature")
|
||||
assert state
|
||||
assert state.state == "14.9"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "0a52080405020095240279 Temperature",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
"Humidity status": "normal",
|
||||
"Temperature": 14.9,
|
||||
"Rssi numeric": 7,
|
||||
"Humidity": 36,
|
||||
"Battery numeric": 9,
|
||||
"Humidity status numeric": 2,
|
||||
}.items()
|
||||
)
|
||||
assert state.attributes.get("unit_of_measurement") == TEMP_CELSIUS
|
||||
|
||||
state = hass.states.get(f"{base_id}_battery_numeric")
|
||||
assert state
|
||||
assert state.state == "9"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
# 1 Update
|
||||
await _signal_event(hass, "0a52085e070100b31b0279")
|
||||
state = hass.states.get("sensor.0a520801070100b81b0279_temperature")
|
||||
base_id = "sensor.0a520801070100b81b0279"
|
||||
|
||||
state = hass.states.get(f"{base_id}_humidity")
|
||||
assert state
|
||||
assert state.state == "27"
|
||||
assert state.attributes.get("unit_of_measurement") == UNIT_PERCENTAGE
|
||||
|
||||
state = hass.states.get(f"{base_id}_humidity_status")
|
||||
assert state
|
||||
assert state.state == "normal"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
state = hass.states.get(f"{base_id}_rssi_numeric")
|
||||
assert state
|
||||
assert state.state == "7"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
state = hass.states.get(f"{base_id}_temperature")
|
||||
assert state
|
||||
assert state.state == "17.9"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "0a520801070100b81b0279 Temperature",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
"Humidity status": "normal",
|
||||
"Temperature": 17.9,
|
||||
"Rssi numeric": 7,
|
||||
"Humidity": 27,
|
||||
"Battery numeric": 9,
|
||||
"Humidity status numeric": 2,
|
||||
}.items()
|
||||
)
|
||||
assert state.attributes.get("unit_of_measurement") == TEMP_CELSIUS
|
||||
|
||||
assert len(hass.states.async_all()) == 2
|
||||
state = hass.states.get(f"{base_id}_battery_numeric")
|
||||
assert state
|
||||
assert state.state == "9"
|
||||
assert state.attributes.get("unit_of_measurement") == ""
|
||||
|
||||
assert len(hass.states.async_all()) == 10
|
||||
|
||||
|
||||
async def test_discover_sensor_noautoadd(hass, rfxtrx):
|
||||
|
@ -215,35 +275,14 @@ async def test_update_of_sensors(hass, rfxtrx):
|
|||
state = hass.states.get("sensor.test_temperature")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "Test Temperature",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
}.items()
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.bath_temperature")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "Bath Temperature",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
}.items()
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.bath_humidity")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "Bath Humidity",
|
||||
"unit_of_measurement": UNIT_PERCENTAGE,
|
||||
}.items()
|
||||
)
|
||||
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
|
@ -253,52 +292,13 @@ async def test_update_of_sensors(hass, rfxtrx):
|
|||
state = hass.states.get("sensor.test_temperature")
|
||||
assert state
|
||||
assert state.state == "13.3"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "Test Temperature",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
"Battery numeric": 9,
|
||||
"Temperature": 13.3,
|
||||
"Humidity": 34,
|
||||
"Humidity status": "normal",
|
||||
"Humidity status numeric": 2,
|
||||
"Rssi numeric": 6,
|
||||
}.items()
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.bath_temperature")
|
||||
assert state
|
||||
assert state.state == "51.1"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "Bath Temperature",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
"Battery numeric": 9,
|
||||
"Temperature": 51.1,
|
||||
"Humidity": 15,
|
||||
"Humidity status": "normal",
|
||||
"Humidity status numeric": 2,
|
||||
"Rssi numeric": 6,
|
||||
}.items()
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.bath_humidity")
|
||||
assert state
|
||||
assert state.state == "15"
|
||||
assert (
|
||||
state.attributes.items()
|
||||
>= {
|
||||
"friendly_name": "Bath Humidity",
|
||||
"unit_of_measurement": UNIT_PERCENTAGE,
|
||||
"Battery numeric": 9,
|
||||
"Temperature": 51.1,
|
||||
"Humidity": 15,
|
||||
"Humidity status": "normal",
|
||||
"Humidity status numeric": 2,
|
||||
"Rssi numeric": 6,
|
||||
}.items()
|
||||
)
|
||||
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue