-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
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
LIFX light effects #7145
Conversation
This will be useful for new methods that also have to find passed in colors.
@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): |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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.
This looks like it is using the lan protocol (rather than going through the lifx server) for the effects? |
@cmsimike That is correct. |
@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), |
There was a problem hiding this comment.
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 ] |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)), |
There was a problem hiding this comment.
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)), |
There was a problem hiding this comment.
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)), |
There was a problem hiding this comment.
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)), |
There was a problem hiding this comment.
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)), |
There was a problem hiding this comment.
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)
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(): |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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, \ |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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(): |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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, \ |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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
@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. |
* 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
Cherry-picked for 0.43 |
Add description of the light effects added in home-assistant/core#7145
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
oreffect
parameters oflight.turn_on
andlight.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
andparty
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
andlight.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-effectI 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
:Checklist:
If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
tox
run successfully.