deCONZ - Support device registry (#16115)
Add support for device registry in deCONZ component
This commit is contained in:
parent
e8775ba2b4
commit
e91a1529e4
12 changed files with 162 additions and 36 deletions
|
@ -7,9 +7,10 @@ https://home-assistant.io/components/binary_sensor.deconz/
|
|||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.deconz.const import (
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ,
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN)
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
@ -113,3 +114,19 @@ class DeconzBinarySensor(BinarySensorDevice):
|
|||
if self._sensor.type in PRESENCE and self._sensor.dark is not None:
|
||||
attr[ATTR_DARK] = self._sensor.dark
|
||||
return attr
|
||||
|
||||
@property
|
||||
def device(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]],
|
||||
'manufacturer': self._sensor.manufacturer,
|
||||
'model': self._sensor.modelid,
|
||||
'name': self._sensor.name,
|
||||
'sw_version': self._sensor.swversion,
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ from homeassistant.const import (
|
|||
CONF_ID, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.core import EventOrigin, callback
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
from homeassistant.util import slugify
|
||||
|
@ -23,7 +24,7 @@ from .const import (
|
|||
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
|
||||
|
||||
REQUIREMENTS = ['pydeconz==43']
|
||||
REQUIREMENTS = ['pydeconz==44']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
|
@ -119,6 +120,14 @@ async def async_setup_entry(hass, config_entry):
|
|||
|
||||
deconz.start()
|
||||
|
||||
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]],
|
||||
manufacturer='Dresden Elektronik', model=deconz.config.modelid,
|
||||
name=deconz.config.name, sw_version=deconz.config.swversion)
|
||||
|
||||
async def async_configure(call):
|
||||
"""Set attribute of device in deCONZ.
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ CONFIG_FILE = 'deconz.conf'
|
|||
DATA_DECONZ_EVENT = 'deconz_events'
|
||||
DATA_DECONZ_ID = 'deconz_entities'
|
||||
DATA_DECONZ_UNSUB = 'deconz_dispatchers'
|
||||
DECONZ_DOMAIN = 'deconz'
|
||||
|
||||
CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'
|
||||
CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
|
||||
|
|
|
@ -6,13 +6,14 @@ https://home-assistant.io/components/light.deconz/
|
|||
"""
|
||||
from homeassistant.components.deconz.const import (
|
||||
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DATA_DECONZ,
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, SWITCH_TYPES)
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN, SWITCH_TYPES)
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR,
|
||||
ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT,
|
||||
SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT,
|
||||
SUPPORT_FLASH, SUPPORT_TRANSITION, Light)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
|
@ -199,3 +200,19 @@ class DeconzLight(Light):
|
|||
if self._light.type == 'LightGroup':
|
||||
attributes['all_on'] = self._light.all_on
|
||||
return attributes
|
||||
|
||||
@property
|
||||
def device(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]],
|
||||
'manufacturer': self._light.manufacturer,
|
||||
'model': self._light.modelid,
|
||||
'name': self._light.name,
|
||||
'sw_version': self._light.swversion,
|
||||
}
|
||||
|
|
|
@ -6,10 +6,11 @@ https://home-assistant.io/components/sensor.deconz/
|
|||
"""
|
||||
from homeassistant.components.deconz.const import (
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ,
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
|
||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN)
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, DEVICE_CLASS_BATTERY)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import slugify
|
||||
|
@ -134,6 +135,22 @@ class DeconzSensor(Entity):
|
|||
attr[ATTR_DAYLIGHT] = self._sensor.daylight
|
||||
return attr
|
||||
|
||||
@property
|
||||
def device(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]],
|
||||
'manufacturer': self._sensor.manufacturer,
|
||||
'model': self._sensor.modelid,
|
||||
'name': self._sensor.name,
|
||||
'sw_version': self._sensor.swversion,
|
||||
}
|
||||
|
||||
|
||||
class DeconzBattery(Entity):
|
||||
"""Battery class for when a device is only represented as an event."""
|
||||
|
@ -192,3 +209,19 @@ class DeconzBattery(Entity):
|
|||
ATTR_EVENT_ID: slugify(self._device.name),
|
||||
}
|
||||
return attr
|
||||
|
||||
@property
|
||||
def device(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]],
|
||||
'manufacturer': self._device.manufacturer,
|
||||
'model': self._device.modelid,
|
||||
'name': self._device.name,
|
||||
'sw_version': self._device.swversion,
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ https://home-assistant.io/components/switch.deconz/
|
|||
"""
|
||||
from homeassistant.components.deconz.const import (
|
||||
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB,
|
||||
POWER_PLUGS, SIRENS)
|
||||
DECONZ_DOMAIN, POWER_PLUGS, SIRENS)
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
@ -79,6 +80,22 @@ class DeconzSwitch(SwitchDevice):
|
|||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def device(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]],
|
||||
'manufacturer': self._switch.manufacturer,
|
||||
'model': self._switch.modelid,
|
||||
'name': self._switch.name,
|
||||
'sw_version': self._switch.swversion,
|
||||
}
|
||||
|
||||
|
||||
class DeconzPowerPlug(DeconzSwitch):
|
||||
"""Representation of power plugs from deCONZ."""
|
||||
|
|
|
@ -15,15 +15,18 @@ STORAGE_KEY = 'core.device_registry'
|
|||
STORAGE_VERSION = 1
|
||||
SAVE_DELAY = 10
|
||||
|
||||
CONNECTION_NETWORK_MAC = 'mac'
|
||||
CONNECTION_ZIGBEE = 'zigbee'
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class DeviceEntry:
|
||||
"""Device Registry Entry."""
|
||||
|
||||
connection = attr.ib(type=list)
|
||||
identifiers = attr.ib(type=list)
|
||||
manufacturer = attr.ib(type=str)
|
||||
model = attr.ib(type=str)
|
||||
connection = attr.ib(type=list)
|
||||
name = attr.ib(type=str, default=None)
|
||||
sw_version = attr.ib(type=str, default=None)
|
||||
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
|
||||
|
@ -48,8 +51,8 @@ class DeviceRegistry:
|
|||
return None
|
||||
|
||||
@callback
|
||||
def async_get_or_create(self, identifiers, manufacturer, model,
|
||||
connection, *, name=None, sw_version=None):
|
||||
def async_get_or_create(self, *, connection, identifiers, manufacturer,
|
||||
model, name=None, sw_version=None):
|
||||
"""Get device. Create if it doesn't exist."""
|
||||
device = self.async_get_device(identifiers, connection)
|
||||
|
||||
|
@ -57,10 +60,10 @@ class DeviceRegistry:
|
|||
return device
|
||||
|
||||
device = DeviceEntry(
|
||||
connection=connection,
|
||||
identifiers=identifiers,
|
||||
manufacturer=manufacturer,
|
||||
model=model,
|
||||
connection=connection,
|
||||
name=name,
|
||||
sw_version=sw_version
|
||||
)
|
||||
|
@ -93,10 +96,10 @@ class DeviceRegistry:
|
|||
data['devices'] = [
|
||||
{
|
||||
'id': entry.id,
|
||||
'connection': entry.connection,
|
||||
'identifiers': entry.identifiers,
|
||||
'manufacturer': entry.manufacturer,
|
||||
'model': entry.model,
|
||||
'connection': entry.connection,
|
||||
'name': entry.name,
|
||||
'sw_version': entry.sw_version,
|
||||
} for entry in self.devices
|
||||
|
|
|
@ -275,8 +275,11 @@ class EntityPlatform:
|
|||
device = entity.device
|
||||
if device is not None:
|
||||
device = device_registry.async_get_or_create(
|
||||
device['identifiers'], device['manufacturer'],
|
||||
device['model'], device['connection'],
|
||||
connection=device['connection'],
|
||||
identifiers=device['identifiers'],
|
||||
manufacturer=device['manufacturer'],
|
||||
model=device['model'],
|
||||
name=device.get('name'),
|
||||
sw_version=device.get('sw_version'))
|
||||
device_id = device.id
|
||||
else:
|
||||
|
|
|
@ -810,7 +810,7 @@ pycsspeechtts==1.0.2
|
|||
pydaikin==0.4
|
||||
|
||||
# homeassistant.components.deconz
|
||||
pydeconz==43
|
||||
pydeconz==44
|
||||
|
||||
# homeassistant.components.zwave
|
||||
pydispatcher==2.0.5
|
||||
|
|
|
@ -139,7 +139,7 @@ py-canary==0.5.0
|
|||
pyblackbird==0.5
|
||||
|
||||
# homeassistant.components.deconz
|
||||
pydeconz==43
|
||||
pydeconz==44
|
||||
|
||||
# homeassistant.components.zwave
|
||||
pydispatcher==2.0.5
|
||||
|
|
|
@ -7,6 +7,16 @@ from homeassistant.components import deconz
|
|||
|
||||
from tests.common import mock_coro
|
||||
|
||||
CONFIG = {
|
||||
"config": {
|
||||
"bridgeid": "0123456789ABCDEF",
|
||||
"mac": "12:34:56:78:90:ab",
|
||||
"modelid": "deCONZ",
|
||||
"name": "Phoscon",
|
||||
"swversion": "2.05.35"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_config_with_host_passed_to_config_entry(hass):
|
||||
"""Test that configured options for a host are loaded via config entry."""
|
||||
|
@ -93,8 +103,11 @@ async def test_setup_entry_successful(hass):
|
|||
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
|
||||
with patch.object(hass, 'async_create_task') as mock_add_job, \
|
||||
patch.object(hass, 'config_entries') as mock_config_entries, \
|
||||
patch('pydeconz.DeconzSession.async_load_parameters',
|
||||
return_value=mock_coro(True)):
|
||||
patch('pydeconz.DeconzSession.async_get_state',
|
||||
return_value=mock_coro(CONFIG)), \
|
||||
patch('pydeconz.DeconzSession.start', return_value=True), \
|
||||
patch('homeassistant.helpers.device_registry.async_get_registry',
|
||||
return_value=mock_coro(Mock())):
|
||||
assert await deconz.async_setup_entry(hass, entry) is True
|
||||
assert hass.data[deconz.DOMAIN]
|
||||
assert hass.data[deconz.DATA_DECONZ_ID] == {}
|
||||
|
@ -117,10 +130,15 @@ async def test_unload_entry(hass):
|
|||
"""Test being able to unload an entry."""
|
||||
entry = Mock()
|
||||
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
|
||||
with patch('pydeconz.DeconzSession.async_load_parameters',
|
||||
return_value=mock_coro(True)):
|
||||
entry.async_unload.return_value = mock_coro(True)
|
||||
deconzmock = Mock()
|
||||
deconzmock.async_load_parameters.return_value = mock_coro(True)
|
||||
deconzmock.sensors = {}
|
||||
with patch('pydeconz.DeconzSession', return_value=deconzmock):
|
||||
assert await deconz.async_setup_entry(hass, entry) is True
|
||||
|
||||
assert deconz.DATA_DECONZ_EVENT in hass.data
|
||||
|
||||
hass.data[deconz.DATA_DECONZ_EVENT].append(Mock())
|
||||
hass.data[deconz.DATA_DECONZ_ID] = {'id': 'deconzid'}
|
||||
assert await deconz.async_unload_entry(hass, entry)
|
||||
|
@ -132,6 +150,9 @@ async def test_unload_entry(hass):
|
|||
|
||||
async def test_add_new_device(hass):
|
||||
"""Test adding a new device generates a signal for platforms."""
|
||||
entry = Mock()
|
||||
entry.data = {'host': '1.2.3.4', 'port': 80,
|
||||
'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False}
|
||||
new_event = {
|
||||
"t": "event",
|
||||
"e": "added",
|
||||
|
@ -147,11 +168,10 @@ async def test_add_new_device(hass):
|
|||
"type": "ZHASwitch"
|
||||
}
|
||||
}
|
||||
entry = Mock()
|
||||
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
|
||||
with patch.object(deconz, 'async_dispatcher_send') as mock_dispatch_send, \
|
||||
patch('pydeconz.DeconzSession.async_load_parameters',
|
||||
return_value=mock_coro(True)):
|
||||
patch('pydeconz.DeconzSession.async_get_state',
|
||||
return_value=mock_coro(CONFIG)), \
|
||||
patch('pydeconz.DeconzSession.start', return_value=True):
|
||||
assert await deconz.async_setup_entry(hass, entry) is True
|
||||
hass.data[deconz.DOMAIN].async_event_handler(new_event)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -162,15 +182,16 @@ async def test_add_new_device(hass):
|
|||
async def test_add_new_remote(hass):
|
||||
"""Test new added device creates a new remote."""
|
||||
entry = Mock()
|
||||
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
|
||||
entry.data = {'host': '1.2.3.4', 'port': 80,
|
||||
'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False}
|
||||
remote = Mock()
|
||||
remote.name = 'name'
|
||||
remote.type = 'ZHASwitch'
|
||||
remote.register_async_callback = Mock()
|
||||
with patch('pydeconz.DeconzSession.async_load_parameters',
|
||||
return_value=mock_coro(True)):
|
||||
with patch('pydeconz.DeconzSession.async_get_state',
|
||||
return_value=mock_coro(CONFIG)), \
|
||||
patch('pydeconz.DeconzSession.start', return_value=True):
|
||||
assert await deconz.async_setup_entry(hass, entry) is True
|
||||
|
||||
async_dispatcher_send(hass, 'deconz_new_sensor', [remote])
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 1
|
||||
|
@ -185,8 +206,9 @@ async def test_do_not_allow_clip_sensor(hass):
|
|||
remote.name = 'name'
|
||||
remote.type = 'CLIPSwitch'
|
||||
remote.register_async_callback = Mock()
|
||||
with patch('pydeconz.DeconzSession.async_load_parameters',
|
||||
return_value=mock_coro(True)):
|
||||
with patch('pydeconz.DeconzSession.async_get_state',
|
||||
return_value=mock_coro(CONFIG)), \
|
||||
patch('pydeconz.DeconzSession.start', return_value=True):
|
||||
assert await deconz.async_setup_entry(hass, entry) is True
|
||||
|
||||
async_dispatcher_send(hass, 'deconz_new_sensor', [remote])
|
||||
|
|
|
@ -26,14 +26,17 @@ 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(
|
||||
[['bridgeid', '0123']], 'manufacturer', 'model',
|
||||
[['ethernet', '12:34:56:78:90:AB:CD:EF']])
|
||||
connection=[['ethernet', '12:34:56:78:90:AB:CD:EF']],
|
||||
identifiers=[['bridgeid', '0123']],
|
||||
manufacturer='manufacturer', model='model')
|
||||
entry2 = registry.async_get_or_create(
|
||||
[['bridgeid', '0123']], 'manufacturer', 'model',
|
||||
[['ethernet', '11:22:33:44:55:66:77:88']])
|
||||
connection=[['ethernet', '11:22:33:44:55:66:77:88']],
|
||||
identifiers=[['bridgeid', '0123']],
|
||||
manufacturer='manufacturer', model='model')
|
||||
entry3 = registry.async_get_or_create(
|
||||
[['bridgeid', '1234']], 'manufacturer', 'model',
|
||||
[['ethernet', '12:34:56:78:90:AB:CD:EF']])
|
||||
connection=[['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
|
||||
|
@ -73,6 +76,7 @@ async def test_loading_from_storage(hass, hass_storage):
|
|||
registry = await device_registry.async_get_registry(hass)
|
||||
|
||||
entry = registry.async_get_or_create(
|
||||
[['serial', '12:34:56:78:90:AB:CD:EF']], 'manufacturer',
|
||||
'model', [['Zigbee', '01.23.45.67.89']])
|
||||
connection=[['Zigbee', '01.23.45.67.89']],
|
||||
identifiers=[['serial', '12:34:56:78:90:AB:CD:EF']],
|
||||
manufacturer='manufacturer', model='model')
|
||||
assert entry.id == 'abcdefghijklm'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue