Skip to content

Add support for eight zone style remote commands #41

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion limitlessled/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from limitlessled.group.rgbw import RgbwGroup, RGBW, BRIDGE_LED
from limitlessled.group.wrgb import WrgbGroup, WRGB
from limitlessled.group.rgbww import RgbwwGroup, RGBWW
from limitlessled.group.eightzone import EightzoneGroup, EIGHTZONE
from limitlessled.group.white import WhiteGroup, WHITE
from limitlessled.group.dimmer import DimmerGroup, DIMMER

Expand Down Expand Up @@ -38,13 +39,15 @@ def group_factory(bridge, number, name, led_type):
:param bridge: Member of this bridge.
:param number: Group number (1-4).
:param name: Name of group.
:param led_type: Either `RGBW`, `WRGB`, `RGBWW`, `WHITE`, `DIMMER` or `BRIDGE_LED`.
:param led_type: Either `RGBW`, `WRGB`, `RGBWW`, `WHITE`, `EIGHTZONE`, `DIMMER` or `BRIDGE_LED`.
:returns: New group.
"""
if led_type in [RGBW, BRIDGE_LED]:
return RgbwGroup(bridge, number, name, led_type)
elif led_type == RGBWW:
return RgbwwGroup(bridge, number, name)
elif led_type == EIGHTZONE:
return EightzoneGroup(bridge, number, name)
elif led_type == WHITE:
return WhiteGroup(bridge, number, name)
elif led_type == DIMMER:
Expand Down
6 changes: 4 additions & 2 deletions limitlessled/group/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ def command_set_factory(bridge, group_number, led_type):
from limitlessled.group.commands.v6 import (
CommandSetBridgeLightV6, CommandSetWhiteV6,
CommandSetDimmerV6, CommandSetRgbwV6,
CommandSetRgbwwV6, CommandSetWrgbV6)
CommandSetRgbwwV6, CommandSetWrgbV6,
CommandSetEightzoneV6)

command_sets = [CommandSetWhiteLegacy, CommandSetRgbwLegacy,
CommandSetBridgeLightV6, CommandSetWhiteV6,
CommandSetDimmerV6, CommandSetRgbwV6,
CommandSetRgbwwV6, CommandSetWrgbV6]
CommandSetRgbwwV6, CommandSetWrgbV6,
CommandSetEightzoneV6]
try:
cls = next(cs for cs in command_sets if
bridge.version in cs.SUPPORTED_VERSIONS and
Expand Down
76 changes: 76 additions & 0 deletions limitlessled/group/commands/v6.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from limitlessled.group.rgbw import RGBW, BRIDGE_LED
from limitlessled.group.rgbww import RGBWW
from limitlessled.group.eightzone import EIGHTZONE
from limitlessled.group.wrgb import WRGB
from limitlessled.group.white import WHITE
from limitlessled.group.dimmer import DIMMER
Expand Down Expand Up @@ -542,3 +543,78 @@ def temperature(self, temperature):
:return: The command.
"""
return self._build_command(0x05, self.convert_temperature(temperature))

class CommandSetEightzoneV6(CommandSetV6):
""" Command set for Eight Zones led light connected to wifi bridge v6. """

SUPPORTED_LED_TYPES = [EIGHTZONE]
REMOTE_STYLE = 0x0A

def __init__(self, group_number):
"""
Initializes the command set.
:param group_number: The group number.
"""
super().__init__(group_number, self.REMOTE_STYLE)

def on(self):
"""
Build command for turning the led on.
:return: The command.
"""
return self._build_command(0x06, 0x01)

def off(self):
"""
Build command for turning the led off.
:return: The command.
"""
return self._build_command(0x06, 0x02)

def night_light(self):
"""
Build command for turning the led into night light mode.
:return: The command.
"""
return self._build_command(0x06, 0x64)

def white(self, temperature=1):
"""
Build command for turning the led into white mode.
:param: The temperature to set.
:return: The command.
"""
return self.temperature(temperature)

def hue(self, hue):
"""
Build command for setting the hue of the led.
:param hue: Value to set (0.0-1.0).
:return: The command.
"""
return self._build_command(0x01, self.convert_hue(hue))

def saturation(self, saturation):
"""
Build command for setting the saturation of the led.
:param saturation: Value to set (0.0-1.0).
:return: The command.
"""
return self._build_command(0x03, self.convert_saturation(saturation))

def brightness(self, brightness):
"""
Build command for setting the brightness of the led.
:param brightness: Value to set (0.0-1.0).
:return: The command.
"""
return self._build_command(0x04, self.convert_brightness(brightness))

def temperature(self, temperature):
"""
Build command for setting the temperature of the led.
:param temperature: Value to set (0.0-1.0).
:return: The command.
"""
return self._build_command(0x02, self.convert_temperature(temperature))

263 changes: 263 additions & 0 deletions limitlessled/group/eightzone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
""" 8 Zone LimitlessLED group. """

import math
import time

from limitlessled import Color, util
from limitlessled.group import Group, rate
from limitlessled.util import steps, hue_of_color, saturation_of_color, to_rgb


EIGHTZONE = 'eightzone'
RGB_WHITE = Color(255, 255, 255)


class EightzoneGroup(Group):
""" Eight Zone LimitlessLED group. """

def __init__(self, bridge, number, name):
""" Initialize Eight Zone group.

:param bridge: Associated bridge.
:param number: Group number (1-8).
:param name: Group name.
"""
super().__init__(bridge, number, name, EIGHTZONE)
self._saturation = 0
self._hue = 0
self._temperature = 0.5
self._color = RGB_WHITE

@property
def color(self):
""" Color property.

:returns: Color.
"""
return self._color

@color.setter
def color(self, color):
""" Set group color.

Color is set on a best-effort basis.

:param color: RGB color tuple.
"""
self._color = color
self.saturation = saturation_of_color(color)
if self.saturation != 0:
self.hue = hue_of_color(color)

def white(self):
""" Set color to white. """
self._color = RGB_WHITE
cmd = self.command_set.white(self.temperature)
self.send(cmd)

def night_light(self):
""" Set night light mode. """
cmd = self.command_set.night_light()
self.send(cmd)

@property
def brightness(self):
""" Brightness property.

:returns: Brightness.
"""
return self._brightness

@brightness.setter
def brightness(self, brightness):
""" Set the group brightness.

:param brightness: Brightness in decimal percent (0.0-1.0).
"""
if brightness < 0 or brightness > 1:
raise ValueError("Brightness must be a percentage "
"represented as decimal 0-1.0")
self._brightness = brightness
cmd = self.command_set.brightness(brightness)
self.send(cmd)

@property
def hue(self):
""" Hue property.

:returns: Hue.
"""
return self._hue

@hue.setter
def hue(self, hue):
""" Set the group hue.

:param hue: Hue in decimal percent (0.0-1.0).
"""
if hue < 0 or hue > 1:
raise ValueError("Hue must be a percentage "
"represented as decimal 0-1.0")
self._hue = hue
self._update_color()
cmd = self.command_set.hue(hue)
self.send(cmd)

@property
def saturation(self):
""" Saturation property.

:returns: Saturation.
"""
return self._saturation

@saturation.setter
def saturation(self, saturation):
""" Set the group saturation.

:param saturation: Saturation in decimal percent (0.0-1.0).
"""
if saturation < 0 or saturation > 1:
raise ValueError("Saturation must be a percentage "
"represented as decimal 0-1.0")
self._saturation = saturation
self._update_color()
if saturation == 0:
self.white()
else:
cmd = self.command_set.saturation(saturation)
self.send(cmd)

def _update_color(self):
""" Update the color property from hue and saturation values.
"""
self._color = to_rgb(self.hue, self.saturation)

@property
def temperature(self):
""" Temperature property.

:returns: Temperature (0.0-1.0)
"""
return self._temperature

@temperature.setter
def temperature(self, temperature):
""" Set the temperature.

:param temperature: Value to set (0.0-1.0).
"""
if temperature < 0 or temperature > 1:
raise ValueError("Temperature must be a percentage "
"represented as decimal 0-1.0")
self._temperature = temperature
cmd = self.command_set.temperature(temperature)
self.send(cmd)

def transition(self, duration,
color=None, brightness=None, temperature=None):
""" Transition wrapper.

Short-circuit transition as necessary.

:param duration: Time to transition.
:param color: Transition to this color.
:param brightness: Transition to this brightness.
:param temperature: Transition to this temperature.
"""
if color and temperature is not None:
raise ValueError("Cannot transition to color and temperature "
"simultaneously.")

# Transition to white immediately.
if color == RGB_WHITE:
self.white()
# Transition away from white immediately.
elif self.color == RGB_WHITE and color is not None:
self.color = color
# Transition immediately if duration is zero.
if duration == 0:
if brightness is not None:
self.brightness = brightness
if color:
self.color = color
if temperature is not None:
self.temperature = temperature
return
# Perform transition
if color and color != self.color:
self._transition(duration, brightness,
hue=hue_of_color(color),
saturation=saturation_of_color(color))
elif temperature != self.temperature:
self._transition(duration, brightness, temperature=temperature)
elif brightness != self.brightness:
self._transition(duration, brightness)

@rate(wait=0.025, reps=1)
def _transition(self, duration, brightness,
hue=None, saturation=None, temperature=None):
""" Transition.

:param duration: Time to transition.
:param brightness: Transition to this brightness.
:param hue: Transition to this hue.
:param saturation: Transition to this saturation.
:param temperature: Transition to this temperature.
"""
# Calculate brightness steps.
b_steps = 0
if brightness is not None:
b_steps = steps(self.brightness,
brightness, self.command_set.brightness_steps)
b_start = self.brightness
# Calculate hue steps.
h_steps = 0
if hue is not None:
h_steps = steps(self.hue,
hue, self.command_set.hue_steps)
h_start = self.hue
# Calculate saturation steps.
s_steps = 0
if saturation is not None:
s_steps = steps(self.saturation,
saturation, self.command_set.saturation_steps)
s_start = self.saturation
# Calculate temperature steps.
t_steps = 0
if temperature is not None:
t_steps = steps(self.temperature,
temperature, self.command_set.temperature_steps)
t_start = self.temperature
# Compute ideal step amount (at least one).
total_steps = max(b_steps, h_steps, s_steps, t_steps, 1)
total_commands = b_steps + h_steps + s_steps + t_steps
# Calculate wait.
wait = self._wait(duration, total_steps, total_commands)
# Scale down steps if no wait time.
if wait == 0:
scaled_steps = self._scale_steps(duration, total_commands, b_steps,
h_steps, s_steps, t_steps)
b_steps, h_steps, s_steps, t_steps = scaled_steps
total_steps = max(b_steps, h_steps, s_steps, t_steps, 1)
# Perform transition.
for i in range(total_steps):
# Brightness.
if b_steps > 0 and i % math.ceil(total_steps/b_steps) == 0:
self.brightness = util.transition(i, total_steps,
b_start, brightness)
# Hue.
if h_steps > 0 and i % math.ceil(total_steps/h_steps) == 0:
self.hue = util.transition(i, total_steps,
h_start, hue)
# Saturation.
if s_steps > 0 and i % math.ceil(total_steps/s_steps) == 0:
self.saturation = util.transition(i, total_steps,
s_start, saturation)
# Temperature.
if t_steps > 0 and i % math.ceil(total_steps/t_steps) == 0:
self.temperature = util.transition(i, total_steps,
t_start, temperature)

# Wait.
time.sleep(wait)