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

Dynalite Integration #27841

Merged
merged 11 commits into from
Feb 10, 2020
Merged

Dynalite Integration #27841

merged 11 commits into from
Feb 10, 2020

Conversation

ziv1234
Copy link
Contributor

@ziv1234 ziv1234 commented Oct 18, 2019

Breaking Change:

Not a breaking change. Only adds a new integration

Description:

Opened a new pull request to clean up fork

I used Troy Kelly's library and component as a starting point, added several features to them, and am now able to control my full house which is based on Dynalite. I know that no other open-source system supports it, so will be glad if it can go into the mainstream, so others can use it as well. Will also be happy to make fixes to issues that come.
Since I am new to open-source development, wanted to understand what is needed to get this merged. I documented the basic features, that are in this pull request and will continue to add the features and documentation, but was asked to do it in parts so each PR is small.
I tried to follow the guidelines in the dev docs, but I am sure that there are some things that I missed. If this is interesting to add, please let me know if there is anything other than the documentation that needs fixing and i will take care of it quickly.

Best,
Ziv

Related issue (if applicable): fixes #

Pull request with documentation for home-assistant.io (if applicable): home-assistant/home-assistant.io#10866

Example entry for configuration.yaml (if applicable):

dynalite:
  bridges:
    - host: DEVICE_IP_ADDRESS
      port: 12345
      autodiscover: true
      polltimer: 1
      areacreate: auto
      log_level: debug
      area:
        '2':
          name: Living Room
          channel:
            '2': 
              name: Entrance Spot
              fade: 10.0
            '3': 
              name: Dining Table

Checklist:

  • The code change is tested and works locally.
  • Local tests pass with tox. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist

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

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

  • The manifest file has all fields filled out correctly. Update and include derived files by running python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt by running python3 -m script.gen_requirements_all.
  • Untested files have been added to .coveragerc.

If the code does not interact with devices:

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

@ziv1234
Copy link
Contributor Author

ziv1234 commented Oct 18, 2019

I had a pull request Dynalite Integration #27621 but because I pulled from the upstream, the pull request included many commits. recreated the fork and added my files cleanly. it passes all the local tests.

please let me know if this is in the right direction. If it is, i can start working on the documentation

@ziv1234
Copy link
Contributor Author

ziv1234 commented Oct 23, 2019

Hi, there was no activity on the new pull request. Do I need to do something to re add it to the queue?

@fabaff
Copy link
Member

fabaff commented Oct 24, 2019

It's Hacktoberfest...we are dealing with approx. 300 open PRs across the organisation. Thus, it can take a while till the review starts.

@kookman
Copy link

kookman commented Dec 30, 2019

Is this still in the queue for review? I’d be very interested in using this in mainline release, rather than persisting with my own fork. Thanks Ziv.

@ziv1234
Copy link
Contributor Author

ziv1234 commented Dec 30, 2019

I would love to have it reviewed if possible. Not sure why no one picked it up. Was just about the send another question...

Note that there is much more functionality that I added in my module, but was suggested to split it into small pull requests, so anxious to have it there and start adding to it.

Ziv

@ziv1234
Copy link
Contributor Author

ziv1234 commented Jan 3, 2020

pinging again. can someone look at it or did i forget to do something to add it to the queues?

if not await bridge.async_setup():
LOGGER.error("bridge.async_setup failed")
return False
hass.data[DOMAIN][host] = bridge
Copy link
Member

Choose a reason for hiding this comment

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

We should store each bridge by entry.entry_id

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you please explain? Not sure what you mean by this. Is there any documentation or example of how this is done correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok. used the pattern from component tradfri. hopefully it is ok now

# Configure the dynalite devices
self.dynalite_devices = DynaliteDevices(
config=self.config,
loop=hass.loop,
Copy link
Member

Choose a reason for hiding this comment

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

Python is moving away from passing in loops. Why do we need to pass in the loop here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done. thanks

async def async_setup(self, tries=0):
"""Set up a Dynalite bridge."""
LOGGER.debug(
"component bridge async_setup - %s", pprint.pformat(self.config_entry.data)
Copy link
Member

Choose a reason for hiding this comment

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

This is expensive. You're always going to pretty format the config entry data, even if it's not going to be logged. Please remove this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed


async def async_step_import(self, import_info):
"""Import a new bridge as a config entry."""
LOGGER.debug("async_step_import - %s", pprint.pformat(import_info))
Copy link
Member

Choose a reason for hiding this comment

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

Please remove all usage of pprint.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

CONF_AREA_CREATE_AUTO = "auto"
CONF_BRIDGES = "bridges"

ENTITY_CATEGORIES = ["light", "switch", "cover"]
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't seem to be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

Copy link
Member

@balloob balloob Feb 10, 2020

Choose a reason for hiding this comment

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

Did you forget to push the change?

from .const import DOMAIN, LOGGER


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
Copy link
Member

Choose a reason for hiding this comment

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

You can remove this. It was never in Home Assistant under the old way.

"""Return true if this entity should be hidden from UI."""
return self._device.hidden

@callback
Copy link
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -0,0 +1,9 @@
{
"domain": "dynalite",
"name": "Direct Integration for Philips Dynalite",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"name": "Direct Integration for Philips Dynalite",
"name": "Philips Dynalite",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed

"domain": "dynalite",
"name": "Direct Integration for Philips Dynalite",
"config_flow": true,
"documentation": "",
Copy link
Member

Choose a reason for hiding this comment

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

Please add url to docs link on website.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

how do i know the docs link? Wouldn't it be merged at the same time this pull request goes into the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is pull request 10866 in the docs

Copy link
Member

Choose a reason for hiding this comment

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

The docs link is https://www.home-assistant.io/integrations/<domain>/

@balloob
Copy link
Member

balloob commented Feb 9, 2020

Hey Ziv, Thanks for your pull request. It took a while but we're slowly going through our backlog. I left some comments. Most of the things already look very good !

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 9, 2020

thanks. Looking at the comments now and will implement them

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 9, 2020

I also rebased it (per the docs instructions) so it is now working off the latest dev version

@codecov

This comment has been minimized.

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 10, 2020

ok. all the requested changes have been implemented

@balloob balloob merged commit 4467409 into home-assistant:dev Feb 10, 2020
@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 10, 2020 via email

from homeassistant.core import callback

from .const import DATA_CONFIGS, DOMAIN, LOGGER
from .light import DynaliteLight
Copy link
Member

Choose a reason for hiding this comment

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

We're leaking entity implementation details from the light platform to the bridge module. That's not a good pattern. The platform details should stay within the platform.

It's ok to extract a common function that's used by multiple platforms but the platform should still be responsible for creating and adding entities etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed. will be pushed shortly

if same_hub_entries:
await asyncio.wait(
[
self.hass.config_entries.async_remove(entry_id)
Copy link
Member

Choose a reason for hiding this comment

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

This is a legacy pattern. We now have the unique_id api for config flow and config entries.

https://developers.home-assistant.io/docs/en/config_entries_config_flow_handler.html#unique-ids

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is quite difficult to understand the config_flow. Do you know if there is any example component that has the most simple config flow (only initiated from configuration.yaml)?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think there's an integration with config flow and only import step that uses the unique_id api. That api is still quite new.

Often the import step just forwards the config to the user step, since the user step often can accept a subset of the items in the config import.

The rainmachine config flow is quite simple and has just import + user step. It doesn't use the unique_id api yet though.

Copy link
Member

Choose a reason for hiding this comment

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

I found a simple flow with just import + user step and unique_id api: gdacs.

LOGGER.debug("Illegal device category %s", device.category)
continue
added_entities.append(entity)
self.all_entities[entity.unique_id] = entity
Copy link
Member

Choose a reason for hiding this comment

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

We should not store the entity instances outside their platforms in a shared container. It's ok to store a reference to the entity, eg the entity_id, to keep track of added entities.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed. will be pushed shortly


for device in devices:
if device.category == "light":
entity = DynaliteLight(device, self)
Copy link
Member

Choose a reason for hiding this comment

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

The entity should be created and added within its platform.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed. will be pushed shortly

@callback
def try_schedule_ha(self):
"""Schedule update HA state if configured."""
if 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.

It's a bad sign that we have to have this check. Entity state update methods should not be called from outside the entity. That's error prone, and that's why this check is needed.

Instead, if we need to tell an entity to update from outside its platform, we can use our dispatch helper to signal the entity to update.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed. will be pushed shortly

else:
LOGGER.info("Disconnected from dynalite host")
for uid in self.all_entities:
self.all_entities[uid].try_schedule_ha()
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed. will be pushed shortly

self.host = config_entry.data[CONF_HOST]
if self.host not in hass.data[DOMAIN][DATA_CONFIGS]:
LOGGER.info("invalid host - %s", self.host)
raise BridgeError(f"invalid host - {self.host}")
Copy link
Member

Choose a reason for hiding this comment

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

We want to avoid side effects like logging or raising an error in init method. Please move that outside.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

config = hass.data[DOMAIN][DATA_CONFIGS].get(host)

if config is None:
LOGGER.error("__init async_setup_entry empty config for host %s", host)
Copy link
Member

Choose a reason for hiding this comment

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

We should not allow this to happen. This should be caught earlier in the config flow.

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 11, 2020 via email

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 11, 2020 via email

@MartinHjelmare
Copy link
Member

MartinHjelmare commented Feb 11, 2020

The platform entity creation and addition should stay within the platform and entities should not be accessed directly outside their platform. We're currently breaking that design pattern here. That's what I meant with "leak".

@MartinHjelmare
Copy link
Member

It looks like the dynalite is using a push based model for updating state. It's good to take the model for updates into account when searching for examples.

Smartthings uses push model and our dispatch helper to update entities. Entities don't seem to be discovered there though.

Zha has a discovery function, using the dispatch helper, in each platform that gets called to add new entities when a new device is discovered.

Mysensors is using a gateway and push model with dispatch helper but doesn't use config entry api, so is a bit dated.

Unfortunately, the integrations with highest code quality and patterns is often not the simplest and may be hard to read as a clean example.

@MartinHjelmare
Copy link
Member

Deconz is good example.

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 11, 2020 via email

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 12, 2020

figured out the dispatcher from the examples provided. Will implement all these comments and send a new pull request soon with them

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 12, 2020 via email

@ziv1234
Copy link
Contributor Author

ziv1234 commented Feb 12, 2020 via email

@lock lock bot locked and limited conversation to collaborators Feb 13, 2020
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.

6 participants