Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Device Registry #15980

Merged
merged 37 commits into from
Aug 22, 2018
Merged

Device Registry #15980

merged 37 commits into from
Aug 22, 2018

Conversation

Kane610
Copy link
Member

@Kane610 Kane610 commented Aug 14, 2018

Description:

Device registry based on home-assistant/architecture#36

Related issue (if applicable): fixes #

Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.github.io#<home-assistant.github.io PR number goes here>

Checklist:

  • The code change is tested and works locally.
  • Local tests pass with tox. Your PR cannot be merged unless tests pass

If user exposed functionality or configuration variables are added/changed:

If the code does not interact with devices:

  • Tests have been added to verify that the new code works.

@Kane610 Kane610 requested a review from a team as a code owner August 14, 2018 20:33
@ghost ghost assigned Kane610 Aug 14, 2018
@ghost ghost added the in progress label Aug 14, 2018
registry = await async_get_registry(hass)

tasks = [
self._async_add_entity(entity, update_before_add,
component_entities, registry)
component_entities, registry, device_registry)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (81 > 79 characters)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename registry -> entity_registry so we keep our sanity :)

@@ -209,11 +210,13 @@ def add_entities(self, new_entities, update_before_add=False):
hass = self.hass
component_entities = set(hass.states.async_entity_ids(self.domain))


device_registry = async_get_device_registry(self.hass)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too many blank lines (2)

@awarecan
Copy link
Contributor

How can I get all entities of one device?

I feel that a Demo device included demo light, demo switch, demo sensor will better than use deconz as demo. And also easy to write unit test for demo device.

class DeviceEntry:
"""Device Registry Entry."""

unique_id = attr.ib(type=str)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's name this id and use a factory to automatically generate it

    id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, read up on unique_id. Drop this comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So after reading everything, let's circle back here:

We should add the id property to DeviceEntry, it will be how we can point at this device from the entity registry.

For unique_id and serial, I would expect us to have a tuple of identifiers. Like connection, but with just identifying things:

identifiers = (
  ('serial', 'abcdefgh'),  # if we can read the device serial
  ('nest', 'czxcz'),  # if the developer wants to pass an identifier. They can use their domain to scope it.
)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we would only do identification on identifiers, or connections are still viable due to discovery?

serial = attr.ib(type=str)
manufacturer = attr.ib(type=str)
model = attr.ib(type=str)
connection = attr.ib(type=tuple)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea was for this to be a tuple of tuples. A device can have multiple connections. For example a Raspberry Pi has WiFi and LAN connection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use some kind of standardisation for the types?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CONNECTION_ZIGBEE = 'zigbee'
CONNECTION_MAC = 'mac'

I guess we can also have duplicates (wifi + lan = 2 macs ?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how often you use both wifi and lan for a device that is connected to hass

model = attr.ib(type=str)
connection = attr.ib(type=tuple)
sw_version = attr.ib(type=str, default=None)
config_entry_id = attr.ib(type=set, default=attr.Factory(set))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize now that this is not the right place.

Instead, this will be linked via the Entity Registry which will contain a reference to the device registry and to the config entry.

serial = self._sensor.uniqueid.split('-', 1)[0]
else:
return None
dev = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this to be a loosely defined dictionary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As opposed to using get_create_device in each entity.device? I started in this way, not set in stone

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could return an instance of a class for this purpose, similar to the DeviceEntry class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could use async_get_create_device directly, then entity_platform would only do the coupling between entity_registry and device_registry.

def async_get_device(self, serial: str, connection: tuple):
"""Check if device is registered."""
for device in self.devices.values():
if (device.serial == serial or
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we're treating serial as unique ID?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the discussion up top originally; first it was unique id, it was then moved to serial. Regardless, gonna redo it all to your suggested identifier tuple

@@ -209,11 +210,13 @@ def add_entities(self, new_entities, update_before_add=False):
hass = self.hass
component_entities = set(hass.states.async_entity_ids(self.domain))


device_registry = async_get_device_registry(self.hass)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this, you could, albeit more lines, type:

device_registry = hass.helpers.device_registry.async_get_registry()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I do the same for entity registry?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah

@@ -269,10 +272,22 @@ def add_entities(self, new_entities, update_before_add=False):
else:
config_entry_id = None

if entity.device is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entity.device will actually execute code so better to just request it once instead of 8 times.

device = entity.device

if device is not None:

entity.device['connection'],
sw_version=entity.device.get('sw_version'),
config_entry_id=config_entry_id)
device_id = device.unique_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be device.id, we need to generate our own IDs for devices and not trust developers.

@balloob
Copy link
Member

balloob commented Aug 15, 2018

This is off to a great start !

Left some comments. Other suggestions:

  • split out Deconz from the PR.
  • Add Storage helper for persistence
  • Add tests

@Kane610
Copy link
Member Author

Kane610 commented Aug 15, 2018

Thanks for the feedback! Everything but tests I should be able to get fixed tonight.

@Kane610
Copy link
Member Author

Kane610 commented Aug 15, 2018

@balloob if we do persistent storage, should I cover removal of entities -> devices with the initial implementation?

device_registry = (await \
hass.helpers.device_registry.async_get_registry())
entity_registry = (await \
hass.helpers.entity_registry.async_get_registry())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line under-indented for visual indent

registry = await async_get_registry(hass)
device_registry = (await \
hass.helpers.device_registry.async_get_registry())
entity_registry = (await \

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the backslash is redundant between brackets

@@ -209,11 +208,15 @@ def add_entities(self, new_entities, update_before_add=False):
hass = self.hass
component_entities = set(hass.states.async_entity_ids(self.domain))

registry = await async_get_registry(hass)
device_registry = (await \
hass.helpers.device_registry.async_get_registry())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line under-indented for visual indent

@@ -209,11 +208,15 @@ def add_entities(self, new_entities, update_before_add=False):
hass = self.hass
component_entities = set(hass.states.async_entity_ids(self.domain))

registry = await async_get_registry(hass)
device_registry = (await \

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the backslash is redundant between brackets

return None

async def async_get_or_create(self, identifiers, manufacturer, model,
connection, *, sw_version=None):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line under-indented for visual indent

@@ -12,6 +12,8 @@
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 (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'homeassistant.helpers.device_registry.async_get_registry as async_get_device_registry' imported but unused

@balloob
Copy link
Member

balloob commented Aug 16, 2018

Let's not cover removal, we do that in a future PR.

@balloob
Copy link
Member

balloob commented Aug 17, 2018

For inspiration on how to handle storage, see #16018

@@ -210,6 +218,7 @@ def _async_update_entity(self, entity_id, *, name=_UNDEF,
entities[entity['entity_id']] = RegistryEntry(
entity_id=entity['entity_id'],
config_entry_id=entity.get('config_entry_id'),
device_id=info.get('device_id'),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'info'

@Kane610 Kane610 changed the title WIP: Device Registry Device Registry Aug 21, 2018
Copy link
Member

@syssi syssi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

if any(identifier in device.identifiers
for identifier in identifiers) or \
any(connection in device.connection
for connection in connections):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line over-indented for visual indent

@balloob balloob merged commit 0009be5 into home-assistant:dev Aug 22, 2018
@ghost ghost removed the in progress label Aug 22, 2018
@Kane610
Copy link
Member Author

Kane610 commented Aug 22, 2018

🎉

@MartinHjelmare
Copy link
Member

Now we need to update the developer docs, so we can show developers how to use this.

@Kane610 Kane610 deleted the device-concept branch August 23, 2018 21:41
This was referenced Aug 27, 2018
@amelchio amelchio mentioned this pull request Aug 30, 2018
6 tasks
@home-assistant home-assistant locked and limited conversation to collaborators Dec 10, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants