Repository: missionpinball/mpf

Issue:
Support multiple channels for the same color in lights
This will only use the last channel since there can be only one per color:
```
  l_white_led:
    number: 6-8
    type: www
```

Discussion:

Patch:
diff --git a/mpf/core/service_controller.py b/mpf/core/service_controller.py
index 694780a75..f4e53b5d3 100644
--- a/mpf/core/service_controller.py
+++ b/mpf/core/service_controller.py
@@ -105,7 +105,7 @@ class ServiceController(MpfController):
             raise AssertionError("Not in service mode!")
         light_map = []
         for light in self.machine.lights.values():
-            light_map.append(LightMap(next(iter(light.hw_drivers.values())).get_board_name(), light))
+            light_map.append(LightMap(next(iter(light.hw_drivers.values()))[0].get_board_name(), light))
 
         # sort by board + driver number
         light_map.sort(key=lambda x: (self._natural_key_sort(x[0]), self._natural_key_sort(str(x[1].config['number']))))
diff --git a/mpf/devices/light.py b/mpf/devices/light.py
index 3795b0a07..a2c5835aa 100644
--- a/mpf/devices/light.py
+++ b/mpf/devices/light.py
@@ -111,14 +111,15 @@ class Light(SystemWideDevice):
         del kwargs
         check_set = set()
         for light in machine.lights:
-            for driver in light.hw_drivers.values():
-                key = (light.platform, driver.number, type(driver))
-                if key in check_set:
-                    raise AssertionError(
-                        "Duplicate light number {} {} for light {}".format(
-                            type(driver), driver.number, light))
+            for drivers in light.hw_drivers.values():
+                for driver in drivers:
+                    key = (light.platform, driver.number, type(driver))
+                    if key in check_set:
+                        raise AssertionError(
+                            "Duplicate light number {} {} for light {}".format(
+                                type(driver), driver.number, light))
 
-                check_set.add(key)
+                    check_set.add(key)
 
     def _map_channels_to_colors(self, channel_list) -> dict:
         if self.config['type']:
@@ -143,20 +144,24 @@ class Light(SystemWideDevice):
         for color_name in color_channels:
             # red channel
             if color_name == 'r':
-                channels["red"] = channel_list.pop(0)
+                full_color_name = "red"
             # green channel
             elif color_name == 'g':
-                channels["green"] = channel_list.pop(0)
+                full_color_name = "green"
             # blue channel
             elif color_name == 'b':
-                channels["blue"] = channel_list.pop(0)
+                full_color_name = "blue"
             # simple white channel
             elif color_name == 'w':
-                channels["white"] = channel_list.pop(0)
+                full_color_name = "white"
             else:
                 raise AssertionError("Invalid element {} in type {} of light {}".format(
                     color_name, self.config['type'], self.name))
 
+            if full_color_name not in channels:
+                channels[full_color_name] = []
+            channels[full_color_name].append(channel_list.pop(0))
+
         return channels
 
     def _load_hw_drivers(self):
@@ -192,13 +197,19 @@ class Light(SystemWideDevice):
                                      format(self.name))
             # alternatively use channels from config
             channels = self.config['channels']
+            # ensure that we got lists
+            for channel in channels:
+                if not isinstance(channels[channel], list):
+                    channels[channel] = [channels[channel]]
 
         if not channels:
             raise AssertionError("Light {} has no channels.".format(self.name))
 
-        for color, channel in channels.items():
-            channel = self.machine.config_validator.validate_config("light_channels", channel)
-            self.hw_drivers[color] = self._load_hw_driver(channel)
+        for color, channel_list in channels.items():
+            self.hw_drivers[color] = []
+            for channel in channel_list:
+                channel = self.machine.config_validator.validate_config("light_channels", channel)
+                self.hw_drivers[color].append(self._load_hw_driver(channel))
 
     def _load_hw_driver(self, channel):
         """Load one channel."""
@@ -462,8 +473,9 @@ class Light(SystemWideDevice):
         self.stack[:] = [x for x in self.stack if x['key'] != key]
 
     def _schedule_update(self):
-        for color, hw_driver in self.hw_drivers.items():
-            hw_driver.set_fade(partial(self._get_brightness_and_fade, color=color))
+        for color, hw_drivers in self.hw_drivers.items():
+            for hw_driver in hw_drivers:
+                hw_driver.set_fade(partial(self._get_brightness_and_fade, color=color))
 
         for platform in self.platforms:
             platform.light_sync()


Test patch:
diff --git a/mpf/tests/MpfTestCase.py b/mpf/tests/MpfTestCase.py
index 7165cd661..8cb97203d 100644
--- a/mpf/tests/MpfTestCase.py
+++ b/mpf/tests/MpfTestCase.py
@@ -533,11 +533,11 @@ class MpfTestCase(unittest.TestCase):
         self.assertEqual(state, self.machine.switch_controller.is_active(name))
 
     def assertLightChannel(self, light_name, brightness, channel="white"):
-        self.assertAlmostEqual(brightness / 255.0, self.machine.lights[light_name].hw_drivers[channel].
+        self.assertAlmostEqual(brightness / 255.0, self.machine.lights[light_name].hw_drivers[channel][0].
                                current_brightness)
 
     def assertNotLightChannel(self, light_name, brightness, channel="white"):
-        self.assertNotEqual(brightness, self.machine.lights[light_name].hw_drivers[channel].
+        self.assertNotEqual(brightness, self.machine.lights[light_name].hw_drivers[channel][0].
                             current_brightness)
 
     def assertLightColor(self, light_name, color):
diff --git a/mpf/tests/machine_files/light/config/light.yaml b/mpf/tests/machine_files/light/config/light.yaml
index 8bb72e4b0..28bea61e7 100644
--- a/mpf/tests/machine_files/light/config/light.yaml
+++ b/mpf/tests/machine_files/light/config/light.yaml
@@ -32,13 +32,13 @@ lights:
   led3:
     channels:
       red:
-        number: 7
+        - number: 7
       green:
-        number: 8
+        - number: 8
       blue:
-        number: 9
+        - number: 9
       white:
-        number: 10
+        - number: 10
     debug: True
   led4:
     number: 11
@@ -46,3 +46,7 @@ lights:
   led_corrected:
     number:
     color_correction_profile: correction_profile_1
+  led_www:
+    number: 23
+    type: www
+    debug: True
diff --git a/mpf/tests/test_DeviceLight.py b/mpf/tests/test_DeviceLight.py
index 1476a3b5b..bc3d3246a 100644
--- a/mpf/tests/test_DeviceLight.py
+++ b/mpf/tests/test_DeviceLight.py
@@ -42,25 +42,25 @@ class TestDeviceLight(MpfTestCase):
         # corrected color
         self.assertEqual(RGBColor([210, 184, 159]), led.color_correct(led.get_color()))
         # check hardware
-        self.assertEqual(210 / 255.0, led.hw_drivers["red"].current_brightness)
-        self.assertEqual(184 / 255.0, led.hw_drivers["green"].current_brightness)
-        self.assertEqual(159 / 255.0, led.hw_drivers["blue"].current_brightness)
+        self.assertEqual(210 / 255.0, led.hw_drivers["red"][0].current_brightness)
+        self.assertEqual(184 / 255.0, led.hw_drivers["green"][0].current_brightness)
+        self.assertEqual(159 / 255.0, led.hw_drivers["blue"][0].current_brightness)
 
         led.color(RGBColor([128, 128, 128]))
         self.advance_time_and_run()
         self.assertLightColor("led1", [128, 128, 128])
         self.assertEqual(RGBColor([96, 83, 70]), led.color_correct(led.get_color()))
-        self.assertEqual(96 / 255.0, led.hw_drivers["red"].current_brightness)
-        self.assertEqual(83 / 255.0, led.hw_drivers["green"].current_brightness)
-        self.assertEqual(70 / 255.0, led.hw_drivers["blue"].current_brightness)
+        self.assertEqual(96 / 255.0, led.hw_drivers["red"][0].current_brightness)
+        self.assertEqual(83 / 255.0, led.hw_drivers["green"][0].current_brightness)
+        self.assertEqual(70 / 255.0, led.hw_drivers["blue"][0].current_brightness)
 
         led.color(RGBColor("black"))
         self.advance_time_and_run()
         self.assertLightColor("led1", [0, 0, 0])
         self.assertEqual(RGBColor([0, 0, 0]), led.color_correct(led.get_color()))
-        self.assertEqual(0 / 255.0, led.hw_drivers["red"].current_brightness)
-        self.assertEqual(0 / 255.0, led.hw_drivers["green"].current_brightness)
-        self.assertEqual(0 / 255.0, led.hw_drivers["blue"].current_brightness)
+        self.assertEqual(0 / 255.0, led.hw_drivers["red"][0].current_brightness)
+        self.assertEqual(0 / 255.0, led.hw_drivers["green"][0].current_brightness)
+        self.assertEqual(0 / 255.0, led.hw_drivers["blue"][0].current_brightness)
 
     def test_consecutive_fades(self):
         self.assertLightColor("led1", [0, 0, 0])
@@ -340,25 +340,25 @@ class TestDeviceLight(MpfTestCase):
         # corrected color
         self.assertEqual(RGBColor([210, 184, 159]), led.color_correct(led.get_color()))
         # check hardware
-        self.assertEqual(210 / 255.0, led.hw_drivers["red"].current_brightness)
-        self.assertEqual(184 / 255.0, led.hw_drivers["green"].current_brightness)
-        self.assertEqual(159 / 255.0, led.hw_drivers["blue"].current_brightness)
+        self.assertEqual(210 / 255.0, led.hw_drivers["red"][0].current_brightness)
+        self.assertEqual(184 / 255.0, led.hw_drivers["green"][0].current_brightness)
+        self.assertEqual(159 / 255.0, led.hw_drivers["blue"][0].current_brightness)
 
         led.color(RGBColor([128, 128, 128]))
         self.advance_time_and_run()
         self.assertLightColor("led_corrected", [128, 128, 128])
         self.assertEqual(RGBColor([96, 83, 70]), led.color_correct(led.get_color()))
-        self.assertEqual(96 / 255.0, led.hw_drivers["red"].current_brightness)
-        self.assertEqual(83 / 255.0, led.hw_drivers["green"].current_brightness)
-        self.assertEqual(70 / 255.0, led.hw_drivers["blue"].current_brightness)
+        self.assertEqual(96 / 255.0, led.hw_drivers["red"][0].current_brightness)
+        self.assertEqual(83 / 255.0, led.hw_drivers["green"][0].current_brightness)
+        self.assertEqual(70 / 255.0, led.hw_drivers["blue"][0].current_brightness)
 
         led.color(RGBColor("black"))
         self.advance_time_and_run()
         self.assertLightColor("led_corrected", [0, 0, 0])
         self.assertEqual(RGBColor([0, 0, 0]), led.color_correct(led.get_color()))
-        self.assertEqual(0 / 255.0, led.hw_drivers["red"].current_brightness)
-        self.assertEqual(0 / 255.0, led.hw_drivers["green"].current_brightness)
-        self.assertEqual(0 / 255.0, led.hw_drivers["blue"].current_brightness)
+        self.assertEqual(0 / 255.0, led.hw_drivers["red"][0].current_brightness)
+        self.assertEqual(0 / 255.0, led.hw_drivers["green"][0].current_brightness)
+        self.assertEqual(0 / 255.0, led.hw_drivers["blue"][0].current_brightness)
 
     def test_non_rgb_leds(self):
         # test bgr
@@ -366,22 +366,22 @@ class TestDeviceLight(MpfTestCase):
 
         led.color(RGBColor((11, 23, 42)))
         self.advance_time_and_run(1)
-        self.assertEqual(42 / 255, led.hw_drivers["blue"].current_brightness)
-        self.assertEqual('led-2', led.hw_drivers["blue"].number)
-        self.assertEqual(23 / 255, led.hw_drivers["green"].current_brightness)
-        self.assertEqual('led-3', led.hw_drivers["green"].number)
-        self.assertEqual(11 / 255, led.hw_drivers["red"].current_brightness)
-        self.assertEqual('led-4', led.hw_drivers["red"].number)
+        self.assertEqual(42 / 255, led.hw_drivers["blue"][0].current_brightness)
+        self.assertEqual('led-2', led.hw_drivers["blue"][0].number)
+        self.assertEqual(23 / 255, led.hw_drivers["green"][0].current_brightness)
+        self.assertEqual('led-3', led.hw_drivers["green"][0].number)
+        self.assertEqual(11 / 255, led.hw_drivers["red"][0].current_brightness)
+        self.assertEqual('led-4', led.hw_drivers["red"][0].number)
 
         led = self.machine.lights.led_bgr_2
         led.color(RGBColor((11, 23, 42)))
         self.advance_time_and_run(1)
-        self.assertEqual(42 / 255, led.hw_drivers["blue"].current_brightness)
-        self.assertEqual('led-42-r', led.hw_drivers["blue"].number)
-        self.assertEqual(23 / 255, led.hw_drivers["green"].current_brightness)
-        self.assertEqual('led-42-g', led.hw_drivers["green"].number)
-        self.assertEqual(11 / 255, led.hw_drivers["red"].current_brightness)
-        self.assertEqual('led-42-b', led.hw_drivers["red"].number)
+        self.assertEqual(42 / 255, led.hw_drivers["blue"][0].current_brightness)
+        self.assertEqual('led-42-r', led.hw_drivers["blue"][0].number)
+        self.assertEqual(23 / 255, led.hw_drivers["green"][0].current_brightness)
+        self.assertEqual('led-42-g', led.hw_drivers["green"][0].number)
+        self.assertEqual(11 / 255, led.hw_drivers["red"][0].current_brightness)
+        self.assertEqual('led-42-b', led.hw_drivers["red"][0].number)
 
         # test rgbw
         led = self.machine.lights.led3
@@ -389,8 +389,20 @@ class TestDeviceLight(MpfTestCase):
         led.color(RGBColor((11, 23, 42)))
         self.advance_time_and_run(1)
         self.assertLightColor("led2", [11, 23, 42])
-        self.assertEqual(11 / 255, led.hw_drivers["white"].current_brightness)
-        self.assertEqual('led-10', led.hw_drivers["white"].number)
+        self.assertEqual(11 / 255, led.hw_drivers["white"][0].current_brightness)
+        self.assertEqual('led-10', led.hw_drivers["white"][0].number)
+
+        # test www light
+        led = self.machine.lights.led_www
+        led.on(128)
+        self.advance_time_and_run(1)
+        self.assertLightColor("led_www", [128, 128, 128])
+        self.assertEqual(128 / 255, led.hw_drivers["white"][0].current_brightness)
+        self.assertEqual('led-23-r', led.hw_drivers["white"][0].number)
+        self.assertEqual(128 / 255, led.hw_drivers["white"][1].current_brightness)
+        self.assertEqual('led-23-g', led.hw_drivers["white"][1].number)
+        self.assertEqual(128 / 255, led.hw_drivers["white"][2].current_brightness)
+        self.assertEqual('led-23-b', led.hw_drivers["white"][2].number)
 
     def test_brightness_correction(self):
         led = self.machine.lights.led1
@@ -398,15 +410,15 @@ class TestDeviceLight(MpfTestCase):
         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"].current_brightness)
-        self.assertEqual(100 / 255.0, led.hw_drivers["green"].current_brightness)
-        self.assertEqual(100 / 255.0, led.hw_drivers["blue"].current_brightness)
+        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.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"].current_brightness)
-        self.assertEqual(80 / 255.0, led.hw_drivers["green"].current_brightness)
-        self.assertEqual(80 / 255.0, led.hw_drivers["blue"].current_brightness)
+        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)
diff --git a/mpf/tests/test_LightGroups.py b/mpf/tests/test_LightGroups.py
index 1a1649c8b..4d8d99404 100644
--- a/mpf/tests/test_LightGroups.py
+++ b/mpf/tests/test_LightGroups.py
@@ -22,28 +22,28 @@ class TestLightGroups(MpfTestCase):
 
     def test_config(self):
         # stripe 1
-        self.assertEqual("led-10-r", self.machine.lights["stripe1_light_10"].hw_drivers["red"].number)
+        self.assertEqual("led-10-r", self.machine.lights["stripe1_light_10"].hw_drivers["red"][0].number)
         self.assertListEqual(["test", "stripe1"], self.machine.lights["stripe1_light_10"].config['tags'])
-        self.assertEqual("led-11-r", self.machine.lights["stripe1_light_11"].hw_drivers["red"].number)
-        self.assertEqual("led-12-r", self.machine.lights["stripe1_light_12"].hw_drivers["red"].number)
-        self.assertEqual("led-13-r", self.machine.lights["stripe1_light_13"].hw_drivers["red"].number)
-        self.assertEqual("led-14-r", self.machine.lights["stripe1_light_14"].hw_drivers["red"].number)
+        self.assertEqual("led-11-r", self.machine.lights["stripe1_light_11"].hw_drivers["red"][0].number)
+        self.assertEqual("led-12-r", self.machine.lights["stripe1_light_12"].hw_drivers["red"][0].number)
+        self.assertEqual("led-13-r", self.machine.lights["stripe1_light_13"].hw_drivers["red"][0].number)
+        self.assertEqual("led-14-r", self.machine.lights["stripe1_light_14"].hw_drivers["red"][0].number)
         self.assertListEqual(["test", "stripe1"], self.machine.lights["stripe1_light_14"].config['tags'])
 
         # stripe 2
-        self.assertEqual("led-7-200-r", self.machine.lights["stripe2_light_200"].hw_drivers["red"].number)
+        self.assertEqual("led-7-200-r", self.machine.lights["stripe2_light_200"].hw_drivers["red"][0].number)
         self.assertEqual(10, self.machine.lights["stripe2_light_200"].config['x'])
         self.assertEqual(20, self.machine.lights["stripe2_light_200"].config['y'])
-        self.assertEqual("led-7-201-r", self.machine.lights["stripe2_light_201"].hw_drivers["red"].number)
+        self.assertEqual("led-7-201-r", self.machine.lights["stripe2_light_201"].hw_drivers["red"][0].number)
         self.assertEqual(15, self.machine.lights["stripe2_light_201"].config['x'])
         self.assertEqual(20, self.machine.lights["stripe2_light_201"].config['y'])
 
         # ring 1
-        self.assertEqual("led-20-r", self.machine.lights["ring1_light_20"].hw_drivers["red"].number)
-        self.assertEqual("led-21-r", self.machine.lights["ring1_light_21"].hw_drivers["red"].number)
-        self.assertEqual("led-22-r", self.machine.lights["ring1_light_22"].hw_drivers["red"].number)
-        self.assertEqual("led-23-r", self.machine.lights["ring1_light_23"].hw_drivers["red"].number)
-        self.assertEqual("led-24-r", self.machine.lights["ring1_light_24"].hw_drivers["red"].number)
+        self.assertEqual("led-20-r", self.machine.lights["ring1_light_20"].hw_drivers["red"][0].number)
+        self.assertEqual("led-21-r", self.machine.lights["ring1_light_21"].hw_drivers["red"][0].number)
+        self.assertEqual("led-22-r", self.machine.lights["ring1_light_22"].hw_drivers["red"][0].number)
+        self.assertEqual("led-23-r", self.machine.lights["ring1_light_23"].hw_drivers["red"][0].number)
+        self.assertEqual("led-24-r", self.machine.lights["ring1_light_24"].hw_drivers["red"][0].number)
         # 90 degree
         self.assertEqual(103, self.machine.lights["ring1_light_20"].config['x'])
         self.assertEqual(50, self.machine.lights["ring1_light_20"].config['y'])
diff --git a/mpf/tests/test_P3_Roc.py b/mpf/tests/test_P3_Roc.py
index 9b8b0bf93..30cec4ec7 100644
--- a/mpf/tests/test_P3_Roc.py
+++ b/mpf/tests/test_P3_Roc.py
@@ -739,78 +739,78 @@ class TestP3Roc(MpfTestCase):
         )
 
         # test enable of matrix light
-        assert not self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_patter.called
-        assert not self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_schedule.called
+        assert not self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_patter.called
+        assert not self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_schedule.called
         self.machine.lights.test_pdb_light.on()
         self.advance_time_and_run(.02)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_schedule.assert_called_with(
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_schedule.assert_called_with(
             cycle_seconds=0, schedule=4294967295, now=True, number=32
         )
 
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_patter = MagicMock()
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_patter = MagicMock()
         self.machine.lights.test_pdb_light.on(brightness=128)
         self.advance_time_and_run(.02)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_patter.assert_called_with(
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_patter.assert_called_with(
             32, 1, 1, 0, True
         )
 
         # test disable of matrix light
-        assert not self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_disable.called
+        assert not self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_disable.called
         self.machine.lights.test_pdb_light.off()
         self.advance_time_and_run(.02)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_disable.assert_called_with(32)
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_disable.assert_called_with(32)
 
     def _test_pdb_gi_light(self):
         # test gi on
         device = self.machine.lights.test_gi
         num = self.machine.coils.test_gi.hw_driver.number
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter = MagicMock()
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule = MagicMock()
         device.color("white")
         self.advance_time_and_run(.1)
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule.assert_has_calls([
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule.assert_has_calls([
             call(now=True, number=num, cycle_seconds=0, schedule=4294967295)])
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter = MagicMock()
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule = MagicMock()
 
         device.color([128, 128, 128])
         self.advance_time_and_run(.1)
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter.assert_has_calls([
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter.assert_has_calls([
             call(num, 1, 1, 0, True)])
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter = MagicMock()
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule = MagicMock()
 
         device.color([245, 245, 245])
         self.advance_time_and_run(.1)
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter.assert_has_calls([
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter.assert_has_calls([
             call(num, 19, 1, 0, True)])
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter = MagicMock()
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule = MagicMock()
 
         # test gi off
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_disable = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_disable = MagicMock()
         device.color("off")
         self.advance_time_and_run(.1)
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_disable.assert_has_calls([
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_disable.assert_has_calls([
             call(num)])
 
     def _test_leds(self):
         device = self.machine.lights.test_led
-        device.hw_drivers['red'].proc.led_color = MagicMock()
+        device.hw_drivers['red'][0].proc.led_color = MagicMock()
 
         # test led on
         device.on()
         self.advance_time_and_run(1)
-        device.hw_drivers['red'].proc.led_color.assert_has_calls([
+        device.hw_drivers['red'][0].proc.led_color.assert_has_calls([
             call(2, 1, 255),
             call(2, 2, 255),
             call(2, 3, 255)], True)
-        device.hw_drivers['red'].proc.proc.led_color = MagicMock()
+        device.hw_drivers['red'][0].proc.proc.led_color = MagicMock()
 
         # test led off
         device.off()
         self.advance_time_and_run(1)
-        device.hw_drivers['red'].proc.led_color.assert_has_calls([
+        device.hw_drivers['red'][0].proc.led_color.assert_has_calls([
             call(2, 1, 0),
             call(2, 2, 0),
             call(2, 3, 0)], True)
@@ -818,27 +818,27 @@ class TestP3Roc(MpfTestCase):
         # test led color
         device.color(RGBColor((2, 23, 42)))
         self.advance_time_and_run(1)
-        device.hw_drivers['red'].proc.led_color.assert_has_calls([
+        device.hw_drivers['red'][0].proc.led_color.assert_has_calls([
             call(2, 1, 2),
             call(2, 2, 23),
             call(2, 3, 42)], True)
 
     def _test_leds_inverted(self):
         device = self.machine.lights.test_led_inverted
-        device.hw_drivers['red'].proc.led_color = MagicMock()
+        device.hw_drivers['red'][0].proc.led_color = MagicMock()
         # test led on
         device.on()
         self.advance_time_and_run(1)
-        device.hw_drivers['red'].proc.led_color.assert_has_calls([
+        device.hw_drivers['red'][0].proc.led_color.assert_has_calls([
             call(2, 4, 0),
             call(2, 5, 0),
             call(2, 6, 0)], True)
-        device.hw_drivers['red'].proc.led_color = MagicMock()
+        device.hw_drivers['red'][0].proc.led_color = MagicMock()
 
         # test led off
         device.color("off")
         self.advance_time_and_run(1)
-        device.hw_drivers['red'].proc.led_color.assert_has_calls([
+        device.hw_drivers['red'][0].proc.led_color.assert_has_calls([
             call(2, 4, 255),
             call(2, 5, 255),
             call(2, 6, 255)], True)
@@ -846,7 +846,7 @@ class TestP3Roc(MpfTestCase):
         # test led color
         device.color(RGBColor((2, 23, 42)))
         self.advance_time_and_run(1)
-        device.hw_drivers['red'].proc.led_color.assert_has_calls([
+        device.hw_drivers['red'][0].proc.led_color.assert_has_calls([
             call(2, 4, 255 - 2),
             call(2, 5, 255 -23),
             call(2, 6, 255 - 42)], True)
diff --git a/mpf/tests/test_P_Roc.py b/mpf/tests/test_P_Roc.py
index 10be6e70f..a55016d2f 100644
--- a/mpf/tests/test_P_Roc.py
+++ b/mpf/tests/test_P_Roc.py
@@ -327,87 +327,87 @@ class TestPRoc(MpfTestCase):
         )
 
         # test enable of matrix light
-        assert not self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_patter.called
-        assert not self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_schedule.called
+        assert not self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_patter.called
+        assert not self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_schedule.called
         self.machine.lights.test_pdb_light.on()
         self.advance_time_and_run(.02)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_schedule.assert_called_with(
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_schedule.assert_called_with(
             cycle_seconds=0, schedule=4294967295, now=True, number=32
         )
 
         self.machine.lights.test_pdb_light.on(brightness=128)
         self.advance_time_and_run(.02)
 
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_patter.assert_called_with(
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_patter.assert_called_with(
             32, 1, 1, 0, True
         )
 
         # test disable of matrix light
-        assert not self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_disable.called
+        assert not self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_disable.called
         self.machine.lights.test_pdb_light.off()
         self.advance_time_and_run(.1)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_disable.assert_called_with(32)
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_disable.assert_called_with(32)
 
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_patter = MagicMock()
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_schedule = MagicMock()
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_disable = MagicMock()
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_patter = MagicMock()
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_schedule = MagicMock()
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_disable = MagicMock()
 
         self.post_event("play_test_show")
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_schedule.assert_called_with(
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_schedule.assert_called_with(
             cycle_seconds=0, schedule=4294967295, now=True, number=32
         )
 
         self.advance_time_and_run(1)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_patter.assert_called_with(
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_patter.assert_called_with(
             32, 3, 1, 0, True
         )
 
         self.advance_time_and_run(1)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_disable.assert_called_with(32)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_disable = MagicMock()
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_disable.assert_called_with(32)
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_disable = MagicMock()
 
         self.advance_time_and_run(1)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_schedule.assert_called_with(
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_schedule.assert_called_with(
             cycle_seconds=0, schedule=4294967295, now=True, number=32
         )
         self.advance_time_and_run(10)
-        self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_schedule.assert_called_with(
+        self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_schedule.assert_called_with(
             cycle_seconds=0, schedule=4294967295, now=True, number=32
         )
-        assert not self.machine.lights.test_pdb_light.hw_drivers["white"].proc.driver_disable.called
+        assert not self.machine.lights.test_pdb_light.hw_drivers["white"][0].proc.driver_disable.called
 
     def _test_pdb_gi_light(self):
         # test gi on
         device = self.machine.lights.test_gi
         num = self.machine.coils.test_gi.hw_driver.number
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter = MagicMock()
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule = MagicMock()
         device.color("white")
         self.advance_time_and_run(.1)
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule.assert_has_calls([
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule.assert_has_calls([
             call(now=True, number=num, cycle_seconds=0, schedule=4294967295)])
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter = MagicMock()
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule = MagicMock()
 
         device.color([128, 128, 128])
         self.advance_time_and_run(.1)
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter.assert_has_calls([
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter.assert_has_calls([
             call(num, 1, 1, 0, True)])
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter = MagicMock()
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule = MagicMock()
 
         device.color([245, 245, 245])
         self.advance_time_and_run(.1)
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter.assert_has_calls([
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter.assert_has_calls([
             call(num, 19, 1, 0, True)])
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_patter = MagicMock()
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_schedule = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_patter = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_schedule = MagicMock()
 
         # test gi off
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_disable = MagicMock()
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_disable = MagicMock()
         device.color("off")
         self.advance_time_and_run(.1)
-        device.hw_drivers["white"].driver.hw_driver.proc.driver_disable.assert_has_calls([
+        device.hw_drivers["white"][0].driver.hw_driver.proc.driver_disable.assert_has_calls([
             call(num)])
 
     def test_load_wpc(self):