Device registry store config entry (#16152)
* Allow device registry to optionally store config entries * Connections and identifiers are now sets with tupels * Make config entries mandatory * Fix duplicate keys in test * Rename device to device_info * Entity platform should only create device entries if config_entry_id exists * Fix Soundtouch tests * Revert soundtouch to use self.device * Fix baloobs comments * Correct type in test
This commit is contained in:
parent
24a8d60566
commit
97173f495c
11 changed files with 142 additions and 61 deletions
|
@ -116,15 +116,15 @@ class DeconzBinarySensor(BinarySensorDevice):
|
|||
return attr
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._sensor.uniqueid is None or
|
||||
self._sensor.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._sensor.uniqueid.split('-', 1)[0]
|
||||
return {
|
||||
'connection': [[CONNECTION_ZIGBEE, serial]],
|
||||
'identifiers': [[DECONZ_DOMAIN, serial]],
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._sensor.manufacturer,
|
||||
'model': self._sensor.modelid,
|
||||
'name': self._sensor.name,
|
||||
|
|
|
@ -123,8 +123,9 @@ async def async_setup_entry(hass, config_entry):
|
|||
device_registry = await \
|
||||
hass.helpers.device_registry.async_get_registry()
|
||||
device_registry.async_get_or_create(
|
||||
connection=[[CONNECTION_NETWORK_MAC, deconz.config.mac]],
|
||||
identifiers=[[DOMAIN, deconz.config.bridgeid]],
|
||||
config_entry=config_entry.entry_id,
|
||||
connections={(CONNECTION_NETWORK_MAC, deconz.config.mac)},
|
||||
identifiers={(DOMAIN, deconz.config.bridgeid)},
|
||||
manufacturer='Dresden Elektronik', model=deconz.config.modelid,
|
||||
name=deconz.config.name, sw_version=deconz.config.swversion)
|
||||
|
||||
|
|
|
@ -202,15 +202,15 @@ class DeconzLight(Light):
|
|||
return attributes
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._light.uniqueid is None or
|
||||
self._light.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._light.uniqueid.split('-', 1)[0]
|
||||
return {
|
||||
'connection': [[CONNECTION_ZIGBEE, serial]],
|
||||
'identifiers': [[DECONZ_DOMAIN, serial]],
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._light.manufacturer,
|
||||
'model': self._light.modelid,
|
||||
'name': self._light.name,
|
||||
|
|
|
@ -87,7 +87,7 @@ class RokuDevice(MediaPlayerDevice):
|
|||
self.ip_address = host
|
||||
self.channels = []
|
||||
self.current_app = None
|
||||
self.device_info = {}
|
||||
self._device_info = {}
|
||||
|
||||
self.update()
|
||||
|
||||
|
@ -96,7 +96,7 @@ class RokuDevice(MediaPlayerDevice):
|
|||
import requests.exceptions
|
||||
|
||||
try:
|
||||
self.device_info = self.roku.device_info
|
||||
self._device_info = self.roku.device_info
|
||||
self.ip_address = self.roku.host
|
||||
self.channels = self.get_source_list()
|
||||
|
||||
|
@ -121,9 +121,9 @@ class RokuDevice(MediaPlayerDevice):
|
|||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
if self.device_info.userdevicename:
|
||||
return self.device_info.userdevicename
|
||||
return "Roku {}".format(self.device_info.sernum)
|
||||
if self._device_info.userdevicename:
|
||||
return self._device_info.userdevicename
|
||||
return "Roku {}".format(self._device_info.sernum)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -149,7 +149,7 @@ class RokuDevice(MediaPlayerDevice):
|
|||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||
return self.device_info.sernum
|
||||
return self._device_info.sernum
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
|
|
|
@ -166,6 +166,11 @@ class SoundTouchDevice(MediaPlayerDevice):
|
|||
"""Return specific soundtouch configuration."""
|
||||
return self._config
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
"""Return Soundtouch device."""
|
||||
return self._device
|
||||
|
||||
def update(self):
|
||||
"""Retrieve the latest data."""
|
||||
self._status = self._device.status()
|
||||
|
@ -318,8 +323,8 @@ class SoundTouchDevice(MediaPlayerDevice):
|
|||
_LOGGER.warning("Unable to create zone without slaves")
|
||||
else:
|
||||
_LOGGER.info("Creating zone with master %s",
|
||||
self._device.config.name)
|
||||
self._device.create_zone([slave.device for slave in slaves])
|
||||
self.device.config.name)
|
||||
self.device.create_zone([slave.device for slave in slaves])
|
||||
|
||||
def remove_zone_slave(self, slaves):
|
||||
"""
|
||||
|
@ -336,8 +341,8 @@ class SoundTouchDevice(MediaPlayerDevice):
|
|||
_LOGGER.warning("Unable to find slaves to remove")
|
||||
else:
|
||||
_LOGGER.info("Removing slaves from zone with master %s",
|
||||
self._device.config.name)
|
||||
self._device.remove_zone_slave([slave.device for slave in slaves])
|
||||
self.device.config.name)
|
||||
self.device.remove_zone_slave([slave.device for slave in slaves])
|
||||
|
||||
def add_zone_slave(self, slaves):
|
||||
"""
|
||||
|
@ -352,5 +357,5 @@ class SoundTouchDevice(MediaPlayerDevice):
|
|||
_LOGGER.warning("Unable to find slaves to add")
|
||||
else:
|
||||
_LOGGER.info("Adding slaves to zone with master %s",
|
||||
self._device.config.name)
|
||||
self._device.add_zone_slave([slave.device for slave in slaves])
|
||||
self.device.config.name)
|
||||
self.device.add_zone_slave([slave.device for slave in slaves])
|
||||
|
|
|
@ -136,15 +136,15 @@ class DeconzSensor(Entity):
|
|||
return attr
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._sensor.uniqueid is None or
|
||||
self._sensor.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._sensor.uniqueid.split('-', 1)[0]
|
||||
return {
|
||||
'connection': [[CONNECTION_ZIGBEE, serial]],
|
||||
'identifiers': [[DECONZ_DOMAIN, serial]],
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._sensor.manufacturer,
|
||||
'model': self._sensor.modelid,
|
||||
'name': self._sensor.name,
|
||||
|
@ -211,15 +211,15 @@ class DeconzBattery(Entity):
|
|||
return attr
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._device.uniqueid is None or
|
||||
self._device.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._device.uniqueid.split('-', 1)[0]
|
||||
return {
|
||||
'connection': [[CONNECTION_ZIGBEE, serial]],
|
||||
'identifiers': [[DECONZ_DOMAIN, serial]],
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._device.manufacturer,
|
||||
'model': self._device.modelid,
|
||||
'name': self._device.name,
|
||||
|
|
|
@ -81,15 +81,15 @@ class DeconzSwitch(SwitchDevice):
|
|||
return False
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._switch.uniqueid is None or
|
||||
self._switch.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._switch.uniqueid.split('-', 1)[0]
|
||||
return {
|
||||
'connection': [[CONNECTION_ZIGBEE, serial]],
|
||||
'identifiers': [[DECONZ_DOMAIN, serial]],
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._switch.manufacturer,
|
||||
'model': self._switch.modelid,
|
||||
'name': self._switch.name,
|
||||
|
|
|
@ -23,8 +23,9 @@ CONNECTION_ZIGBEE = 'zigbee'
|
|||
class DeviceEntry:
|
||||
"""Device Registry Entry."""
|
||||
|
||||
connection = attr.ib(type=list)
|
||||
identifiers = attr.ib(type=list)
|
||||
config_entries = attr.ib(type=set, converter=set)
|
||||
connections = attr.ib(type=set, converter=set)
|
||||
identifiers = attr.ib(type=set, converter=set)
|
||||
manufacturer = attr.ib(type=str)
|
||||
model = attr.ib(type=str)
|
||||
name = attr.ib(type=str, default=None)
|
||||
|
@ -46,29 +47,36 @@ class DeviceRegistry:
|
|||
"""Check if device is registered."""
|
||||
for device in self.devices:
|
||||
if any(iden in device.identifiers for iden in identifiers) or \
|
||||
any(conn in device.connection for conn in connections):
|
||||
any(conn in device.connections for conn in connections):
|
||||
return device
|
||||
return None
|
||||
|
||||
@callback
|
||||
def async_get_or_create(self, *, connection, identifiers, manufacturer,
|
||||
model, name=None, sw_version=None):
|
||||
def async_get_or_create(self, *, config_entry, connections, identifiers,
|
||||
manufacturer, model, name=None, sw_version=None):
|
||||
"""Get device. Create if it doesn't exist."""
|
||||
device = self.async_get_device(identifiers, connection)
|
||||
if not identifiers and not connections:
|
||||
return None
|
||||
|
||||
device = self.async_get_device(identifiers, connections)
|
||||
|
||||
if device is not None:
|
||||
if config_entry not in device.config_entries:
|
||||
device.config_entries.add(config_entry)
|
||||
self.async_schedule_save()
|
||||
return device
|
||||
|
||||
device = DeviceEntry(
|
||||
connection=connection,
|
||||
config_entries=[config_entry],
|
||||
connections=connections,
|
||||
identifiers=identifiers,
|
||||
manufacturer=manufacturer,
|
||||
model=model,
|
||||
name=name,
|
||||
sw_version=sw_version
|
||||
)
|
||||
|
||||
self.devices.append(device)
|
||||
|
||||
self.async_schedule_save()
|
||||
|
||||
return device
|
||||
|
@ -81,7 +89,16 @@ class DeviceRegistry:
|
|||
self.devices = []
|
||||
return
|
||||
|
||||
self.devices = [DeviceEntry(**device) for device in devices['devices']]
|
||||
self.devices = [DeviceEntry(
|
||||
config_entries=device['config_entries'],
|
||||
connections={tuple(conn) for conn in device['connections']},
|
||||
identifiers={tuple(iden) for iden in device['identifiers']},
|
||||
manufacturer=device['manufacturer'],
|
||||
model=device['model'],
|
||||
name=device['name'],
|
||||
sw_version=device['sw_version'],
|
||||
id=device['id'],
|
||||
) for device in devices['devices']]
|
||||
|
||||
@callback
|
||||
def async_schedule_save(self):
|
||||
|
@ -95,13 +112,14 @@ class DeviceRegistry:
|
|||
|
||||
data['devices'] = [
|
||||
{
|
||||
'id': entry.id,
|
||||
'connection': entry.connection,
|
||||
'identifiers': entry.identifiers,
|
||||
'config_entries': list(entry.config_entries),
|
||||
'connections': list(entry.connections),
|
||||
'identifiers': list(entry.identifiers),
|
||||
'manufacturer': entry.manufacturer,
|
||||
'model': entry.model,
|
||||
'name': entry.name,
|
||||
'sw_version': entry.sw_version,
|
||||
'id': entry.id,
|
||||
} for entry in self.devices
|
||||
]
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ class Entity:
|
|||
return None
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
def device_info(self):
|
||||
"""Return device specific attributes.
|
||||
|
||||
Implemented by platform classes.
|
||||
|
|
|
@ -272,15 +272,16 @@ class EntityPlatform:
|
|||
else:
|
||||
config_entry_id = None
|
||||
|
||||
device = entity.device
|
||||
if device is not None:
|
||||
device_info = entity.device_info
|
||||
if config_entry_id is not None and device_info is not None:
|
||||
device = device_registry.async_get_or_create(
|
||||
connection=device['connection'],
|
||||
identifiers=device['identifiers'],
|
||||
manufacturer=device['manufacturer'],
|
||||
model=device['model'],
|
||||
name=device.get('name'),
|
||||
sw_version=device.get('sw_version'))
|
||||
config_entry=config_entry_id,
|
||||
connections=device_info.get('connections', []),
|
||||
identifiers=device_info.get('identifiers', []),
|
||||
manufacturer=device_info.get('manufacturer'),
|
||||
model=device_info.get('model'),
|
||||
name=device_info.get('name'),
|
||||
sw_version=device_info.get('sw_version'))
|
||||
device_id = device.id
|
||||
else:
|
||||
device_id = None
|
||||
|
|
|
@ -26,22 +26,73 @@ def registry(hass):
|
|||
async def test_get_or_create_returns_same_entry(registry):
|
||||
"""Make sure we do not duplicate entries."""
|
||||
entry = registry.async_get_or_create(
|
||||
connection=[['ethernet', '12:34:56:78:90:AB:CD:EF']],
|
||||
identifiers=[['bridgeid', '0123']],
|
||||
config_entry='1234',
|
||||
connections={('ethernet', '12:34:56:78:90:AB:CD:EF')},
|
||||
identifiers={('bridgeid', '0123')},
|
||||
manufacturer='manufacturer', model='model')
|
||||
entry2 = registry.async_get_or_create(
|
||||
connection=[['ethernet', '11:22:33:44:55:66:77:88']],
|
||||
identifiers=[['bridgeid', '0123']],
|
||||
config_entry='1234',
|
||||
connections={('ethernet', '11:22:33:44:55:66:77:88')},
|
||||
identifiers={('bridgeid', '0123')},
|
||||
manufacturer='manufacturer', model='model')
|
||||
entry3 = registry.async_get_or_create(
|
||||
connection=[['ethernet', '12:34:56:78:90:AB:CD:EF']],
|
||||
identifiers=[['bridgeid', '1234']],
|
||||
config_entry='1234',
|
||||
connections={('ethernet', '12:34:56:78:90:AB:CD:EF')},
|
||||
identifiers={('bridgeid', '1234')},
|
||||
manufacturer='manufacturer', model='model')
|
||||
|
||||
assert len(registry.devices) == 1
|
||||
assert entry is entry2
|
||||
assert entry is entry3
|
||||
assert entry.identifiers == [['bridgeid', '0123']]
|
||||
assert entry.identifiers == {('bridgeid', '0123')}
|
||||
|
||||
|
||||
async def test_requirement_for_identifier_or_connection(registry):
|
||||
"""Make sure we do require some descriptor of device."""
|
||||
entry = registry.async_get_or_create(
|
||||
config_entry='1234',
|
||||
connections={('ethernet', '12:34:56:78:90:AB:CD:EF')},
|
||||
identifiers=set(),
|
||||
manufacturer='manufacturer', model='model')
|
||||
entry2 = registry.async_get_or_create(
|
||||
config_entry='1234',
|
||||
connections=set(),
|
||||
identifiers={('bridgeid', '0123')},
|
||||
manufacturer='manufacturer', model='model')
|
||||
entry3 = registry.async_get_or_create(
|
||||
config_entry='1234',
|
||||
connections=set(),
|
||||
identifiers=set(),
|
||||
manufacturer='manufacturer', model='model')
|
||||
|
||||
assert len(registry.devices) == 2
|
||||
assert entry
|
||||
assert entry2
|
||||
assert entry3 is None
|
||||
|
||||
|
||||
async def test_multiple_config_entries(registry):
|
||||
"""Make sure we do not get duplicate entries."""
|
||||
entry = registry.async_get_or_create(
|
||||
config_entry='123',
|
||||
connections={('ethernet', '12:34:56:78:90:AB:CD:EF')},
|
||||
identifiers={('bridgeid', '0123')},
|
||||
manufacturer='manufacturer', model='model')
|
||||
entry2 = registry.async_get_or_create(
|
||||
config_entry='456',
|
||||
connections={('ethernet', '12:34:56:78:90:AB:CD:EF')},
|
||||
identifiers={('bridgeid', '0123')},
|
||||
manufacturer='manufacturer', model='model')
|
||||
entry3 = registry.async_get_or_create(
|
||||
config_entry='123',
|
||||
connections={('ethernet', '12:34:56:78:90:AB:CD:EF')},
|
||||
identifiers={('bridgeid', '0123')},
|
||||
manufacturer='manufacturer', model='model')
|
||||
|
||||
assert len(registry.devices) == 1
|
||||
assert entry is entry2
|
||||
assert entry is entry3
|
||||
assert entry.config_entries == {'123', '456'}
|
||||
|
||||
|
||||
async def test_loading_from_storage(hass, hass_storage):
|
||||
|
@ -51,7 +102,10 @@ async def test_loading_from_storage(hass, hass_storage):
|
|||
'data': {
|
||||
'devices': [
|
||||
{
|
||||
'connection': [
|
||||
'config_entries': [
|
||||
'1234'
|
||||
],
|
||||
'connections': [
|
||||
[
|
||||
'Zigbee',
|
||||
'01.23.45.67.89'
|
||||
|
@ -67,7 +121,7 @@ async def test_loading_from_storage(hass, hass_storage):
|
|||
'manufacturer': 'manufacturer',
|
||||
'model': 'model',
|
||||
'name': 'name',
|
||||
'sw_version': 'version'
|
||||
'sw_version': 'version',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -76,7 +130,9 @@ async def test_loading_from_storage(hass, hass_storage):
|
|||
registry = await device_registry.async_get_registry(hass)
|
||||
|
||||
entry = registry.async_get_or_create(
|
||||
connection=[['Zigbee', '01.23.45.67.89']],
|
||||
identifiers=[['serial', '12:34:56:78:90:AB:CD:EF']],
|
||||
config_entry='1234',
|
||||
connections={('Zigbee', '01.23.45.67.89')},
|
||||
identifiers={('serial', '12:34:56:78:90:AB:CD:EF')},
|
||||
manufacturer='manufacturer', model='model')
|
||||
assert entry.id == 'abcdefghijklm'
|
||||
assert isinstance(entry.config_entries, set)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue