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

LIFX light effects #7145

Merged
merged 17 commits into from
Apr 21, 2017
Merged

LIFX light effects #7145

merged 17 commits into from
Apr 21, 2017

Conversation

amelchio
Copy link
Contributor

@amelchio amelchio commented Apr 17, 2017

Update for users: if you know how, please test this :)

Update for reviewers: the below description covers the first three commits. By quite popular demand I have now added a colorloop effect as well. That took some work, and the PR is now quite big. Let me know if you want some reading guidance.

Description:

This is my first stab at LIFX light effects support. These are intended as short-running effects that are useful for notifications.

Maybe the implementation is controversial, I don’t know :-)

I have chosen not to build on the flash or effect parameters of light.turn_on and light.turn_off because I think those calls are already too overloaded (I can elaborate if you like). Adding in different LIFX specific operations would just make matters even worse.

So I wanted a new service call, but not a generic one like light.effect_start.

The term "light effects" sounds like something collective. In reality, though, each light effect has its own distinct set of parameters. For example, potential blink, fireplace and party effects could have quite different options. Thus, I think it is best to make each effect have its own service call. This avoids the situation of having a bunch of parameters where an unpredictable subset will not make sense in any given situation. It also makes it easier for users to discover all effects and their parameters.

Consequently, even though the two initial effects do share the same parameter set, I have split them into two service calls: light.lifx_effect_breathe and light.lifx_effect_pulse. This matches the API chosen by LIFX, so there is some familiarity for LIFX users: https://api.developer.lifx.com/docs/breathe-effect and https://api.developer.lifx.com/docs/pulse-effect

I have added these service calls only for lifx because I think it is infeasible to implement them without hardware support.

Documentation will follow once the implementation settles down.

Example entry for configuration.yaml:

automation:
  - alias: Forgot to close window
    trigger:
      platform: state
      entity_id: binary_sensor.aeotec_zw112_door_window_sensor_6_sensor_1_0
      from: 'off'
      to: 'on'
      for:
        minutes: 30
    action:
      - service: light.lifx_effect_pulse
        data:
          entity_id: light.office, light.kitchen
          color_name: 'blue'
          brightness: 200
          period: 0.25
          cycles: 3

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.

This will be useful for new methods that also have to find passed in colors.
@mention-bot
Copy link

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

self.event = asyncio.Event()

@callback
def callback(self,device,message):

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.async_add_job(entity.async_update_ha_state())

class AwaitAioLIFX:

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

This encapsulates the callback and Event that aiolifx needs and thus avoids an
explosion of those when new calls are added.

The refresh_state is now generally useful, so move it into its own method.
These effects are useful as notifications. They mimic the breathe and pulse
effects from the LIFX HTTP API:

    https://api.developer.lifx.com/docs/breathe-effect
    https://api.developer.lifx.com/docs/pulse-effect

However, this implementation runs locally with the LIFX LAN protocol.
@amelchio amelchio changed the title [WIP] LIFX flash effects LIFX flash effects Apr 17, 2017
@cmsimike
Copy link
Contributor

This looks like it is using the lan protocol (rather than going through the lifx server) for the effects?

@amelchio
Copy link
Contributor Author

@cmsimike That is correct.

@cmsimike
Copy link
Contributor

@amelchio thank you!!

Now the color is "full saturation, no brightness". This avoids a lot of
temporary white when fading from the "no color" value and into a real color.

hsbk = [
int(65535/359*lhue),
int(random.uniform(0.8,1.0)*65535),

Choose a reason for hiding this comment

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

missing whitespace after ','

if not light.device:
self.lights.remove(light)
else:
light.effect_data = [ self, light.is_on, light.device.color ]

Choose a reason for hiding this comment

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

whitespace after '['
whitespace before ']'

data[ATTR_RGB_COLOR] = [
random.randint(100,255),
random.randint(100,255),
random.randint(100,255),

Choose a reason for hiding this comment

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

missing whitespace after ','

if service in (SERVICE_EFFECT_BREATHE, SERVICE_EFFECT_PULSE):
data[ATTR_RGB_COLOR] = [
random.randint(100,255),
random.randint(100,255),

Choose a reason for hiding this comment

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

missing whitespace after ','


if service in (SERVICE_EFFECT_BREATHE, SERVICE_EFFECT_PULSE):
data[ATTR_RGB_COLOR] = [
random.randint(100,255),

Choose a reason for hiding this comment

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

missing whitespace after ','

return True

@asyncio.coroutine

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

vol.Optional(ATTR_CHANGE, default=20.0): vol.All(vol.Coerce(float),
vol.Clamp(min=0, max=360)),
vol.Optional(ATTR_SPREAD, default=30.0): vol.All(vol.Coerce(float),
vol.Clamp(min=0, max=360)),

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

vol.Optional(ATTR_PERIOD, default=60.0): vol.All(vol.Coerce(float),
vol.Clamp(min=1)),
vol.Optional(ATTR_CHANGE, default=20.0): vol.All(vol.Coerce(float),
vol.Clamp(min=0, max=360)),

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

LIFX_EFFECT_COLORLOOP_SCHEMA = LIFX_EFFECT_SCHEMA.extend({
ATTR_BRIGHTNESS: vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)),
vol.Optional(ATTR_PERIOD, default=60.0): vol.All(vol.Coerce(float),
vol.Clamp(min=1)),

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

vol.Optional(ATTR_CHANGE, default=20.0): vol.All(vol.Coerce(float),
vol.Clamp(min=0, max=360)),
vol.Optional(ATTR_SPREAD, default=30.0): vol.All(vol.Coerce(float),
vol.Clamp(min=0, max=360)),

Choose a reason for hiding this comment

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

line too long (80 > 79 characters)

vol.Optional(ATTR_PERIOD, default=60.0): vol.All(vol.Coerce(float),
vol.Clamp(min=1)),
vol.Optional(ATTR_CHANGE, default=20.0): vol.All(vol.Coerce(float),
vol.Clamp(min=0, max=360)),

Choose a reason for hiding this comment

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

line too long (80 > 79 characters)

@balloob
Copy link
Member

balloob commented Apr 20, 2017

I would just create 1 file for the effects.

The name is actually the easiest way to identify a bulb so just using it
as a fallback was a bit odd.
This is a stopgap. When a bit more infrastructure is in place, the intention
is to turn the current hue some degrees. This will guarantee a flash color
that is both unlike the current color and unlike white.
We have to wait for the bulbs, so let us wait for all of them at once.
The colorloop effect is most impressive if run on many lights. Testing
this has revealed the need for an easy way to stop effects on all lights
and return to the initial state of each bulb. This new call does just that.

Calling turn_on/turn_off could also stop the effect but that would not
restore the initial state.
To fade nicely from power off, the breathe effect needs to keep an
unchanging hue. So give up on using a static start color and just find the
correct hue from the target color.

The colorloop effect can start from anything but we use a random color
just to keep things a little interesting during power on.
data[ATTR_BRIGHTNESS] = 255
yield from light.hass.services.async_call(DOMAIN, service, data)

def effect_list():

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1


hass.async_add_job(effect.async_perform(**data))

@asyncio.coroutine

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

descriptions.get(SERVICE_EFFECT_STOP),
schema=LIFX_EFFECT_STOP_SCHEMA)

@asyncio.coroutine

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1


if devices:
yield from start_effect(hass, devices, \
service.service, **service.data)

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

devices = list(lifx_manager.entities.values())

if devices:
yield from start_effect(hass, devices, \

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

vol.Optional(ATTR_POWER_ON, default=False): cv.boolean,
})

def setup(hass, lifx_manager):

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

data[ATTR_BRIGHTNESS] = 255
yield from light.hass.services.async_call(DOMAIN, service, data)

def effect_list():

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1


hass.async_add_job(effect.async_perform(**data))

@asyncio.coroutine

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

descriptions.get(SERVICE_EFFECT_STOP),
schema=LIFX_EFFECT_STOP_SCHEMA)

@asyncio.coroutine

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1


if devices:
yield from start_effect(hass, devices, \
service.service, **service.data)

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

devices = list(lifx_manager.entities.values())

if devices:
yield from start_effect(hass, devices, \

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

vol.Optional(ATTR_POWER_ON, default=False): cv.boolean,
})

def setup(hass, lifx_manager):

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

@amelchio amelchio changed the title LIFX flash effects LIFX light effects Apr 20, 2017
@amelchio
Copy link
Contributor Author

@balloob I have moved the effects into their own file now. It’s much better that way, thanks! I also did a little further tweaking so I think it is good to go now.

@balloob balloob added this to the 0.43 milestone Apr 21, 2017
@balloob balloob merged commit d4b0850 into home-assistant:dev Apr 21, 2017
balloob pushed a commit that referenced this pull request Apr 21, 2017
* Refactor into find_hsbk

This will be useful for new methods that also have to find passed in colors.

* Add AwaitAioLIFX

This encapsulates the callback and Event that aiolifx needs and thus avoids an
explosion of those when new calls are added.

The refresh_state is now generally useful, so move it into its own method.

* Initial effects support for LIFX

These effects are useful as notifications. They mimic the breathe and pulse
effects from the LIFX HTTP API:

    https://api.developer.lifx.com/docs/breathe-effect
    https://api.developer.lifx.com/docs/pulse-effect

However, this implementation runs locally with the LIFX LAN protocol.

* Saturate LIFX no color value

Now the color is "full saturation, no brightness". This avoids a lot of
temporary white when fading from the "no color" value and into a real color.

* Organize LIFX effects in classes

This is to move the setup/restore away from the actual effect, making it quite
simple to add additional effects.

* Stop running LIFX effects on conflicting service calls

Turning the light on/off or starting a new effect will now stop the running
effect.

* Present default LIFX effects as light.turn_on effects

This makes the effects (with default parameters) easily accessible from
the UI.

* Add LIFX colorloop effect

This cycles the HSV colors, so that is added as an internal way to set a
color.

* Move lifx to its own package and split effects into a separate file

* Always show LIFX light name in logs

The name is actually the easiest way to identify a bulb so just using it
as a fallback was a bit odd.

* Compact effect getter

* Always use full brightness for random flash color

This is a stopgap. When a bit more infrastructure is in place, the intention
is to turn the current hue some degrees. This will guarantee a flash color
that is both unlike the current color and unlike white.

* Clear effects concurrently

We have to wait for the bulbs, so let us wait for all of them at once.

* Add lifx_effect_stop

The colorloop effect is most impressive if run on many lights. Testing
this has revealed the need for an easy way to stop effects on all lights
and return to the initial state of each bulb. This new call does just that.

Calling turn_on/turn_off could also stop the effect but that would not
restore the initial state.

* Always calculate the initial effect color

To fade nicely from power off, the breathe effect needs to keep an
unchanging hue. So give up on using a static start color and just find the
correct hue from the target color.

The colorloop effect can start from anything but we use a random color
just to keep things a little interesting during power on.

* Fix lint

* Update .coveragerc
@balloob
Copy link
Member

balloob commented Apr 21, 2017

Cherry-picked for 0.43

@balloob balloob mentioned this pull request Apr 22, 2017
amelchio added a commit to home-assistant/home-assistant.io that referenced this pull request Apr 22, 2017
Add description of the light effects added in home-assistant/core#7145
@balloob balloob mentioned this pull request May 5, 2017
@home-assistant home-assistant locked and limited conversation to collaborators Aug 12, 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.

7 participants