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

Add deCONZ component #10321

Merged
merged 98 commits into from
Jan 1, 2018
Merged

Conversation

Kane610
Copy link
Member

@Kane610 Kane610 commented Nov 3, 2017

Description:

Component supporting Deconz (lights, sensors) rest API and web sockets for push notification.

The Implementation is more or less code complete.

The Component supports all devices (on a theoretical level) supported by deCONZ. There are lists of verified devices in the documentation.

Pull request in home-assistant.github.io with documentation: home-assistant/home-assistant.io#3967
Pull request in home-assistant-polymer with new logo for configurator: home-assistant/frontend#689

Example entry for configuration.yaml (if applicable):

deconz:
  host: 192.168.0.90
  api_key: 1234567890

If you don't have a API key and want to generate one use this config and then follow the steps in the GUI:

deconz:
  host: 192.168.0.90

The component will automatically create an API key and put it into deconz.conf.

If you prefer you can use auto discovery component instead and follow instructions in GUI.

Checklist:

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

If the code communicates with devices, web services, or third-party tools:

  • Local tests with tox run successfully. Your PR cannot be merged unless tests pass
  • New dependencies have been added to the REQUIREMENTS variable (example).
  • New dependencies are only imported inside functions that use them (example).
  • New dependencies have been added to requirements_all.txt by running script/gen_requirements_all.py.
  • New files were added to .coveragerc.


from homeassistant.components.light import (Light,
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_XY_COLOR,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, SUPPORT_XY_COLOR)

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
line too long (80 > 79 characters)

import logging

from homeassistant.components.light import (Light,
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_XY_COLOR,

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

import asyncio
import logging

from homeassistant.components.light import (Light,

Choose a reason for hiding this comment

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

'homeassistant.components.light.ATTR_XY_COLOR' imported but unused

from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_connect)
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify

Choose a reason for hiding this comment

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

'homeassistant.util.slugify' imported but unused

DOMAIN = 'deconz'

DECONZ_DATA = 'deconz_data'
CONFIG_FILE = 'deconz.conf'
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the path to the config file should be configurable.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is just the name, the read_json, load_json gets the full path from HASS. From my understanding this is standard behaviour.

Copy link
Contributor

Choose a reason for hiding this comment

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

As, I see what that file is for.

Copy link
Member

Choose a reason for hiding this comment

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

If it's not meant for users to manually read/write it, prefix the name with a .

vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_API_KEY): cv.string,
vol.Optional(CONF_PORT, default=80): cv.port,
vol.Optional(TYPE_AS_EVENT, default=['ZHASwitch']):
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this supposed to do? It looks strange, and it's not documented in the accompanying documentation PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Normal behaviour for momentary sensors are to be events, I'd like to have an opening in getting it as a sensor entity instead by disabling it. I'm just keeping it configurable.

DECONZ_DATA = 'deconz_data'
CONFIG_FILE = 'deconz.conf'

TYPE_AS_EVENT = 'type_as_event'
Copy link
Contributor

Choose a reason for hiding this comment

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

For naming reasons, this should probably also start with CONF_.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, good catch!

def async_setup(hass, config):
"""Setup services for Deconz."""
deconz_config = config[DOMAIN]
config_file = load_json(hass.config.path(CONFIG_FILE))
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure, but: Since this is a coroutine, it runs in the event loop, and load_json probably blocks. Is there an asynchroneous way of reading the JSON?

Copy link
Member Author

Choose a reason for hiding this comment

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

There is no similar async version but from what I could see in Discord, Balloob didn't think this was an issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, great! 👍

Copy link
Member

Choose a reason for hiding this comment

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

uuuh what. This is a really big issue. NEVER do I/O inside the event loop.

It is not a big issue that no async version exists, because one does never do I/O inside the event loop. Instead, do yield from hass.async_add_job(load_json, hass.config.path(CONFIG_FILE)). Although, if you are not familiar with how asyncio works, it is preferred that you don't write async components. Write sync ones instead.

Store API key in deconz.conf.
"""
from pydeconz.utils import get_api_key
deconz_config[CONF_USERNAME] = call.data.get(CONF_USERNAME, 'delight')
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that the default values for username / password should be hard coded here, but rather added as default value to the config schema. Nobody's going to look for the default value here. ;)

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 guess I could create a second config schema for the service? Since the username and password is only relevant for the service to generate the API key. Not sure what is best :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I just now realize that this is a service! Why do you make this a service? The component cannot work before that service was called / the API key was generated, can it? So how about this workflow:

  • On startup, check if an API key was generated
    • If so, initialize the component
    • If not, retrieve username / password from the config, generate a new API key
      • If that worked, initialize the component
      • If it didn't, fail the initialization

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 had it like that initially, but then I thought that I don't want the user to need to remove the username and password afterwords. Originally I created a notification in the GUI for the user to write down and add to the configuration.yaml. This is the opposite in that after initially specifying the component you are not required to add any additional configuration for the component.

But in the end I guess it is a matter of taste.

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'm gonna think a bit more about this, you have some valid points!

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 changed it to more as you suggested

deconz_config[CONF_USERNAME] = call.data.get(CONF_USERNAME, 'delight')
deconz_config[CONF_PASSWORD] = call.data.get(CONF_PASSWORD, 'delight')
api_key = yield from get_api_key(hass.loop, **deconz_config)
if not save_json(
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above - is there an async way of doing this?

Copy link
Member Author

Choose a reason for hiding this comment

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

See previous comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

else:
hass.services.async_register(
DOMAIN, 'generate_api_key', generate_api_key)
_LOGGER.warning("Deconz needs API key to set up session. See docs.")
Copy link
Contributor

Choose a reason for hiding this comment

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

If the component can't do anything without an API key, this should probably be an error, not just a warning (i.e., return False here)

Copy link
Member Author

Choose a reason for hiding this comment

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

An error is a bit extreme since you still intentionally have to enable the component. Which means that if you don't specify the API key you have the intent of generating one using the service which will only load when the API key is missing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I agree that a failure would be too hard if you have to call the generate_api_key service manually. But with the workflow from above, that should not be necessary, should 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.

Absolutely. See the other post for my reasoning.

DOMAIN,
deconz_config,
config))
deconz.start()
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this block?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it is a asyncio transport class.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍



class DeconzEvent(object):
"""When you want signals instead of entities."""
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm afraid I don't understand what that means. :(

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 understand this since you only had time to spend on this file ;).

It is a event class that will take a device sensor and generate events in hass instead of creating a sensor entity.

I will improve the pydocstring.

data = {'on': True}

if ATTR_BRIGHTNESS in kwargs:
data['bri'] = kwargs[ATTR_BRIGHTNESS]
Copy link
Contributor

Choose a reason for hiding this comment

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

You might want to move this down below RGB so one can specify both and have the RGB brightness overridden.

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 hear you.

@tinloaf
Copy link
Contributor

tinloaf commented Nov 15, 2017

Regarding the event / entity thing: I think I got it now. I wonder though what the advantage of the "event mode" is over having an entity. If you have an entity for every sensor, you still get an event every time the sensor's state changes (a STATE_CHANGED event), so "entity" mode basically has all features of the "event" mode … and then some.

Also, from what I know it's pretty uncommon to not represent physical devices by entities, but by a stream of events instead. I think if my assumption above is correct (that the event mode doesn't give you any "extra features") I would be in favor of just using the "entity mode". Reduces code complexity and makes this component behave more like all other components.

@Kane610
Copy link
Member Author

Kane610 commented Nov 15, 2017

This specific thing I've had a long talk with another author of a Deconz component as well as Balloob to get a proper decision. I can agree that some people would prefer entities for all devices, that's why I left the 'undocumented' configuration possibility to alter this from the default behaviour.

@property
def unit_of_measurement(self):
"""Unit of measurement of this entity."""
return self._sensor.sensor_unit
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you should translate here into home assistant - standardized units such as TEMP_CELSIUS (which is defined as 'C', while pydeconz would use 'Celsius'). The same probably holds for other units?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch! Too quick on that one.

return self._sensor.sensor_icon

@property
def unit_of_measurement(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

See the other sensor file - maybe translation would be benefical here, although I am not sure what unit a binary sensor should have.

Copy link
Member Author

Choose a reason for hiding this comment

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

No that is true, copy paste issue...

def device_state_attributes(self):
"""Return the state attributes of the sensor."""
attr = {
'battery_level': self._sensor.battery,
Copy link
Contributor

Choose a reason for hiding this comment

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

ATTR_BATTERY_LEVEL from const.py

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point!

'manufacturer': self._sensor.manufacturer,
'model_number': self._sensor.modelid,
'reachable': self._sensor.reachable,
'uniqueid': self._sensor.uniqueid,
Copy link
Contributor

Choose a reason for hiding this comment

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

ATTR_ID from const.py ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point!

@property
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
attr = {
Copy link
Contributor

Choose a reason for hiding this comment

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

For the attribute names that cannot be imported from const.py, I think it would be cleaner to define your own ATTR_* constants in the component, so that you have one central place where they are defined and could be changed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure

@property
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
if self._light.type != 'LightGroup':
Copy link
Contributor

Choose a reason for hiding this comment

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

See binary sensor.

@tinloaf
Copy link
Contributor

tinloaf commented Nov 15, 2017

Edit: If you're in a hurry

What follows is a long thread of me not understanding that the "sensors" sometimes represent clickable buttons instead of real sensors, and having the (on the first glance somewhat weird-looking) "event instead of entity" approach is probably a good idea. If you're in a hurry you can probably fast-forward to #10321 (comment)

Original comment

My preference for "entity only" is less because of what the user might want, but because of (in my eyes unnecessarily) added code complexity. I mean, you could remove two out of five classes (the event class and the battery class), you would not have the (somewhat strange, I think) "battery" entities, …

Also, breaking the "an phyiscal device is an entity" rule makes your component unusable with some other components, for example the recorder, mqtt statestream, anything that wants to do templating, anything that records historical data, …

But if @balloob is on okay with this, it's probably okay. 😉

@Kane610
Copy link
Member Author

Kane610 commented Nov 16, 2017

Valid points

Copy link
Contributor

@tinloaf tinloaf left a comment

Choose a reason for hiding this comment

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

I just had another quick discussion with @balloob , and we think that physical sensors should always be represented by an entity, instead of "hiding" them, generating custom events plus a battery sensor.

We have to try to keep a certain consistency throughout the home assistant code base to ensure maintainability. Since we're not able to see the advantages, we think the added code complexity and different approach from all other sensor platforms is not worth it.

@Kane610
Copy link
Member Author

Kane610 commented Nov 16, 2017

Sure, as long as there isn't a third change back to events later on ;)

@tinloaf
Copy link
Contributor

tinloaf commented Nov 16, 2017

Just to make sure: We're really talking about sensors here, not some buttons that are internally called "sensors", are we?

Copy link
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

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

Nice!

@Kane610
Copy link
Member Author

Kane610 commented Dec 31, 2017

@MartinHjelmare what's left for this PR to be merged?

@MartinHjelmare
Copy link
Member

MartinHjelmare commented Dec 31, 2017

I'm happy but our CLA bot seems to have troubles processing this PR. My guess is the PR simply has too many commits. Try squashing the older commits into one commit if you are comfortable with git. Otherwise you can make a new branch and copy your changes manually and open a new PR and link to this PR.

@Kane610
Copy link
Member Author

Kane610 commented Dec 31, 2017

I will look into how to squash commits tomorrow

@Kane610
Copy link
Member Author

Kane610 commented Dec 31, 2017

@balloob do you have any knowledge about the issue mentioned by MartinHjelmare?

Bump dependency to v23
Use only HASS aiohttp session
Change when to use 'deconz' or domain or deconz data
Clean up unused logger defines
Remove unnecessary return values
Fix faulty references to component documentation
Move callback registration to after entity has been initialized by HASS
Less inception style on pydocs ;)
Simplify loading platforms by using a for loop
Added voluptous schema for service
Yaml file is for deconz only, no need to have the domain present
Remove domain constraint when creating event title
@Kane610
Copy link
Member Author

Kane610 commented Jan 1, 2018

@MartinHjelmare I squashed a few commits, less than a 100 commits seems to be the sweet spot. But there seems to be a bigger issue with travis atm

@MartinHjelmare
Copy link
Member

@Kane610 I've restarted the build. Let's see what happens.

@Kane610
Copy link
Member Author

Kane610 commented Jan 1, 2018

Great @MartinHjelmare ! It is ready for merging 🍾

Copy link
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

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

Great!

@MartinHjelmare MartinHjelmare changed the title deCONZ component Add deCONZ component Jan 1, 2018
@MartinHjelmare MartinHjelmare merged commit b9c8523 into home-assistant:dev Jan 1, 2018
@ScuttleSE
Copy link

ScuttleSE commented Jan 4, 2018

So...I'm not sure I'm doing this correctly, but I have a bug report for this component :)

I have merged this pull request with my current installation of 0.60 and have gotten the deCONZ integration running just fine.

Except for one type of sensor I have; the Aqara Temperature/Humidity sensor.

When this is added to HA from deCONZ it is added as three sensors. One for temperature, one for humidity and one for airpressure.

They are added as sensor.[name in deconz], sensor.[name in deconz]_2 and sensor.[name in deconz]_3

The problem is that this order is seeminly random. After restarting HA, the sensor that previously was temperature might be the humidity or airpressure sensor.

Attaching two screenshots to illustrate. The first image is before restarting HA, the second one is after restarting.

sensor1a

sensor1b

@Kane610
Copy link
Member Author

Kane610 commented Jan 4, 2018

@ScuttleSE this is known. It is expected that the user name their devices uniquely.

@ScuttleSE
Copy link

How though? This is one device in deCONZ named temp_1_balcony which in HA gets named sensor.temp_1_balcony, sensor.temp_1_balcony_2 and sensor.temp_1_balcony_3

@Kane610
Copy link
Member Author

Kane610 commented Jan 4, 2018

@ScuttleSE either through the API, which you can use the deconz service in hass for, I think you can also use the new web frontend phoscon to do it. I'm also planning on looking in to doing frontend support later on for deconz. Lets take further discussions on the forums, this PR is closed.

@ScuttleSE
Copy link

ScuttleSE commented Jan 4, 2018

@Kane610 But...maybe I am misunderstanding here... :)

I have named the device temp_1_balcony using the phoscon webthing, that's where I added the sensor too, but there is only an option to name the whole device there, not the three "sub-sensors".

When the device gets sucked into HA through the API, it gets added as three separate devices, and HA seems to add the extensions _2 and _3 to two of the sensors at random to be able to differentiate between them

@Kane610
Copy link
Member Author

Kane610 commented Jan 4, 2018

As I said, lets take further discussions in the forum https://community.home-assistant.io/t/conbee-zigbee-stick-and-ha-will-this-work-together/

@ScuttleSE
Copy link

ScuttleSE commented Jan 4, 2018

I would if the site weren't in read-only mode :)

Never mind, it just disappeared

@Kane610
Copy link
Member Author

Kane610 commented Jan 4, 2018

I don't understand how that could be, have you posted earlier in the forums?

@MartinHjelmare
Copy link
Member

Please open an issue if you suspect a bug. If you need help please use our help channels:
https://home-assistant.io/help/#communication-channels

Merged PRs should not be used for support, bug reports or to discuss the implementation. Thanks!

@home-assistant home-assistant locked and limited conversation to collaborators Jan 4, 2018
@Kane610 Kane610 deleted the deconz-component branch January 31, 2018 21:24
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.