Skip to content

Create zwave devices on OZW thread and only add them during discovery#6096

Merged
balloob merged 10 commits into
home-assistant:devfrom
andrey-git:zwave_thread
Feb 23, 2017
Merged

Create zwave devices on OZW thread and only add them during discovery#6096
balloob merged 10 commits into
home-assistant:devfrom
andrey-git:zwave_thread

Conversation

@andrey-git
Copy link
Copy Markdown
Contributor

Description:

Create zwave devices on OZW thread and only add them during discovery

If the code does not interact with devices:

  • Local tests with tox run successfully. Your PR cannot be merged unless tests pass

@mention-bot
Copy link
Copy Markdown

@andrey-git, thanks for your PR! By analyzing the history of the files in this pull request, we identified @turbokongen, @fabaff and @armills to be potential reviewers.

@emlove
Copy link
Copy Markdown
Contributor

emlove commented Feb 19, 2017

Awesome work. This is exactly what we needed for zwave. 👍

I just have two small suggestions:

  1. For the _DEVICES dict, can we use the zwave ValueID as the key, instead of entityID? The ValueID already has a lot of information built-in to ensure uniqueness. Technically, using EntityID shouldn't be a problem, because of the way the zwave component generates them uniqueness should never be a problem. But procedurally, we really shouldn't be using entity IDs as keys for anything until they have been added to the state machine, since that is where HASS authoritatively confirms the entity ID is valid.

  2. in zwave.get_device, can we have it pop from the dict? Each device should only be added and accessed once, so this way we can keep the dictionary size small.

@andrey-git
Copy link
Copy Markdown
Contributor Author

  1. Will do

  2. Since OZW thread can update the dict it is not safe to update it in loop thread.
    I hope it is ok to read it in loop thread.

@balloob
Copy link
Copy Markdown
Member

balloob commented Feb 19, 2017

You could have OZW add an async job to hass to add it to the dict.

entity = get_entity(…)

@callback
def add_entity():
    hass.data[DATA_ZWAVE_DICT][entity.entity_id] = entity

hass.add_job(add_entity)

(obv replace entity_id with value id etc)

@emlove
Copy link
Copy Markdown
Contributor

emlove commented Feb 19, 2017

@balloob If we call that before scheduling the platform setup, do we know for sure it will have been added before setup_platform is called?

And actually, is there any reason we can't just pass the entity object directly to platform setup? After the call to platform get_device, nothing is touching that object until setup_platform.

@andrey-git
Copy link
Copy Markdown
Contributor Author

@armills everything passed to platform setup must be serializable. This was the first thing I tried.

@emlove
Copy link
Copy Markdown
Contributor

emlove commented Feb 19, 2017

OK, I wasn't certain. Actually, what you could do is add a job like @balloob said, and have it first update the dict, then call the discovery method. That way we know for certain it happens in the correct order.

EDIT: Like this from balloob's previous example.

entity = get_entity(…)

@asyncio.coroutine
def add_entity():
    hass.data[DATA_ZWAVE_DICT][entity.entity_id] = entity
    yield from discovery.async_load_platform(hass, component, DOMAIN, {
        ...

hass.add_job(add_entity)

Copy link
Copy Markdown
Member

@balloob balloob left a comment

Choose a reason for hiding this comment

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

This looks very good. Just a few minor tweaks.

_LOGGER.debug('name=%s node_config=%s CONF_REFRESH_VALUE=%s'
' CONF_REFRESH_DELAY=%s', name, node_config,
refresh, delay)
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(for a future PR)

Should this filtering happening inside these methods or should get_device only be called with the correct values? (And thus the filtering done before)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Some filtering can be removed as it is already done in component. I'll look into it in another PR

if value.genre != zwave.const.GENRE_USER:
return
return None
if node.has_command_class(zwave.const.COMMAND_CLASS_USER_CODE):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Registering these services should stay in setup_platform

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

They are registered conditionally by accessing node, so must be done on OZW thread.

value.disable_poll()
platform = get_platform(component, DOMAIN)
device = platform.get_device(
node=node, value=value, node_config=node_config, hass=hass)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can move node_config out of hass.data since we pass it in here.

I am not a big fan of using **kwargs for get_device. I think that we should standardize it.

get_config(node, value, node_config):

There is no hass there because this all about creating the device. Hass will be set on the entity once added to hass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Climate needs hass to pull the default units.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Also most platforms don't need all 4 - won't it look better if each one would "declare" only what it needs?

return None


def setup_platform(hass, config, add_devices, discovery_info=None):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that you can make a generic setup that you just import for each platform. We should make it async for improved speed:

(to be placed in zwave/__init__.py)

@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    """Generic Z-Wave platform setup."""
    if discovery_info is None or zwave.NETWORK is None:
        return False
    device = zwave.get_device(hass, discovery_info[zwave.const.DISCOVERY_DEVICE])
    if device:
        yield from async_add_devices([device])
        return True
    else:
        return False

In the case of the lock component, you could then wrap this method and register the services.

from homeassistant.components.zwave import async_setup_platform as zwave_async_setup_platform
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    result = yield from zwave_async_setup_platform(…)
    if not result or hass.services.has_service('lock', SERVICE_XX):
        return
    # Register services

def discover_device(component, device, dict_id):
"""Put device in a dictionary and call discovery on it."""
hass.data[DATA_ZWAVE_DICT][dict_id] = device
discovery.load_platform(hass, component, DOMAIN, {
Copy link
Copy Markdown
Contributor

@emlove emlove Feb 19, 2017

Choose a reason for hiding this comment

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

We should probably use the async equivalent here, since we're already in the event loop at this point. No sense in creating a new job: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/helpers/discovery.py#L136 There's nothing waiting for discover_device to return.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

load_platform will blow up when used inside the event loop, so yes. We should.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

@andrey-git
Copy link
Copy Markdown
Contributor Author

Addressed comments, commented on those not addressed.

@andrey-git
Copy link
Copy Markdown
Contributor Author

Something is broken - the devices are not added. I'll investigate tomorrow.

if device:
dict_id = value.value_id

@callback
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When this was a callback it was never executed

@andrey-git
Copy link
Copy Markdown
Contributor Author

Fixed

return None


@callback
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not a callback: there is a yield in this method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done



@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why create this method instead of just directly importing it?

from homeassistant.components.zwave import async_setup_platform  # noqa

That's all there is to it 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(noqa to tell our linter to sssh)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done with # noqa # pylint: disable=unused-import
noqa only affects flake8

track_point_in_time(
self._hass, self.async_update_ha_state,
self.invalidate_after)
if self.hass:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please use a guard clause:

if not self.hass:
    return

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

platform = get_platform(component, DOMAIN)
device = platform.get_device(
node=node, value=value, node_config=node_config, hass=hass)
if device:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please use a guard clause.

if not device:
    return

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

@andrey-git
Copy link
Copy Markdown
Contributor Author

Lets not merge this into 0.39 but let it soak in dev after the release is cut.

@andrey-git
Copy link
Copy Markdown
Contributor Author

0.39 has been cut - now ready for merge :)

@balloob balloob merged commit 1d32bce into home-assistant:dev Feb 23, 2017
@andrey-git andrey-git deleted the zwave_thread branch February 24, 2017 06:10
@home-assistant home-assistant locked and limited conversation to collaborators Jun 2, 2017
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.

5 participants