Skip to content

Commit

Permalink
simplify fade
Browse files Browse the repository at this point in the history
  • Loading branch information
jabdoa2 committed Mar 5, 2017
1 parent 80109d0 commit 5426788
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 163 deletions.
142 changes: 45 additions & 97 deletions mpf/devices/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class Light(SystemWideDevice):
machine = None

lights_to_update = set()
lights_to_fade = set()
_updater_task = None

@classmethod
Expand All @@ -52,7 +51,6 @@ def device_class_init(cls, machine: MachineController):
machine: MachineController which is used
"""
cls.machine = machine
cls.lights_to_fade = set()
cls.lights_to_update = set()

machine.validate_machine_config_section('light_settings')
Expand Down Expand Up @@ -107,19 +105,15 @@ def update_lights(cls, dt):
Args:
dt: time since last call
"""
for light in list(cls.lights_to_fade):
if light.fade_in_progress:
light.fade_task(dt)

# todo we could make a change here (or an option) so that it writes
# every light, every frame. That way they'd fix themselves if something
# got weird due to interference? Or is that a platform thing?
del dt

new_lights_to_update = set()
if cls.lights_to_update:
for light in cls.lights_to_update:
light.write_color_to_hw_driver()

cls.lights_to_update = set()
if light.fade_in_progress:
new_lights_to_update.add(light)
cls.lights_to_update = new_lights_to_update

@classmethod
def mode_stop(cls, mode: Mode):
Expand Down Expand Up @@ -375,6 +369,10 @@ def _add_to_stack(self, color, fade_ms, priority, key, mode):
self.debug_log("color: %s", new_color)
self.debug_log("key: %s", key)

self._schedule_update()

def _schedule_update(self):
self.fade_in_progress = self.stack and self.stack[0]['dest_time']
self.__class__.lights_to_update.add(self)

def clear_stack(self):
Expand All @@ -383,7 +381,7 @@ def clear_stack(self):

self.debug_log("Clearing Stack")

self.__class__.lights_to_update.add(self)
self._schedule_update()

def remove_from_stack_by_key(self, key):
"""Remove a group of color settings from the stack.
Expand All @@ -399,7 +397,8 @@ def remove_from_stack_by_key(self, key):
self.debug_log("Removing key '%s' from stack", key)

self.stack[:] = [x for x in self.stack if x['key'] != key]
self.__class__.lights_to_update.add(self)

self._schedule_update()

def remove_from_stack_by_mode(self, mode: Mode):
"""Remove a group of color settings from the stack.
Expand All @@ -414,22 +413,8 @@ def remove_from_stack_by_mode(self, mode: Mode):
self.debug_log("Removing mode '%s' from stack", mode)

self.stack[:] = [x for x in self.stack if x['mode'] != mode]
self.__class__.lights_to_update.add(self)

def get_color(self):
"""Return an RGBColor() instance of the 'color' setting of the highest color setting in the stack.
This is usually the same color as the
physical light, but not always (since physical lights are updated once per
frame, this value could vary.

Also note the color returned is the "raw" color that does has not had
the color correction profile applied.
"""
try:
return self.stack[0]['color']
except IndexError:
return RGBColor('off')
self._schedule_update()

def _get_priority_from_key(self, key):
try:
Expand All @@ -446,38 +431,24 @@ def write_color_to_hw_driver(self):
This method is automatically called whenever a color change has been
made (including when fades are active).
"""
# TODO: untangle fade and setting values. remove delay?
if not self.stack:
self.color('off')

# if there's a current fade, but the new command doesn't have one
if not self.stack[0]['dest_time'] and self.fade_in_progress:
self._stop_fade_task()

# If the new command has a fade, but the fade task isn't running
if self.stack[0]['dest_time'] and not self.fade_in_progress:
self._setup_fade()

# If there's no current fade and no new fade, or a current fade and new
# fade
else:
corrected_color = self.gamma_correct(self.stack[0]['color'])
corrected_color = self.color_correct(corrected_color)

self._color = list(self.stack[0]['color'])
self._corrected_color = corrected_color
self.debug_log("Writing color to hw driver: %s", corrected_color)

for color, hw_driver in self.hw_drivers.items():
# TODO: implement fade_ms here
fade_ms = 0
if color in ["red", "blue", "green"]:
hw_driver.set_brightness(getattr(corrected_color, color) / 255.0, fade_ms)
elif color == "white":
hw_driver.set_brightness(
min(corrected_color.red, corrected_color.green, corrected_color.blue) / 255.0, fade_ms)
else:
raise AssertionError("Invalid color {} in light {}".format(color, self.name))
color = self.get_color()
corrected_color = self.gamma_correct(color)
corrected_color = self.color_correct(corrected_color)

self._color = list(color)
self._corrected_color = corrected_color
self.debug_log("Writing color to hw driver: %s", corrected_color)

for color, hw_driver in self.hw_drivers.items():
# TODO: implement fade_ms here
fade_ms = 0
if color in ["red", "blue", "green"]:
hw_driver.set_brightness(getattr(corrected_color, color) / 255.0, fade_ms)
elif color == "white":
hw_driver.set_brightness(
min(corrected_color.red, corrected_color.green, corrected_color.blue) / 255.0, fade_ms)
else:
raise AssertionError("Invalid color {} in light {}".format(color, self.name))

def gamma_correct(self, color):
"""Apply max brightness correction to color.
Expand Down Expand Up @@ -546,30 +517,23 @@ def off(self, fade_ms=None, priority=0, key=None, **kwargs):
self.color(color=RGBColor(), fade_ms=fade_ms, priority=priority,
key=key)

def _setup_fade(self):
self.fade_in_progress = True

self.debug_log("Setting up the fade task")

self.__class__.lights_to_fade.add(self)
def get_color(self):
"""Return an RGBColor() instance of the 'color' setting of the highest color setting in the stack.
def fade_task(self, dt):
"""Perform a fade depending on the current time.
This is usually the same color as the physical light, but not always (since physical lights are updated once per
frame, this value could vary.
Args:
dt: time since last call
Also note the color returned is the "raw" color that does has not had the color correction profile applied.
"""
del dt

try:
color_settings = self.stack[0]
except IndexError:
self._stop_fade_task()
return
# no stack
return RGBColor('off')

# todo
# no fade
if not color_settings['dest_time']:
return
return color_settings['dest_color']

# figure out the ratio of how far along we are
try:
Expand All @@ -580,27 +544,11 @@ def fade_task(self, dt):
except ZeroDivisionError:
ratio = 1.0

self.debug_log("Fade task, ratio: %s", ratio)
self.debug_log("Fade, ratio: %s", ratio)

if ratio >= 1.0: # fade is done
self._end_fade()
color_settings['color'] = color_settings['dest_color']
self.fade_in_progress = False
color_settings['dest_time'] = 0
return color_settings['dest_color']
else:
color_settings['color'] = (
RGBColor.blend(color_settings['start_color'],
color_settings['dest_color'],
ratio))

self.__class__.lights_to_update.add(self)

def _end_fade(self):
# stops the fade and instantly sets the light to its destination color
self._stop_fade_task()
self.stack[0]['dest_time'] = 0

def _stop_fade_task(self):
# stops the fade task. Light is left in whatever state it was in
self.fade_in_progress = False
self.__class__.lights_to_fade.remove(self)

self.debug_log("Stopping fade task")
return RGBColor.blend(color_settings['start_color'], color_settings['dest_color'], ratio)
65 changes: 9 additions & 56 deletions mpf/tests/test_DeviceLED.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_color_and_stack(self):
self.assertEqual(color_setting['start_color'], RGBColor('off'))
self.assertEqual(color_setting['dest_time'], 0)
self.assertEqual(color_setting['dest_color'], RGBColor('red'))
self.assertEqual(color_setting['color'], RGBColor('red'))
self.assertEqual(led1.get_color(), RGBColor('red'))
self.assertIsNone(color_setting['key'])

# test get_color()
Expand All @@ -51,7 +51,7 @@ def test_color_and_stack(self):
self.assertEqual(color_setting['start_color'], RGBColor('red'))
self.assertEqual(color_setting['dest_time'], 0)
self.assertEqual(color_setting['dest_color'], RGBColor('blue'))
self.assertEqual(color_setting['color'], RGBColor('blue'))
self.assertEqual(led1.get_color(), RGBColor('blue'))
self.assertIsNone(color_setting['key'])
self.assertEqual(len(led1.stack), 1)

Expand All @@ -68,7 +68,7 @@ def test_color_and_stack(self):
self.assertEqual(color_setting['start_color'], RGBColor('blue'))
self.assertEqual(color_setting['dest_time'], 0)
self.assertEqual(color_setting['dest_color'], RGBColor('green'))
self.assertEqual(color_setting['color'], RGBColor('green'))
self.assertEqual(led1.get_color(), RGBColor('green'))
self.assertIsNone(color_setting['key'])

# set led1 orange, lower priority, but with a key, so led should stay
Expand All @@ -83,7 +83,7 @@ def test_color_and_stack(self):
self.assertEqual(color_setting['start_color'], RGBColor('blue'))
self.assertEqual(color_setting['dest_time'], 0)
self.assertEqual(color_setting['dest_color'], RGBColor('green'))
self.assertEqual(color_setting['color'], RGBColor('green'))
self.assertEqual(led1.get_color(), RGBColor('green'))
self.assertIsNone(color_setting['key'])

# remove the orange key from the stack
Expand Down Expand Up @@ -133,7 +133,7 @@ def test_fades(self):

self._synchronise_led_update()
led1.color('red', fade_ms=2000)
self.advance_time_and_run(0.02)
self.machine_run()

# check the stack before the fade starts
color_setting = led1.stack[0]
Expand All @@ -142,7 +142,7 @@ def test_fades(self):
self.assertEqual(color_setting['dest_time'],
color_setting['start_time'] + 2)
self.assertEqual(color_setting['dest_color'], RGBColor('red'))
self.assertEqual(color_setting['color'], RGBColor('off'))
self.assertEqual(led1.get_color(), RGBColor('off'))
self.assertIsNone(color_setting['key'])

# advance to half way through the fade
Expand All @@ -154,9 +154,9 @@ def test_fades(self):
self.assertEqual(color_setting['dest_time'],
color_setting['start_time'] + 2)
self.assertEqual(color_setting['dest_color'], RGBColor('red'))
self.assertEqual(color_setting['color'], RGBColor((128, 0, 0)))
self.assertEqual(led1.get_color(), RGBColor((127, 0, 0)))
self.assertIsNone(color_setting['key'])
self.assertLightColor("led1", [128, 0, 0])
self.assertLightColor("led1", [126, 0, 0])

# advance to after the fade is done
self.advance_time_and_run(2)
Expand All @@ -166,7 +166,7 @@ def test_fades(self):
self.assertEqual(color_setting['start_color'], RGBColor('off'))
self.assertEqual(color_setting['dest_time'], 0)
self.assertEqual(color_setting['dest_color'], RGBColor('red'))
self.assertEqual(color_setting['color'], RGBColor('red'))
self.assertEqual(led1.get_color(), RGBColor('red'))
self.assertIsNone(color_setting['key'])
self.assertLightColor("led1", "red")

Expand All @@ -180,53 +180,6 @@ def test_fades(self):
self.advance_time_and_run(.5)
self.assertLightColor("led4", [255, 255, 255])

def test_interrupted_fade(self):
led1 = self.machine.lights.led1

self._synchronise_led_update()
led1.color('red', fade_ms=2000)
self.advance_time_and_run(0.02)

# check the stack before the fade starts
color_setting = led1.stack[0]
self.assertEqual(color_setting['priority'], 0)
self.assertEqual(color_setting['start_color'], RGBColor('off'))
self.assertEqual(color_setting['dest_time'],
color_setting['start_time'] + 2)
self.assertEqual(color_setting['dest_color'], RGBColor('red'))
self.assertEqual(color_setting['color'], RGBColor('off'))
self.assertIsNone(color_setting['key'])

# advance to half way through the fade
self.advance_time_and_run(1)

self.assertTrue(led1.fade_in_progress)
self.assertEqual(color_setting['priority'], 0)
self.assertEqual(color_setting['start_color'], RGBColor('off'))
self.assertEqual(color_setting['dest_time'],
color_setting['start_time'] + 2)
self.assertEqual(color_setting['dest_color'], RGBColor('red'))
self.assertEqual(color_setting['color'], RGBColor((128, 0, 0)))
self.assertIsNone(color_setting['key'])
self.assertLightColor("led1", [128, 0, 0])

# kill the fade
led1._end_fade()

# advance to after the fade should have been done
self.advance_time_and_run(2)

# everything should still be the same as the last check (except
# dest_time should be 0)
self.assertFalse(led1.fade_in_progress)
self.assertEqual(color_setting['priority'], 0)
self.assertEqual(color_setting['start_color'], RGBColor('off'))
self.assertEqual(color_setting['dest_time'], 0)
self.assertEqual(color_setting['dest_color'], RGBColor('red'))
self.assertEqual(color_setting['color'], RGBColor((128, 0, 0)))
self.assertIsNone(color_setting['key'])
self.assertLightColor("led1", [128, 0, 0])

def test_restore_to_fade_in_progress(self):
led1 = self.machine.lights.led1

Expand Down
11 changes: 5 additions & 6 deletions mpf/tests/test_LedPlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ def test_led_player(self):
self.advance_time_and_run()

self.assertLightColor("led1", 'off')
self.assertEqual(0, self.machine.lights.led1.stack[0]['priority'])
self.assertFalse(self.machine.lights.led1.stack)

self.assertLightColor("led2", 'off')
self.assertEqual(0, self.machine.lights.led2.stack[0]['priority'])
self.assertFalse(self.machine.lights.led2.stack)

self.assertLightColor("led3", 'off')
self.assertEqual(0, self.machine.lights.led3.stack[0]['priority'])
self.assertFalse(self.machine.lights.led3.stack)

# test fades via express config with a few different options
self._synchronise_led_update()
Expand Down Expand Up @@ -180,9 +180,8 @@ def test_show_no_hold_leds(self):
self.advance_time_and_run()

# led should be off when show ends
self.assertEqual(RGBColor('off'),
self.machine.lights.led1.stack[0]['color'])
self.assertEqual(0, self.machine.lights.led1.stack[0]['priority'])
self.assertLightColor("led1", 'off')
self.assertFalse(self.machine.lights.led1.stack)

def test_show_same_priority(self):
# start show2, leds are red
Expand Down
Loading

0 comments on commit 5426788

Please sign in to comment.