Skip to content

Move LIFX to aiolifx for driving the bulbs #6584

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

Merged
merged 8 commits into from
Mar 16, 2017

Conversation

amelchio
Copy link
Contributor

@amelchio amelchio commented Mar 13, 2017

Description:

This is more or less a rewrite of the LIFX component, migrating it to aiolifx. This makes it async, fixes all reported issues (and some unreported ones) and lays the ground for new features like support for the flash parameter.

The only tricky thing is that LIFX bulbs will report the old state for a short while after setting a new state. I therefore pause update requests to the bulb for 0.5s after setting a state.

The rework has been tested in #6435 but I am new to Python as well as Home Assistant so I expect some issues with implementation details.

Related issue (if applicable): fixes #3604, #4589, #5158, #6245, #6435

Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#2259

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.
  • New dependencies have been added to the REQUIREMENTS variable ([example][ex-requir]).
  • New dependencies are only imported inside functions that use them ([example][ex-import]).
  • New dependencies have been added to requirements_all.txt by running script/gen_requirements_all.py.

@mention-bot
Copy link

@amelchio, thanks for your PR! By analyzing the history of the files in this pull request, we identified @fabaff, @kitcorey and @BillyNate to be potential reviewers.

if fade < BULB_LATENCY:
self.set_power(0)

def got_color(self,device,msg):

Choose a reason for hiding this comment

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

missing whitespace after ','

else:
kelvin = self._kel

hsbk = [ hue, saturation, brightness, kelvin ]

Choose a reason for hiding this comment

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

whitespace after '['
whitespace before ']'

def probe(self, address=None):
"""Probe the light."""
self._liffylights.probe(address)
def unregister(self,device):

Choose a reason for hiding this comment

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

missing whitespace after ','

self.hass = hass
self.async_add_devices = async_add_devices

def register(self,device):

Choose a reason for hiding this comment

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

missing whitespace after ','

@amelchio amelchio changed the title [WIP] Move LIFX to aiolifx for driving the bulbs Move LIFX to aiolifx for driving the bulbs Mar 13, 2017
self.set_power(0)

def got_color(self, device, msg):
"""Callback that gets current power/color status"""
Copy link
Contributor

Choose a reason for hiding this comment

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

End with a period.

@amelchio
Copy link
Contributor Author

Sorry for multiple fixups. I don't have a proper local environment yet.

I need help on the available setter method.

amelchio added a commit to amelchio/home-assistant.github.io that referenced this pull request Mar 14, 2017
Remove the broadcast configuration variable.
@keatontaylor
Copy link
Contributor

@amelchio, I've been looking over this code and testing it on my own and ran into a few problems.

  1. It seems to work really well at detecting unavailable or offline bulbs, but in my testing when the bulbs come back online, home assistant takes a very long time to mark the bulbs as available again. (I tested by killing power to the bulb, waiting for it to be marked unavailable, then restoring power) How long should this take? What is the update interval.
  2. Also, requesting an update on the bulb after a transition is complete is a good way to prevent the state in HA from becoming incorrect, but I would also like to see the ability to disable automatic polling all together as a feature and (polling on by default) and instead only using the force update code when the bulb state changes.
  3. Make the polling interval configurable for those who wish to have more frequent views of the outside world, this would help with those who wish to control LIFX bulbs from external devices.

@keatontaylor
Copy link
Contributor

Also, there is a potential problem I just realized. What happens when the bulb is unavailable and automations try and control the bulbs? Do the automations outright fail, or does the lifx component soft fail and allow the rest of the automation to continue.

@keatontaylor
Copy link
Contributor

Also, we don't need to request an acknowledgement from the bulbs if we're not using it for anything. So on the aiolifx side we should be able to use fire and forget since the component is waiting a predefined interval, then checking the light state.

@amelchio
Copy link
Contributor Author

@keatontaylor Thank you for looking at this!

  1. My LIFX Color 1000 and LIFX White 800 consistently reappear as available in about 13 seconds. This should not be related to the update interval because the bulb broadcasts its state when booting up.
  2. I don't like turning off polling completely, there are too many error cases. So please show it to be a problem first.
  3. The polling is decided by the core scan_interval value so it can be configured for any interval you like. I believe the default is 15 seconds.
  4. I actually don't know, the available property is also from core, I just set the value.
  5. The ACK is used to retry packet sends. It also drives the unregister that sets lights unavailable.

@keatontaylor
Copy link
Contributor

@amelchio I believe I discovered why it is taking so long for re-registering after bulbs are inactive. I am running the docker HA image and the component needed to be set to listen to incoming broadcasts packets from the right subnet, but this isn't easily configurable. So those of us using the docker image will need that added.

@amelchio
Copy link
Contributor Author

@keatontaylor I admit ignorance on Docker so I must get it set up to test that. I assume that this is Docker on Linux? Also, how long is "so long"?

@arsaboo
Copy link
Contributor

arsaboo commented Mar 15, 2017

@keatontaylor I have been testing this PR for some time on RPi and it is significantly more responsive compared to the current Lifx component.

Copy link
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 already looks very good!

Just a few minor comments.


lifx_library.probe()
hass.loop.create_task(coro)
Copy link
Member

Choose a reason for hiding this comment

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

Please use hass.async_add_job (it does the same except that it allows us to only finish starting up when this task is done)

self.hass = hass
self.async_add_devices = async_add_devices

def register(self, device):
Copy link
Member

Choose a reason for hiding this comment

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

Please mark these classes with @callback since they are being called from inside the event loop. This will help readers of this code understand what is going on.

entity = self.entities[device.mac_addr]
_LOGGER.debug("%s register AGAIN", entity.ipaddr)
entity.available = True
entity.schedule_update_ha_state()
Copy link
Member

Choose a reason for hiding this comment

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

You are already in the event loop. Please use self.hass.async_add_job(entity.async_update_ha_state())

def poll(self, now):
"""Polling for the light."""
self.probe()
def ready(self, device, msg):
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 a @callback annotation.

def probe(self, address=None):
"""Probe the light."""
self._liffylights.probe(address)
def unregister(self, device):
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 a @callback annotation.

_LOGGER.debug("%s unregister", entity.ipaddr)
entity.available = False
entity.updated_event.set()
entity.schedule_update_ha_state()
Copy link
Member

Choose a reason for hiding this comment

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

You are already in the event loop. Please use self.hass.async_add_job(entity.async_update_ha_state())

def should_poll(self):
"""No polling needed for LIFX light."""
return False
def available(self):
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 being all this fancy, you can also just have an available property on the entity (defined in __init__). No need abstracting it in a setter.

def turn_off(self, **kwargs):
self.update_later(0)
if fade < BULB_LATENCY:
self.set_power(1)
Copy link
Member

Choose a reason for hiding this comment

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

For other reviewers:

This is not doing I/O in the event loop. Checked it, aiolifx will schedule a task on the event loop when we call this. https://github.com/frawau/aiolifx/blob/master/aiolifx/aiolifx34.py#L143

@amelchio
Copy link
Contributor Author

@balloob Thanks for the review and the intro text in particular, that made my day :-)

I now added the @callback and async_add_job that you requested, also in a few places where you didn't (hopefully that was correct).

But I cannot figure out how to get rid of the fancy @available.setter. The property getter is defined in class Entity and the property is set from (the class that I now renamed to) LIFXManager. Any simpler way that I tried ended up either not working, or producing lint.

@balloob
Copy link
Member

balloob commented Mar 16, 2017

Oh my bad about available. Sorry, let's keep it as is.

@balloob balloob merged commit 9ef084d into home-assistant:dev Mar 16, 2017
balloob pushed a commit to home-assistant/home-assistant.io that referenced this pull request Mar 16, 2017
Remove the broadcast configuration variable.
@balloob balloob mentioned this pull request Mar 24, 2017
@kbickar
Copy link
Contributor

kbickar commented Mar 26, 2017

After updating to the version with this commit, my LiFX bulb no longer appears. It worked flawlessly in the previous version

@amelchio
Copy link
Contributor Author

@kbickar I'm sorry to hear that but thanks for reporting it. Can you tell me a bit about your bulb and HA setup so we can locate the problem?

@kbickar
Copy link
Contributor

kbickar commented Mar 27, 2017

@amelchio I have a LIFX Original - 02d279 v2.1 and the bulb is defined in my main configuration file as:
light 2:
platform: lifx
host: 192.168.1.39

@amelchio
Copy link
Contributor Author

@kbickar Okay, because of the hostconfiguration I am guessing that you have multiple networks on the host. Is that right? As a test, are you able to open the file .homeassistant/deps/aiolifx/aiolifx.py and change the line

UDP_BROADCAST_IP = "255.255.255.255"

to

UDP_BROADCAST_IP = "192.168.1.255"

(or whatever your actual broadcast IP address is) and restart HA?

@keatontaylor
Copy link
Contributor

Experiencing a similar issue, not all bulbs are showing up when HA is restarted. Changed the broadcast as you suggested without success.

@amelchio
Copy link
Contributor Author

This change is a big rework, so some fallout was expected. I am, however, committed to fix these things up so the change ends up a net improvement.

It is probably best to file individual issues for the regressions that you experience so we can go through them one by one and see if they each have the same cause.

@arsaboo
Copy link
Contributor

arsaboo commented Mar 27, 2017

As a datapoint, it's working very well for me. Best to file individual bugs, so that these issues can be tracked.

@kbickar
Copy link
Contributor

kbickar commented Mar 27, 2017

Setting the broadcast IP did not help. Additionally, I was watching in wireshark and I don't see any traffic going to either the IP of the bulb (the bulb occasionally broadcasts itself though), or to the UDP port 56700. Are there any changes that might be needed in the configuration file?

@amelchio
Copy link
Contributor Author

@kbickar On UDP port 56700 you should at least see a broadcast every 180 seconds, probing for new bulbs. So if you do not see that, maybe the LIFX component is not even loaded. I am actually not sure how to verify that (I would just search the logfile for "lifx" and hope it has some relevant message).

For the configuration, host should be server (this is not new). But it only matters if you are actually running multiple IP networks?

Which OS are you on?

light 2:
  - platform: lifx
    server: 192.168.1.39

@kbickar
Copy link
Contributor

kbickar commented Mar 27, 2017

I have multiple network interfaces for VMs running on Windows 10.
I switched the server variable, but that also doesn't work because it's then trying to bind to the remote IP. Setting server to 0.0.0.0 lets it continue but then it gets "Not implemented" error in _make_datagram_transport.

Does asyncio support windows?

@amelchio
Copy link
Contributor Author

@kbickar oh my, it seems you are right. That will take a little while to sort out, so you may want to revert to the previous lifx.py for now.

@keatontaylor
Copy link
Contributor

Oddly my issue was fixed not by changing the broadcast, but instead by power cycling the bulbs. Not sure why, but I've experienced these same issues before with the LIFX iOS app.

@home-assistant home-assistant locked and limited conversation to collaborators Jul 17, 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.

Home Assistant Creates New Entities As Duplicate, But Same Light Bulb
9 participants