def _expand_device_config(self, device_settings): # convert all colors to RGBColor device_settings = super()._expand_device_config(device_settings) color = device_settings['color'] if isinstance( color, str) and "(" not in color and color not in ("on", "stop"): # hack to keep compatibility for matrix_light values if len(color) == 1: color = "0" + color + "0" + color + "0" + color elif len(color) == 2: color = color + color + color device_settings['color'] = RGBColor(color) return device_settings
def gamma_correct(self, color): """Apply max brightness correction to color. Args: ---- color: The RGBColor() instance you want to have gamma applied. Returns an updated RGBColor() instance with gamma corrected. """ factor = self.machine.light_controller.brightness_factor if factor == 1.0: return color return RGBColor([int(x * factor) for x in color])
def color(self, color, fade_ms=None, priority=0, key=None, start_time=None): """Add or update a color entry in this light's stack. Calling this methods is how you tell this light what color you want it to be. Args: color: RGBColor() instance, or a string color name, hex value, or 3-integer list/tuple of colors. fade_ms: Int of the number of ms you want this light to fade to the color in. A value of 0 means it's instant. A value of None (the default) means that it will use this light's and/or the machine's default fade_ms setting. priority: Int value of the priority of these incoming settings. If this light has current settings in the stack at a higher priority, the settings you're adding here won't take effect. However they're still added to the stack, so if the higher priority settings are removed, then the next-highest apply. key: An arbitrary identifier (can be any immutable object) that's used to identify these settings for later removal. If any settings in the stack already have this key, those settings will be replaced with these new settings. """ if self._debug: self.debug_log( "Received color() command. color: %s, fade_ms: %s " "priority: %s, key: %s", color, fade_ms, priority, key) if isinstance(color, str) and color == "on": color = self.config['default_on_color'] elif not isinstance(color, RGBColor): color = RGBColor(color) if fade_ms is None: fade_ms = self.default_fade_ms if not start_time: start_time = self.machine.clock.get_time() color_changes = not self.stack or self.stack[ 0].priority <= priority or self.stack[0].dest_color is None self._add_to_stack(color, fade_ms, priority, key, start_time) if color_changes: self._schedule_update()
def gamma_correct(self, color): """Apply max brightness correction to color. Args: color: The RGBColor() instance you want to have gamma applied. Returns: An updated RGBColor() instance with gamma corrected. """ factor = self.machine.get_machine_var("brightness") if not factor: return color else: return RGBColor([int(x * factor) for x in color])
def test_show_in_step(self): self.start_game() # start_game() advances the time 1 sec, so by now we're already on # step 2 of the rainbow show self.assertEqual(list(RGBColor('orange').rgb), self.machine.leds.led_11.hw_driver.current_color) # make sure show is advancing on its own self.advance_time_and_run(1) self.assertEqual(list(RGBColor('yellow').rgb), self.machine.leds.led_11.hw_driver.current_color) # hit the shot, changes to show1 self.hit_and_release_switch("switch_11") self.advance_time_and_run(0.1) self.assertEqual(list(RGBColor('aliceblue').rgb), self.machine.leds.led_11.hw_driver.current_color) # make sure show is advancing on its own self.advance_time_and_run(1) self.assertEqual(list(RGBColor('antiquewhite').rgb), self.machine.leds.led_11.hw_driver.current_color)
def _light_color(self, light, instance_dict, full_context, color, fade_ms, priority): if color == "stop": self._light_remove(light, instance_dict, full_context, fade_ms) return if color != "on": # hack to keep compatibility for matrix_light values if len(color) == 1: color = "0" + color + "0" + color + "0" + color elif len(color) == 2: color = color + color + color color = RGBColor(color) light.color(color, key=full_context, fade_ms=fade_ms, priority=priority) instance_dict[light.name] = light
def get_color_below(self, priority, key): """Return an RGBColor() instance of the 'color' setting of the highest color below a certain key. Similar to get_color. """ if not self.stack: return RGBColor("off") stack = [] for i, entry in enumerate(self.stack): if entry['priority'] <= priority and entry["key"] <= key: stack = self.stack[i:] break return self._get_color_and_fade(stack, 0)[0]
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 LED, but not always (since physical LEDs 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')
def test_single_step_show(self): # with single step shows, loops are automatically set to 0, hold is # automatically set to true self.machine.shows['show1'].play() self.advance_time_and_run() self.assertLightColor("led1", 'red') # when a show ends with hold, the final step of the show will cache # the led settings self.assertEqual(RGBColor('red'), self.machine.lights.led1.stack[0]['dest_color']) self.assertEqual(0, self.machine.lights.led1.stack[0]['priority'])
async def _initialize(self): """Initialise display.""" await super()._initialize() # load platform self.platform = self.machine.get_platform_sections( 'segment_displays', self.config['platform']) self.platform.assert_has_feature("segment_displays") if not self.platform.features[ 'allow_empty_numbers'] and self.config['number'] is None: self.raise_config_error("Segment Display must have a number.", 1) self.size = self.config['size'] self._default_color = [ RGBColor(color) for color in self.config["default_color"][0:self.size] ] if len(self._default_color) < self.size: self._default_color += [RGBColor("white") ] * (self.size - len(self._default_color)) # configure hardware try: self.hw_display = await self.platform.configure_segment_display( self.config['number'], self.size, self.config['platform_settings']) except AssertionError as ex: raise AssertionError( "Error in platform while configuring segment display {}. " "See error above.".format(self.name)) from ex text = SegmentDisplayText.from_str("", self.size, self.config['integrated_dots'], self.config['integrated_commas'], self._default_color) self._update_display( SegmentDisplayState(text, FlashingType.NO_FLASH, ''))
def test_brightness_correction(self): led = self.machine.lights["led1"] led.color(RGBColor((100, 100, 100))) self.advance_time_and_run(1) self.assertLightColor("led1", [100, 100, 100]) self.assertEqual(100 / 255.0, led.hw_drivers["red"][0].current_brightness) self.assertEqual(100 / 255.0, led.hw_drivers["green"][0].current_brightness) self.assertEqual(100 / 255.0, led.hw_drivers["blue"][0].current_brightness) self.machine.variables.set_machine_var("brightness", 0.8) led.color(RGBColor((100, 100, 100))) self.advance_time_and_run(1) self.assertLightColor("led1", [100, 100, 100]) self.assertEqual(80 / 255.0, led.hw_drivers["red"][0].current_brightness) self.assertEqual(80 / 255.0, led.hw_drivers["green"][0].current_brightness) self.assertEqual(80 / 255.0, led.hw_drivers["blue"][0].current_brightness)
def test_non_rgb_leds(self): # test bgr led = self.machine.leds.led2 led.color(RGBColor((11, 23, 42))) self.advance_time_and_run(1) self.assertEqual([42, 23, 11], led.hw_driver.current_color) # test rgbw led = self.machine.leds.led3 led.color(RGBColor((11, 23, 42))) self.advance_time_and_run(1) self.assertEqual([11, 23, 42, 11], led.hw_driver.current_color) # test w+- led = self.machine.leds.led5 led.color(RGBColor((100, 100, 100))) self.advance_time_and_run(1) self.assertEqual([100, 255, 0], led.hw_driver.current_color)
def gamma_correct(self, color): """Apply max brightness correction to color. Args: color: The RGBColor() instance you want to have gamma applied. Returns: An updated RGBColor() instance with gamma corrected. """ factor = self.machine.get_machine_var("brightness") # do not correct when there is no config or when using lights as channels (they are corrected on their own) if factor is None or not self.platform: return color else: return RGBColor([int(x * factor) for x in color])
async def _initialize(self): await super()._initialize() if self.config['previous']: await self.config['previous'].wait_for_loaded() start_channel = self.config['previous'].get_successor_number() self._load_hw_driver_sequentially(start_channel) elif self.config['start_channel']: self._load_hw_driver_sequentially(self.config['start_channel']) else: self._load_hw_drivers() self._drivers_loaded.set_result(True) self.config['default_on_color'] = RGBColor( self.config['default_on_color']) if self.config['color_correction_profile'] is not None: profile_name = self.config['color_correction_profile'] elif 'light_settings' in self.machine.config and \ self.machine.config['light_settings']['default_color_correction_profile'] is not None: profile_name = self.machine.config['light_settings'][ 'default_color_correction_profile'] else: profile_name = None if profile_name: if profile_name in self.machine.light_controller.light_color_correction_profiles: profile = self.machine.light_controller.light_color_correction_profiles[ profile_name] if profile is not None: self._set_color_correction_profile(profile) else: # pragma: no cover error = "Color correction profile '{}' was specified for light '{}'"\ " but the color correction profile does not exist."\ .format(profile_name, self.name) self.error_log(error) raise ValueError(error) if self.config['fade_ms'] is not None: self.default_fade_ms = self.config['fade_ms'] else: self.default_fade_ms = ( self.machine.config['light_settings']['default_fade_ms']) self.debug_log( "Initializing Light. CC Profile: %s, " "Default fade: %sms", self._color_correction_profile, self.default_fade_ms)
def __init__(self, machine, name): """Initialise light.""" self.hw_drivers = {} # type: Dict[str, LightPlatformInterface] self.hw_driver_functions = [] self.platforms = set() # type: Set[LightsPlatform] super().__init__(machine, name) self.machine.light_controller.initialise_light_subsystem() self.delay = DelayManager(self.machine) self.default_fade_ms = None self._off_color = RGBColor("off") self._color_correction_profile = None self.stack = list() # type: List[LightStackEntry] """A list of dicts which represents different commands that have come
def _test_leds_inverted(self): device = self.machine.lights["test_led_inverted"] self.pinproc.write_data = MagicMock(return_value=True) # test led on device.on() self.wait_for_platform() self.pinproc.write_data.assert_has_calls([ # first LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | 4), # low byte of address (4) call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (6 << 8)), # high byte of address (0) call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 0), # set color (0) # second LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 0), # set color (0) # third LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 0), # set color (0) ], False) self.pinproc.write_data = MagicMock(return_value=True) # test led off device.color("off") self.wait_for_platform() self.pinproc.write_data.assert_has_calls([ # first LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | 4), # low byte of address (4) call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (6 << 8)), # high byte of address (0) call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 255), # set color (255) # second LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 255), # set color (255) # third LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 255), # set color (255) ], False) self.pinproc.write_data = MagicMock(return_value=True) # test led color device.color(RGBColor((2, 23, 42))) self.wait_for_platform() self.pinproc.write_data.assert_has_calls([ # first LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | 4), # low byte of address (4) call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (6 << 8)), # high byte of address (0) call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 253), # set color (255 - 2) # second LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 232), # set color (255 - 23) # third LED call(3, 3072, 0x01000000 | (2 & 0x3F) << 16 | (1 << 8) | 213), # set color (255 - 42) ], False) self.pinproc.write_data = MagicMock(return_value=True)
def _convert_color(self, color, *, context=None) -> RGBColor: """Convert color to RGBColor.""" # hack to keep compatibility for matrix_light values if len(color) == 1: color = "0" + color + "0" + color + "0" + color elif len(color) == 2: color = color + color + color try: color = RGBColor(color) except ColorException as e: self.raise_config_error("Invalid color {}".format(color), 1, source_exception=e, context=context) return color
def _validate_type_color(self, item, validation_failure_info): if isinstance(item, tuple): if len(item) != 3: self.validation_error(item, validation_failure_info, "Color needs three components") return item # Validates colors by name, hex, or list, into a 3-item list, RGB, # with individual values from 0-255 color_string = str(item).lower() if color_string in NAMED_RGB_COLORS: return NAMED_RGB_COLORS[color_string] if Util.is_hex_string(color_string): return RGBColor.hex_to_rgb(color_string) color = Util.string_to_list(color_string) return int(color[0]), int(color[1]), int(color[2])
def _light_color(self, light, instance_dict, full_context, color, **s): if color == "stop": self._light_remove(light, instance_dict, full_context, s.get("fade_ms", None)) return if color == "on": color = light.config['default_on_color'] else: # hack to keep compatibility for matrix_light values if len(color) == 1: color = "0" + color + "0" + color + "0" + color elif len(color) == 2: color = color + color + color color = RGBColor(color) light.color(color, key=full_context, **s) instance_dict[light.name] = light
def assertLightColors(self, light_name, color_list, secs=1, check_delta=.1): colors = list() # have to do it this weird way because `if 'on' in color_list:` doesn't # work since it tries to convert it to a color for color in color_list[:]: if isinstance(color, str) and color.lower() == 'on': color_list.remove('on') color_list.append(self.machine.lights[light_name].config['default_on_color']) break for x in range(int(secs / check_delta)): color = self.machine.lights[light_name].get_color() colors.append(color) self.advance_time_and_run(check_delta) for color in color_list: self.assertIn(RGBColor(color), colors)
def _get_color_and_fade(self, stack, max_fade_ms: int, *, current_time=None) -> Tuple[RGBColor, int, bool]: try: color_settings = stack[0] except IndexError: # no stack return self._off_color, -1, True dest_color = color_settings.dest_color # no fade if not color_settings.dest_time: # if we are transparent just return the lower layer if dest_color is None: return self._get_color_and_fade(stack[1:], max_fade_ms) return dest_color, -1, True if current_time is None: current_time = self.machine.clock.get_time() # fade is done if current_time >= color_settings.dest_time: # if we are transparent just return the lower layer if dest_color is None: return self._get_color_and_fade(stack[1:], max_fade_ms) return color_settings.dest_color, -1, True if dest_color is None: dest_color, lower_fade_ms, _ = self._get_color_and_fade(stack[1:], max_fade_ms) if lower_fade_ms > 0: max_fade_ms = lower_fade_ms target_time = current_time + (max_fade_ms / 1000.0) # check if fade will be done before max_fade_ms if target_time > color_settings.dest_time: return dest_color, int((color_settings.dest_time - current_time) * 1000), True # figure out the ratio of how far along we are try: ratio = ((target_time - color_settings.start_time) / (color_settings.dest_time - color_settings.start_time)) except ZeroDivisionError: ratio = 1.0 return RGBColor.blend(color_settings.start_color, dest_color, ratio), max_fade_ms, False
def test_show_in_higher_profile(self): self.start_game() # make sure show is running from base config self.assertEqual(list(RGBColor('orange').rgb), self.machine.leds.led_23.hw_driver.current_color) # advance to make sure show is running self.advance_time_and_run() self.assertEqual(list(RGBColor('yellow').rgb), self.machine.leds.led_23.hw_driver.current_color) # start mode1, should flip to show 2 colors self.machine.modes.mode1.start() self.advance_time_and_run(0.02) self.assertEqual(list(RGBColor('aliceblue').rgb), self.machine.leds.led_23.hw_driver.current_color) # advance to make sure show is running self.advance_time_and_run(1) self.assertEqual(list(RGBColor('antiquewhite').rgb), self.machine.leds.led_23.hw_driver.current_color) self.advance_time_and_run(1) self.assertEqual(list(RGBColor('aquamarine').rgb), self.machine.leds.led_23.hw_driver.current_color) # stop the mode, make sure the show from the base is still running self.machine.modes.mode1.stop() self.advance_time_and_run(0.02) self.assertEqual(list(RGBColor('yellow').rgb), self.machine.leds.led_23.hw_driver.current_color) self.advance_time_and_run(1) self.assertEqual(list(RGBColor('green').rgb), self.machine.leds.led_23.hw_driver.current_color) self.advance_time_and_run(1) self.assertEqual(list(RGBColor('blue').rgb), self.machine.leds.led_23.hw_driver.current_color)
def get_color_below(self, priority, key): """Return an RGBColor() instance of the 'color' setting of the highest color below a certain key. Similar to get_color. """ if not self.stack: # no stack -> we are black return RGBColor("off") if self.stack[0].key == key and self.stack[0].priority == priority: # fast path for resetting the top element return self._get_color_and_fade(self.stack, 0)[0] stack = [] for i, entry in enumerate(self.stack): if entry.priority <= priority and entry.key <= key: stack = self.stack[i:] break return self._get_color_and_fade(stack, 0)[0]
def test_init_and_equal(self): black = RGBColor("black") color = RGBColor([1, 2, 3]) self.assertEqual((1, 2, 3), color.rgb) color2 = RGBColor((1, 2, 3)) color3 = RGBColor([1, 2, 3]) color4 = RGBColor(color) color5 = RGBColor("010203") color6 = RGBColor("010203") color7 = RGBColor("") self.assertEqual(color2, color) self.assertEqual(color3, color) self.assertEqual(color4, color) self.assertEqual(color5, color) self.assertEqual(color6, color) self.assertEqual(color7, black) self.assertNotEqual(black, color) self.assertNotEqual(black, "010203") self.assertEqual("(1, 2, 3)", str(color5))
def _test_rdb_led(self): self.advance_time_and_run() device = self.machine.leds.test_led self.assertEqual("000000", self.rgb_cpu.leds['97']) self.rgb_cpu.leds = {} # test led on device.on() self.advance_time_and_run(1) self.assertEqual("ffffff", self.rgb_cpu.leds['97']) # test led off device.off() self.advance_time_and_run(1) self.assertEqual("000000", self.rgb_cpu.leds['97']) # test led color device.color(RGBColor((2, 23, 42))) self.advance_time_and_run(1) self.assertEqual("02172a", self.rgb_cpu.leds['97'])
def color(self, color, fade_ms=None, priority=0, key=None, mode=None): """Add or update a color entry in this LED's stack, which is how you tell this LED what color you want it to be. Args: color: RGBColor() instance, or a string color name, hex value, or 3-integer list/tuple of colors. fade_ms: Int of the number of ms you want this LED to fade to the color in. A value of 0 means it's instant. A value of None (the default) means that it will use this LED's and/or the machine's default fade_ms setting. priority: Int value of the priority of these incoming settings. If this LED has current settings in the stack at a higher priority, the settings you're adding here won't take effect. However they're still added to the stack, so if the higher priority settings are removed, then the next-highest apply. key: An arbitrary identifier (can be any immutable object) that's used to identify these settings for later removal. If any settings in the stack already have this key, those settings will be replaced with these new settings. mode: Optional mode instance of the mode that is setting this color. When a mode ends, entries from the stack with that mode will automatically be removed. """ if self.debug: self.log.debug( "Received color() command. color: %s, fade_ms: %s" "priority: %s, key: %s", color, fade_ms, priority, key) if not isinstance(color, RGBColor): color = RGBColor(color) if fade_ms is None: fade_ms = self.default_fade_ms if priority < self._get_priority_from_key(key): if self.debug: self.log.debug("Incoming priority is lower than an existing " "stack item with the same key. Not adding to " "stack.") return self._add_to_stack(color, fade_ms, priority, key, mode)
def _initialize(self): yield from super()._initialize() self._load_hw_drivers() self.config['default_on_color'] = RGBColor( self.config['default_on_color']) if self.config['color_correction_profile'] is not None: profile_name = self.config['color_correction_profile'] elif 'light_settings' in self.machine.config and \ self.machine.config['light_settings']['default_color_correction_profile'] is not None: profile_name = self.machine.config['light_settings'][ 'default_color_correction_profile'] else: profile_name = None if profile_name: if profile_name in self.machine.light_controller.light_color_correction_profiles: profile = self.machine.light_controller.light_color_correction_profiles[ profile_name] if profile is not None: self._set_color_correction_profile(profile) else: # pragma: no cover error = "Color correction profile '{}' was specified for light '{}'"\ " but the color correction profile does not exist."\ .format(profile_name, self.name) self.error_log(error) raise ValueError(error) if self.config['fade_ms'] is not None: self.default_fade_ms = self.config['fade_ms'] else: self.default_fade_ms = ( self.machine.config['light_settings']['default_fade_ms']) self.debug_log( "Initializing Light. CC Profile: %s, " "Default fade: %sms", self._color_correction_profile, self.default_fade_ms)
def _get_color_and_target_time( self, stack) -> Tuple[RGBColor, int, RGBColor, int]: try: color_settings = stack[0] except IndexError: # no stack return self._off_color, -1, self._off_color, -1 dest_color = color_settings.dest_color dest_time = color_settings.dest_time # no fade if not dest_time: # if we are transparent just return the lower layer if dest_color is None: return self._get_color_and_target_time(stack[1:]) return dest_color, -1, dest_color, -1 # fade out if dest_color is None: _, _, lower_dest_color, lower_dest_time = self._get_color_and_target_time( stack[1:]) start_time = color_settings.start_time if lower_dest_time < 0: # no fade going on below current layer dest_color = lower_dest_color elif start_time < lower_dest_time < dest_time: # fade below is shorter than fade out. removing the fade will trigger a new fade in this case ratio = (dest_time - lower_dest_time) / (dest_time - start_time) dest_color = RGBColor.blend(color_settings.start_color, dest_color, ratio) dest_time = lower_dest_time else: # upper fade is longer. use color target below. this might be slightly inaccurate dest_color = lower_dest_color # return destination color and time return color_settings.start_color, color_settings.start_time, dest_color, dest_time
def test_no_show_when_disabled(self): shot20 = self.machine.shots.shot_20 self.start_game() # shot20 config has enable_events: none, so it should be disabled self.assertFalse(shot20.enabled) # make sure the show is not running and not affecting the LED self.assertEqual(list(RGBColor('off').rgb), self.machine.leds.led_20.hw_driver.current_color) # enable the shot, show should start shot20.enable() self.assertFalse( shot20.get_profile_by_key('mode', None)['settings']['show_when_disabled']) self.advance_time_and_run(.1) self.assertEqual(list(RGBColor('red').rgb), self.machine.leds.led_20.hw_driver.current_color) # make sure show is advancing self.advance_time_and_run(1) self.assertEqual(list(RGBColor('orange').rgb), self.machine.leds.led_20.hw_driver.current_color) # hit the shot, show should switch shot20.hit() self.advance_time_and_run(.1) self.assertEqual(list(RGBColor('aliceblue').rgb), self.machine.leds.led_20.hw_driver.current_color) # and that show should be running self.advance_time_and_run() self.assertEqual(list(RGBColor('antiquewhite').rgb), self.machine.leds.led_20.hw_driver.current_color) # disable the shot shot20.disable() self.advance_time_and_run() # LEDs should be off since show_when_disabled == false self.assertEqual(list(RGBColor('off').rgb), self.machine.leds.led_20.hw_driver.current_color)
def fade_task(self, dt): """Perform a fade depending on the current time. Args: dt: time since last call """ del dt try: color_settings = self.stack[0] except IndexError: self._stop_fade_task() return # todo if not color_settings['dest_time']: return # figure out the ratio of how far along we are try: ratio = ( (self.machine.clock.get_time() - color_settings['start_time']) / (color_settings['dest_time'] - color_settings['start_time'])) except ZeroDivisionError: ratio = 1.0 if self.debug: self.log.debug("Fade task, ratio: %s", ratio) if ratio >= 1.0: # fade is done self._end_fade() color_settings['color'] = color_settings['dest_color'] else: color_settings['color'] = (RGBColor.blend( color_settings['start_color'], color_settings['dest_color'], ratio)) Led.leds_to_update.add(self)