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:
Joakim Plate 2020-07-10 14:52:07 +02:00 committed by GitHub
parent b45a952d61
commit 67038c6ba8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 365 additions and 334 deletions

View file

@ -8,10 +8,8 @@ import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_NAME,
ATTR_STATE, ATTR_STATE,
CONF_DEVICE, CONF_DEVICE,
CONF_DEVICES,
CONF_HOST, CONF_HOST,
CONF_PORT, CONF_PORT,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
@ -25,6 +23,8 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify from homeassistant.util import slugify
from .const import DEVICE_PACKET_TYPE_LIGHTING4
DOMAIN = "rfxtrx" DOMAIN = "rfxtrx"
DEFAULT_SIGNAL_REPETITIONS = 1 DEFAULT_SIGNAL_REPETITIONS = 1
@ -80,7 +80,6 @@ DATA_TYPES = OrderedDict(
] ]
) )
RFX_DEVICES = {}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_RFXOBJECT = "rfxobject" DATA_RFXOBJECT = "rfxobject"
@ -207,31 +206,15 @@ def get_pt2262_cmd(device_id, data_bits):
return hex(data[-1] & mask) 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.""" """Look for the device which id matches the given device_id parameter."""
for device in RFX_DEVICES.values(): for dev_id in device_ids:
if ( if len(dev_id) == len(device_id):
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):
size = None size = None
for i, (char1, char2) in enumerate(zip(dev_id, device_id)): for i, (char1, char2) in enumerate(zip(dev_id, device_id)):
if char1 != char2: if char1 != char2:
break break
size = i size = i
if size is not None: if size is not None:
size = len(dev_id) - size - 1 size = len(dev_id) - size - 1
_LOGGER.info( _LOGGER.info(
@ -246,60 +229,19 @@ def find_possible_pt2262_device(device_id):
dev_id[-size:], dev_id[-size:],
device_id[-size:], device_id[-size:],
) )
return device return dev_id
return None return None
def get_devices_from_config(config, device): def get_device_id(device, data_bits=None):
"""Read rfxtrx configuration.""" """Calculate a device id for device."""
signal_repetitions = config[CONF_SIGNAL_REPETITIONS] 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 = [] return (f"{device.packettype:x}", f"{device.subtype:x}", id_string)
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
def fire_command_event(hass, entity_id, command): def fire_command_event(hass, entity_id, command):
@ -330,7 +272,8 @@ class RfxtrxDevice(Entity):
self._device = device self._device = device
self._state = datas[ATTR_STATE] self._state = datas[ATTR_STATE]
self._should_fire_event = datas[ATTR_FIRE_EVENT] 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: if event:
self._apply_event(event) self._apply_event(event)

View file

@ -13,31 +13,29 @@ from homeassistant.const import (
CONF_COMMAND_OFF, CONF_COMMAND_OFF,
CONF_COMMAND_ON, CONF_COMMAND_ON,
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
CONF_DEVICES,
CONF_NAME, CONF_NAME,
) )
from homeassistant.helpers import config_validation as cv, event as evt from homeassistant.helpers import config_validation as cv, event as evt
from homeassistant.util import slugify
from . import ( from . import (
ATTR_NAME,
CONF_AUTOMATIC_ADD, CONF_AUTOMATIC_ADD,
CONF_DATA_BITS, CONF_DATA_BITS,
CONF_DEVICES,
CONF_FIRE_EVENT, CONF_FIRE_EVENT,
CONF_OFF_DELAY, CONF_OFF_DELAY,
RFX_DEVICES,
SIGNAL_EVENT, SIGNAL_EVENT,
find_possible_pt2262_device, find_possible_pt2262_device,
fire_command_event, fire_command_event,
get_device_id,
get_pt2262_cmd, get_pt2262_cmd,
get_pt2262_device,
get_pt2262_deviceid, get_pt2262_deviceid,
get_rfx_object, 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__) _LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICES, default={}): { 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): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Binary Sensor platform to RFXtrx.""" """Set up the Binary Sensor platform to RFXtrx."""
sensors = [] sensors = []
device_ids = set()
device_bits = {}
pt2262_devices = []
for packet_id, entity in config[CONF_DEVICES].items(): for packet_id, entity in config[CONF_DEVICES].items():
event = get_rfx_object(packet_id) event = get_rfx_object(packet_id)
device_id = slugify(event.device.id_string.lower()) if event is None:
_LOGGER.error("Invalid device: %s", packet_id)
if device_id in RFX_DEVICES:
continue continue
if entity.get(CONF_DATA_BITS) is not None: device_id = get_device_id(event.device, data_bits=entity.get(CONF_DATA_BITS))
_LOGGER.debug( if device_id in device_ids:
"Masked device id: %s", continue
get_pt2262_deviceid(device_id, entity.get(CONF_DATA_BITS)), device_ids.add(device_id)
)
_LOGGER.debug( if event.device.packettype == DEVICE_PACKET_TYPE_LIGHTING4:
"Add %s rfxtrx.binary_sensor (class %s)", find_possible_pt2262_device(pt2262_devices, event.device.id_string)
entity[ATTR_NAME], pt2262_devices.append(event.device.id_string)
entity.get(CONF_DEVICE_CLASS),
)
device = RfxtrxBinarySensor( device = RfxtrxBinarySensor(
event.device, event.device,
@ -95,7 +105,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
entity.get(CONF_COMMAND_OFF), entity.get(CONF_COMMAND_OFF),
) )
sensors.append(device) sensors.append(device)
RFX_DEVICES[device_id] = device
add_entities(sensors) add_entities(sensors)
@ -104,35 +113,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if not isinstance(event, rfxtrxmod.ControlEvent): if not isinstance(event, rfxtrxmod.ControlEvent):
return 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: _LOGGER.info(
# Add the entity if not exists and automatic_add is True "Added binary sensor (Device ID: %s Class: %s Sub: %s)",
if not config[CONF_AUTOMATIC_ADD]: event.device.id_string.lower(),
return event.device.__class__.__name__,
event.device.subtype,
if event.device.packettype == 0x13: )
poss_dev = find_possible_pt2262_device(device_id) pkt_id = "".join(f"{x:02x}" for x in event.data)
if poss_dev is not None: sensor = RfxtrxBinarySensor(
poss_id = slugify(poss_dev.event.device.id_string.lower()) event.device, pkt_id, data_bits=data_bits, event=event
_LOGGER.debug("Found possible matching device ID: %s", poss_id) )
add_entities([sensor])
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,
)
# Subscribe to main RFXtrx events # 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): class RfxtrxBinarySensor(BinarySensorEntity):
@ -158,22 +160,13 @@ class RfxtrxBinarySensor(BinarySensorEntity):
self._device_class = device_class self._device_class = device_class
self._off_delay = off_delay self._off_delay = off_delay
self._state = False self._state = False
self.is_lighting4 = device.packettype == 0x13
self.delay_listener = None self.delay_listener = None
self._data_bits = data_bits self._data_bits = data_bits
self._cmd_on = cmd_on self._cmd_on = cmd_on
self._cmd_off = cmd_off self._cmd_off = cmd_off
if data_bits is not None: self._device_id = get_device_id(device, data_bits=data_bits)
self._masked_id = get_pt2262_deviceid(device.id_string.lower(), data_bits) self._unique_id = "_".join(x for x in self._device_id)
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}"
)
if event: if event:
self._apply_event(event) self._apply_event(event)
@ -193,11 +186,6 @@ class RfxtrxBinarySensor(BinarySensorEntity):
"""Return the device name.""" """Return the device name."""
return self._name return self._name
@property
def masked_id(self):
"""Return the masked device id (isolated address bits)."""
return self._masked_id
@property @property
def data_bits(self): def data_bits(self):
"""Return the number of data bits.""" """Return the number of data bits."""
@ -263,20 +251,15 @@ class RfxtrxBinarySensor(BinarySensorEntity):
def _apply_event(self, event): def _apply_event(self, event):
"""Apply command from rfxtrx.""" """Apply command from rfxtrx."""
if self.is_lighting4: if event.device.packettype == DEVICE_PACKET_TYPE_LIGHTING4:
self._apply_event_lighting4(event) self._apply_event_lighting4(event)
else: else:
self._apply_event_standard(event) self._apply_event_standard(event)
def _handle_event(self, event): def _handle_event(self, event):
"""Check if event applies to me and update.""" """Check if event applies to me and update."""
if self._masked_id: if get_device_id(event.device, data_bits=self._data_bits) != self._device_id:
masked_id = get_pt2262_deviceid(event.device.id_string, self._data_bits) return
if masked_id != self._masked_id:
return
else:
if event.device.id_string != self._device.id_string:
return
_LOGGER.debug( _LOGGER.debug(
"Binary sensor update (Device ID: %s Class: %s Sub: %s)", "Binary sensor update (Device ID: %s Class: %s Sub: %s)",

View file

@ -14,3 +14,4 @@ COMMAND_OFF_LIST = [
"Down", "Down",
"Close (inline relay)", "Close (inline relay)",
] ]
DEVICE_PACKET_TYPE_LIGHTING4 = 0x13

View file

@ -1,23 +1,25 @@
"""Support for RFXtrx covers.""" """Support for RFXtrx covers."""
import logging
import RFXtrx as rfxtrxmod import RFXtrx as rfxtrxmod
import voluptuous as vol import voluptuous as vol
from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity 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 import config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from . import ( from . import (
ATTR_FIRE_EVENT,
CONF_AUTOMATIC_ADD, CONF_AUTOMATIC_ADD,
CONF_DEVICES,
CONF_FIRE_EVENT, CONF_FIRE_EVENT,
CONF_SIGNAL_REPETITIONS, CONF_SIGNAL_REPETITIONS,
DEFAULT_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS,
SIGNAL_EVENT, SIGNAL_EVENT,
RfxtrxDevice, RfxtrxDevice,
fire_command_event, fire_command_event,
get_devices_from_config, get_device_id,
get_new_device, get_rfx_object,
) )
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST 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): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the RFXtrx cover.""" """Set up the RFXtrx cover."""
covers = get_devices_from_config(config, RfxtrxCover) device_ids = set()
add_entities(covers)
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): def cover_update(event):
"""Handle cover updates from the RFXtrx gateway.""" """Handle cover updates from the RFXtrx gateway."""
@ -53,12 +76,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
): ):
return return
new_device = get_new_device(event, config, RfxtrxCover) device_id = get_device_id(event.device)
if new_device: if device_id in device_ids:
add_entities([new_device]) 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 # 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): class RfxtrxCover(RfxtrxDevice, CoverEntity, RestoreEntity):

View file

@ -10,21 +10,21 @@ from homeassistant.components.light import (
SUPPORT_BRIGHTNESS, SUPPORT_BRIGHTNESS,
LightEntity, 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 import config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from . import ( from . import (
ATTR_FIRE_EVENT,
CONF_AUTOMATIC_ADD, CONF_AUTOMATIC_ADD,
CONF_DEVICES,
CONF_FIRE_EVENT, CONF_FIRE_EVENT,
CONF_SIGNAL_REPETITIONS, CONF_SIGNAL_REPETITIONS,
DEFAULT_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS,
SIGNAL_EVENT, SIGNAL_EVENT,
RfxtrxDevice, RfxtrxDevice,
fire_command_event, fire_command_event,
get_devices_from_config, get_device_id,
get_new_device, get_rfx_object,
) )
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST 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): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the RFXtrx platform.""" """Set up the RFXtrx platform."""
lights = get_devices_from_config(config, RfxtrxLight) device_ids = set()
add_entities(lights)
# 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): def light_update(event):
"""Handle light updates from the RFXtrx gateway.""" """Handle light updates from the RFXtrx gateway."""
@ -63,12 +84,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
): ):
return return
new_device = get_new_device(event, config, RfxtrxLight) device_id = get_device_id(event.device)
if new_device: if device_id in device_ids:
add_entities([new_device]) 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 # 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): class RfxtrxLight(RfxtrxDevice, LightEntity, RestoreEntity):

View file

@ -5,21 +5,17 @@ from RFXtrx import SensorEvent
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA 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 import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
from . import ( from . import (
ATTR_DATA_TYPE,
ATTR_FIRE_EVENT,
CONF_AUTOMATIC_ADD, CONF_AUTOMATIC_ADD,
CONF_DATA_TYPE, CONF_DATA_TYPE,
CONF_DEVICES,
CONF_FIRE_EVENT, CONF_FIRE_EVENT,
DATA_TYPES, DATA_TYPES,
RFX_DEVICES,
SIGNAL_EVENT, SIGNAL_EVENT,
get_device_id,
get_rfx_object, get_rfx_object,
) )
@ -46,64 +42,63 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the RFXtrx platform.""" """Set up the RFXtrx platform."""
sensors = [] data_ids = set()
entities = []
for packet_id, entity_info in config[CONF_DEVICES].items(): for packet_id, entity_info in config[CONF_DEVICES].items():
event = get_rfx_object(packet_id) event = get_rfx_object(packet_id)
device_id = "sensor_{}".format(slugify(event.device.id_string.lower())) if event is None:
if device_id in RFX_DEVICES: _LOGGER.error("Invalid device: %s", packet_id)
continue continue
_LOGGER.info("Add %s rfxtrx.sensor", entity_info[ATTR_NAME])
sub_sensors = {} if entity_info[CONF_DATA_TYPE]:
data_types = entity_info[ATTR_DATA_TYPE] data_types = entity_info[CONF_DATA_TYPE]
if not data_types: else:
data_types = [""] data_types = list(set(event.values) & set(DATA_TYPES))
for data_type in DATA_TYPES:
if data_type in event.values: device_id = get_device_id(event.device)
data_types = [data_type] for data_type in data_types:
break data_id = (*device_id, data_type)
for _data_type in data_types: if data_id in data_ids:
new_sensor = RfxtrxSensor( continue
data_ids.add(data_id)
entity = RfxtrxSensor(
event.device, event.device,
entity_info[ATTR_NAME], entity_info[CONF_NAME],
_data_type, data_type,
entity_info[ATTR_FIRE_EVENT], entity_info[CONF_FIRE_EVENT],
) )
sensors.append(new_sensor) entities.append(entity)
sub_sensors[_data_type] = new_sensor
RFX_DEVICES[device_id] = sub_sensors add_entities(entities)
add_entities(sensors)
def sensor_update(event): def sensor_update(event):
"""Handle sensor updates from the RFXtrx gateway.""" """Handle sensor updates from the RFXtrx gateway."""
if not isinstance(event, SensorEvent): if not isinstance(event, SensorEvent):
return 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) 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 = "" _LOGGER.debug(
for _data_type in DATA_TYPES: "Added sensor (Device ID: %s Class: %s Sub: %s)",
if _data_type in event.values: event.device.id_string.lower(),
data_type = _data_type event.device.__class__.__name__,
break event.device.subtype,
new_sensor = RfxtrxSensor(event.device, pkt_id, data_type, event=event) )
sub_sensors = {}
sub_sensors[new_sensor.data_type] = new_sensor entity = RfxtrxSensor(event.device, pkt_id, data_type, event=event)
RFX_DEVICES[device_id] = sub_sensors add_entities([entity])
add_entities([new_sensor])
# Subscribe to main RFXtrx events # 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): class RfxtrxSensor(Entity):
@ -117,9 +112,8 @@ class RfxtrxSensor(Entity):
self.should_fire_event = should_fire_event self.should_fire_event = should_fire_event
self.data_type = data_type self.data_type = data_type
self._unit_of_measurement = DATA_TYPES.get(data_type, "") self._unit_of_measurement = DATA_TYPES.get(data_type, "")
self._unique_id = ( self._device_id = get_device_id(device)
f"{device.packettype:x}_{device.subtype:x}_{device.id_string}_{data_type}" self._unique_id = "_".join(x for x in (*self._device_id, data_type))
)
if event: if event:
self._apply_event(event) self._apply_event(event)

View file

@ -5,21 +5,21 @@ import RFXtrx as rfxtrxmod
import voluptuous as vol import voluptuous as vol
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity 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 import config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from . import ( from . import (
ATTR_FIRE_EVENT,
CONF_AUTOMATIC_ADD, CONF_AUTOMATIC_ADD,
CONF_DEVICES,
CONF_FIRE_EVENT, CONF_FIRE_EVENT,
CONF_SIGNAL_REPETITIONS, CONF_SIGNAL_REPETITIONS,
DEFAULT_SIGNAL_REPETITIONS, DEFAULT_SIGNAL_REPETITIONS,
SIGNAL_EVENT, SIGNAL_EVENT,
RfxtrxDevice, RfxtrxDevice,
fire_command_event, fire_command_event,
get_devices_from_config, get_device_id,
get_new_device, get_rfx_object,
) )
from .const import COMMAND_OFF_LIST, COMMAND_ON_LIST 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): def setup_platform(hass, config, add_entities_callback, discovery_info=None):
"""Set up the RFXtrx platform.""" """Set up the RFXtrx platform."""
device_ids = set()
# Add switch from config file # Add switch from config file
switches = get_devices_from_config(config, RfxtrxSwitch) entities = []
add_entities_callback(switches) 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): def switch_update(event):
"""Handle sensor updates from the RFXtrx gateway.""" """Handle sensor updates from the RFXtrx gateway."""
@ -58,12 +77,28 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None):
): ):
return return
new_device = get_new_device(event, config, RfxtrxSwitch) device_id = get_device_id(event.device)
if new_device: if device_id in device_ids:
add_entities_callback([new_device]) 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 # 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): class RfxtrxSwitch(RfxtrxDevice, SwitchEntity, RestoreEntity):

View file

@ -59,8 +59,6 @@ async def rfxtrx_cleanup():
): ):
yield yield
rfxtrx_core.RFX_DEVICES.clear()
@pytest.fixture(name="rfxtrx") @pytest.fixture(name="rfxtrx")
async def rfxtrx_fixture(hass): async def rfxtrx_fixture(hass):

View file

@ -141,7 +141,7 @@ async def test_fire_event(hass):
assert state assert state
assert state.state == "on" assert state.state == "on"
assert 1 == len(calls) assert len(calls) == 1
assert calls[0].data == {"entity_id": "switch.test", "state": "on"} 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) hass.bus.async_listen("signal_received", record_event)
await _signal_event(hass, "0a520802060101ff0f0269") await _signal_event(hass, "0a520802060101ff0f0269")
assert 1 == len(calls) assert len(calls) == 5
assert calls[0].data == {"entity_id": "sensor.test_temperature"} assert any(call.data == {"entity_id": "sensor.test_temperature"} for call in calls)

View file

@ -57,7 +57,7 @@ async def test_default_config(hass, rfxtrx):
) )
await hass.async_block_till_done() 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): async def test_one_light(hass, rfxtrx):

View file

@ -55,12 +55,39 @@ async def test_one_sensor_no_datatype(hass, rfxtrx):
) )
await hass.async_block_till_done() 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
assert state.state == "unknown" 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 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): async def test_several_sensors(hass, rfxtrx):
"""Test with 3 sensors.""" """Test with 3 sensors."""
@ -113,61 +140,94 @@ async def test_discover_sensor(hass, rfxtrx):
) )
await hass.async_block_till_done() await hass.async_block_till_done()
# 1
await _signal_event(hass, "0a520801070100b81b0279") 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
assert state.state == "18.4" assert state.state == "18.4"
assert ( assert state.attributes.get("unit_of_measurement") == TEMP_CELSIUS
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()
)
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") 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
assert state.state == "14.9" assert state.state == "14.9"
assert ( assert state.attributes.get("unit_of_measurement") == TEMP_CELSIUS
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()
)
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") 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
assert state.state == "17.9" assert state.state == "17.9"
assert ( assert state.attributes.get("unit_of_measurement") == TEMP_CELSIUS
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 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): 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") state = hass.states.get("sensor.test_temperature")
assert state assert state
assert state.state == "unknown" 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") state = hass.states.get("sensor.bath_temperature")
assert state assert state
assert state.state == "unknown" 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") state = hass.states.get("sensor.bath_humidity")
assert state assert state
assert state.state == "unknown" 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 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") state = hass.states.get("sensor.test_temperature")
assert state assert state
assert state.state == "13.3" 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") state = hass.states.get("sensor.bath_temperature")
assert state assert state
assert state.state == "51.1" 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") state = hass.states.get("sensor.bath_humidity")
assert state assert state
assert state.state == "15" 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 assert len(hass.states.async_all()) == 3