import colorsys
import logging
from enum import Enum
from itertools import chain
from .utils import _clamp
_LOGGER = logging.getLogger(__name__)
class Action(Enum):
"""
The Flow action enumeration.
Use this as the ``action`` parameter in a flow, to specify what should
happen after the flow ends.
"""
recover = 0
stay = 1
off = 2
[docs]class Flow(object):
actions = Action
def __init__(self, count=0, action=Action.recover, transitions=None):
"""
A complete flow, consisting of one or multiple transitions.
Example:
>>> transitions = [RGBTransition(255, 0, 0), SleepTransition(400)]
>>> Flow(3, Flow.actions.recover, transitions)
:param int count: The number of times to run this flow (0 to run
forever).
:param action action: The action to take after the flow stops. Can be
``Flow.actions.recover`` to go back to the state
before the flow, ``Flow.actions.stay`` to stay at
the last state, and ``Flow.actions.off`` to turn
off.
:param list transitions: A list of :py:class:`FlowTransition
<yeelight.FlowTransition>` instances that
describe the flow transitions to perform.
"""
if transitions is None:
transitions = []
self.count = count
self.action = action
self.transitions = transitions
# Note, main depends on us, so we cannot import BulbException here.
if len(self.transitions) > 9:
_LOGGER.warning(
"The bulb seems to support up to 9 transitions. Your %s might fail."
% len(self.transitions)
)
@property
def expression(self):
"""
Return a YeeLight-compatible expression that implements this flow.
:rtype: list
"""
expr = chain.from_iterable(
transition.as_list() for transition in self.transitions
)
expr = ", ".join(str(value) for value in expr)
return expr
@property
def as_start_flow_params(self):
"""
Return a YeeLight start_cf compatible params.
:rtype: list
"""
return self.count * len(self.transitions), self.action.value, self.expression
class FlowTransition(object):
"""A single transition in the flow."""
def as_list(self):
"""
Return a YeeLight-compatible expression that implements this transition.
:rtype: list
"""
brightness = min(int(self.brightness), 100)
# Duration must be at least 50, otherwise there's an error.
return [max(50, self.duration), self._mode, self._value, brightness]
[docs]class RGBTransition(FlowTransition):
def __init__(self, red, green, blue, duration=300, brightness=100):
"""
An RGB transition.
:param int red: The value of red (0-255).
:param int green: The value of green (0-255).
:param int blue: The value of blue (0-255).
:param int duration: The duration of the effect, in milliseconds. The
minimum is 50.
:param int brightness: The brightness value to transition to (1-100).
"""
self.red = red
self.green = green
self.blue = blue
# The mode value the YeeLight protocol mandates.
self._mode = 1
self.duration = duration
self.brightness = brightness
@property
def _value(self):
"""The YeeLight-compatible value for this transition."""
red = _clamp(self.red, 0, 255)
green = _clamp(self.green, 0, 255)
blue = _clamp(self.blue, 0, 255)
return red * 65536 + green * 256 + blue
def __repr__(self):
return "<%s(%s,%s,%s) duration %s, brightness %s>" % (
self.__class__.__name__,
self.red,
self.green,
self.blue,
self.duration,
self.brightness,
)
[docs]class HSVTransition(FlowTransition):
def __init__(self, hue, saturation, duration=300, brightness=100):
"""
An HSV transition.
:param int hue: The color hue to transition to (0-359).
:param int saturation: The color saturation to transition to (0-100).
:param int duration: The duration of the effect, in milliseconds. The
minimum is 50.
:param int brightness: The brightness value to transition to (1-100).
"""
self.hue = hue
self.saturation = saturation
# The mode value the YeeLight protocol mandates.
self._mode = 1
self.duration = duration
self.brightness = brightness
@property
def _value(self):
"""The YeeLight-compatible value for this transition."""
hue = _clamp(self.hue, 0, 359) / 359.0
saturation = max(0, min(100, self.saturation)) / 100.0
red, green, blue = [
int(round(col * 255)) for col in colorsys.hsv_to_rgb(hue, saturation, 1)
]
return red * 65536 + green * 256 + blue
def __repr__(self):
return "<%s(%s,%s) duration %s, brightness %s>" % (
self.__class__.__name__,
self.hue,
self.saturation,
self.duration,
self.brightness,
)
[docs]class TemperatureTransition(FlowTransition):
def __init__(self, degrees, duration=300, brightness=100):
"""
A Color Temperature transition.
:param int degrees: The degrees to set the color temperature to
(1700-6500).
:param int duration: The duration of the effect, in milliseconds. The
minimum is 50.
:param int brightness: The brightness value to transition to (1-100).
"""
self.degrees = degrees
# The mode value the YeeLight protocol mandates.
self._mode = 2
self.duration = duration
self.brightness = _clamp(brightness, 1, 100)
@property
def _value(self):
"""The YeeLight-compatible value for this transition."""
return max(1700, min(6500, self.degrees))
def __repr__(self):
return "<%s(%sK) duration %s, brightness %s>" % (
self.__class__.__name__,
self.degrees,
self.duration,
self.brightness,
)
[docs]class SleepTransition(FlowTransition):
def __init__(self, duration=300):
"""
A Sleep transition.
:param int duration: The duration of the effect, in milliseconds. The
minimum is 50.
"""
# The mode value the YeeLight protocol mandates.
self._mode = 7
# Ignored by YeeLight.
self._value = 1
self.brightness = 2
self.duration = duration
def __repr__(self):
return "<%s: duration %s>" % (self.__class__.__name__, self.duration)