Repository: XKNX/xknx
Pull request #508 (2020-11-24 19:04:41)

Light implementation to control lights using individual group addresses
Light implementation to control lights using individual group addresses for red, green, blue and white

<!--
  You are awesome! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template!.
-->
## Description
This PR adds a light implementation that makes it possible to control RGB and RGBW lights that have separate addresses for red, green, blue and white. I have made this a new class to separate it from the existing code, because the addresses needed are different and I thought it might be better to create new class instead of extending the existing class.
<!--
I would like to know if you would be interested in this feature before I put the time into adding this to the documentation and adding tests.
-->
<!--
Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
-->


Fixes # (issue)

## Type of change
<!--
Please tick the applicable options.
NOTE: Ticking multiple options most likely indicates
that your change is to big and it is suggested to split it into several smaller PRs.
-->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Checklist:

- [x] I have performed a self-review of my own code
- [x] The documentation has been adjusted accordingly
- [x] The changes generate no new warnings
- [x] Tests have been added that prove the fix is effective or that the feature works
- [x] The changes are documented in the changelog
- [x] The Homeassistant plugin has been adjusted in case of new config options
  
## example Homeassistant Configuration

```
xknx:
  light:
    - name: 'LED strip'
      individual_colors:
        white:
          address: '3/0/0'
          state_address: '3/3/0'
          brightness_address: '3/1/0'
          brightness_state_address: '3/3/1'
        green:
          address: '3/0/4'
          state_address: '3/3/4'
          brightness_address: '3/1/4'
          brightness_state_address: '3/3/5'
        blue:
          address: '3/0/6'
          state_address: '3/3/6'
          brightness_address: '3/1/6'
          brightness_state_address: '3/3/7'
        red:
          address: '3/0/2'
          state_address: '3/3/2'
          brightness_address: '3/1/2'
          brightness_state_address: '3/3/3'
```
it is also possible to switch all colors on/off with only one address
```
xknx:
  light:
    - name: 'LED strip'
      address: '3/0/0'
      individual_colors:
        white:
          state_address: '3/3/0'
          brightness_address: '3/1/0'
          brightness_state_address: '3/3/1'
        green:
          state_address: '3/3/4'
          brightness_address: '3/1/4'
          brightness_state_address: '3/3/5'
        blue:
          state_address: '3/3/6'
          brightness_address: '3/1/6'
          brightness_state_address: '3/3/7'
        red:
          state_address: '3/3/2'
          brightness_address: '3/1/2'
          brightness_state_address: '3/3/3'
``` 

coveralls: 
[![Coverage Status](https://coveralls.io/builds/35219781/badge)](https://coveralls.io/builds/35219781)

Coverage decreased (-1.2%) to 93.285% when pulling **caaa7dd4a55a87d165c100a45bd1ce617ea8392b on Lennart99:master** into **b4fdb2a2cf978c30c7a3efa1c772c7fa5b4c1478 on XKNX:master**.

farmio: Thanks, this would be a nice addition! 👍
I'll try to do a review later this week.
Lennart99: I believe this PR is ready for review now
Lennart99: I haved moved the functionality into the Light class and updated the config schemas.
I have tried to keep the schemas in the Homeassistant plugin and the library the same as much as possible.
Lennart99: I have updated the schemas to make it possible to omit the normal switch address when the switch addresses for the individual colors are used, it is also possible to use both at the same time. Some example configs are present in the *test\config_tests\resources\light* folder.

Patch:
diff --git a/changelog.md b/changelog.md
index 8d91c307..6dc88775 100644
--- a/changelog.md
+++ b/changelog.md
@@ -5,6 +5,7 @@
 ### Devices
 
 - Sensor: add DPT-3 datatypes "stepwise_dimming", "stepwise_blinds", "startstop_dimming", "startstop_blinds"
+- Light: It is now possible to control lights using individual group addresses for red, green, blue and white
 
 ### HA integration
 
diff --git a/docs/light.md b/docs/light.md
index 4ba00f2b..549d4495 100644
--- a/docs/light.md
+++ b/docs/light.md
@@ -27,6 +27,24 @@ The Light object is either a representation of a binary or dimm actor, LED-contr
 - `group_address_tunable_white_state` KNX group address for the current relative color temperature. *DPT 5.001*
 - `group_address_color_temperature` KNX group address to set absolute color temperature. *DPT 7.600*
 - `group_address_color_temperature_state` KNX group address for the current absolute color temperature. *DPT 7.600*
+
+- `group_address_switch_red` KNX group address to switch the red component. *DPT 1.001*
+- `group_address_switch_red_state` KNX group address for the state of the red component. *DPT 1.001*
+- `group_address_brightness_red` KNX group address to set the brightness of the red component. *DPT 5.001*
+- `group_address_brightness_red_state` KNX group address for the current brightness of the red component. *DPT 5.001*
+- `group_address_switch_green` KNX group address to switch the green component. *DPT 1.001*
+- `group_address_switch_green_state` KNX group address for the state of the green component. *DPT 1.001*
+- `group_address_brightness_green` KNX group address to set the brightness of the green component. *DPT 5.001*
+- `group_address_brightness_green_state` KNX group address for the current brightness of the green component. *DPT 5.001*
+- `group_address_switch_blue` KNX group address to switch the blue component. *DPT 1.001*
+- `group_address_switch_blue_state` KNX group address for the state of the blue component. *DPT 1.001*
+- `group_address_brightness_blue` KNX group address to set the brightness of the blue component. *DPT 5.001*
+- `group_address_brightness_blue_state` KNX group address for the current brightness of the blue component. *DPT 5.001*
+- `group_address_switch_white` KNX group address to switch the white component. *DPT 1.001*
+- `group_address_switch_white_state` KNX group address for the state of the white component. *DPT 1.001*
+- `group_address_brightness_white` KNX group address to set the brightness of the white component. *DPT 5.001*
+- `group_address_brightness_white_state` KNX group address for the current brightness of the white component. *DPT 5.001*
+
 - `min_kelvin` lowest possible color temperature in Kelvin. Default: 2700
 - `max_kelvin` hightest possible color temperature in Kelvin. Default: 6000
 - `device_updated_cb` awaitable callback for each update.
@@ -93,6 +111,60 @@ print(light.current_color_temperature)
 await light.sync()
 ```
 
+## [](#header-2)Example: RGBW light with individual group addresses for red, green, blue and white
+
+```python
+light = Light(xknx,
+              name='TestRGBWLight',
+              group_address_switch_red="1/1/1",
+              group_address_switch_red_state="1/1/2",
+              group_address_brightness_red="1/1/3",
+              group_address_brightness_red_state="1/1/4",
+              group_address_switch_green="1/1/5",
+              group_address_switch_green_state="1/1/6",
+              group_address_brightness_green="1/1/7",
+              group_address_brightness_green_state="1/1/8",
+              group_address_switch_blue="1/1/9",
+              group_address_switch_blue_state="1/1/10",
+              group_address_brightness_blue="1/1/11",
+              group_address_brightness_blue_state="1/1/12",
+              group_address_switch_white="1/1/13",
+              group_address_switch_white_state="1/1/14",
+              group_address_brightness_white="1/1/15",
+              group_address_brightness_white_state="1/1/16")
+
+# Switching light on
+await light.set_on()
+
+# Switching light off
+await light.set_off()
+
+# Set color
+await light.set_color((20, 70,200))
+
+# Set rgbw color
+await light.set_color((20,70,200), 30)
+
+
+# Accessing light via 'do'
+await light.do('on')
+await light.do('off')
+await light.do('brightness:80')
+
+# Accessing state
+print(light.state)
+print(light.supports_brightness)
+print(light.supports_color)
+print(light.supports_rgbw)
+print(light.current_color)
+print(light.supports_tunable_white)
+print(light.supports_color_temperature)
+
+# Requesting current state via KNX GroupValueRead for all _state addresses
+await light.sync()
+
+## [](#header-2)Configuration via **xknx.yaml**
+```
 ## [](#header-2)Configuration via **xknx.yaml**
 
 Lights are usually configured via [`xknx.yaml`](/configuration):
@@ -117,5 +189,28 @@ groups:
         # Light with color temperature in percent
         Living-Room.Light_TW:  {group_address_switch: '1/6/21', group_address_switch_state: '1/6/20', group_address_brightness: '1/6/22', group_address_brightness_state: '1/6/23', group_address_tunable_white: '1/6/24',  group_address_tunable_white_state: '1/6/25'}
 
+        # Light with RGBW color
+        Kitchen.Light_rgbw:
+            {
+                individual_colors:
+                    {
+                        white: {group_address_switch: "1/6/4", group_address_switch_state: "1/6/5", group_address_brightness: "1/6/6", group_address_brightness_state: "1/6/7"},
+                        red: {group_address_switch: "1/6/14", group_address_switch_state: "1/6/15", group_address_brightness: "1/6/16", group_address_brightness_state: "1/6/17"},
+                        green: {group_address_switch: "1/6/24", group_address_switch_state: "1/6/25", group_address_brightness: "1/6/26", group_address_brightness_state: "1/6/27"},
+                        blue: {group_address_switch: "1/6/34", group_address_switch_state: "1/6/35", group_address_brightness: "1/6/36", group_address_brightness_state: "1/6/37"}
+                    }
+            }
+
+        # Light with RGB color and no white
+        Kitchen.Light_rgb:
+            {
+                individual_colors:
+                    {
+                        red: {group_address_switch: "1/6/14", group_address_switch_state: "1/6/15", group_address_brightness: "1/6/16", group_address_brightness_state: "1/6/17"},
+                        green: {group_address_switch: "1/6/24", group_address_switch_state: "1/6/25", group_address_brightness: "1/6/26", group_address_brightness_state: "1/6/27"},
+                        blue: {group_address_switch: "1/6/34", group_address_switch_state: "1/6/35", group_address_brightness: "1/6/36", group_address_brightness_state: "1/6/37"}
+                    }
+                r
+            }
 ```
 
diff --git a/home-assistant-plugin/custom_components/xknx/factory.py b/home-assistant-plugin/custom_components/xknx/factory.py
index 9fe70bf3..cee6b67a 100644
--- a/home-assistant-plugin/custom_components/xknx/factory.py
+++ b/home-assistant-plugin/custom_components/xknx/factory.py
@@ -1,4 +1,6 @@
 """Factory function to initialize KNX devices from config."""
+from typing import Optional, Tuple
+
 from xknx import XKNX
 from xknx.devices import (
     BinarySensor as XknxBinarySensor,
@@ -86,8 +88,30 @@ def _create_cover(knx_module: XKNX, config: ConfigType) -> XknxCover:
     )
 
 
+def _create_light_color(
+    color: str, config: ConfigType
+) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
+    """Load color configuration from configuration structure."""
+    if "individual_colors" in config and color in config["individual_colors"]:
+        sub_config = config["individual_colors"][color]
+        group_address_switch = sub_config.get(CONF_ADDRESS)
+        group_address_switch_state = sub_config.get(LightSchema.CONF_STATE_ADDRESS)
+        group_address_brightness = sub_config.get(LightSchema.CONF_BRIGHTNESS_ADDRESS)
+        group_address_brightness_state = sub_config.get(
+            LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS
+        )
+        return (
+            group_address_switch,
+            group_address_switch_state,
+            group_address_brightness,
+            group_address_brightness_state,
+        )
+    return None, None, None, None
+
+
 def _create_light(knx_module: XKNX, config: ConfigType) -> XknxLight:
     """Return a KNX Light device to be used within XKNX."""
+
     group_address_tunable_white = None
     group_address_tunable_white_state = None
     group_address_color_temp = None
@@ -103,10 +127,35 @@ def _create_light(knx_module: XKNX, config: ConfigType) -> XknxLight:
             LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS
         )
 
+    (
+        red_switch,
+        red_switch_state,
+        red_brightness,
+        red_brightness_state,
+    ) = _create_light_color(LightSchema.CONF_RED, config)
+    (
+        green_switch,
+        green_switch_state,
+        green_brightness,
+        green_brightness_state,
+    ) = _create_light_color(LightSchema.CONF_GREEN, config)
+    (
+        blue_switch,
+        blue_switch_state,
+        blue_brightness,
+        blue_brightness_state,
+    ) = _create_light_color(LightSchema.CONF_BLUE, config)
+    (
+        white_switch,
+        white_switch_state,
+        white_brightness,
+        white_brightness_state,
+    ) = _create_light_color(LightSchema.CONF_WHITE, config)
+
     return XknxLight(
         knx_module,
         name=config[CONF_NAME],
-        group_address_switch=config[CONF_ADDRESS],
+        group_address_switch=config.get(CONF_ADDRESS),
         group_address_switch_state=config.get(LightSchema.CONF_STATE_ADDRESS),
         group_address_brightness=config.get(LightSchema.CONF_BRIGHTNESS_ADDRESS),
         group_address_brightness_state=config.get(
@@ -120,6 +169,22 @@ def _create_light(knx_module: XKNX, config: ConfigType) -> XknxLight:
         group_address_tunable_white_state=group_address_tunable_white_state,
         group_address_color_temperature=group_address_color_temp,
         group_address_color_temperature_state=group_address_color_temp_state,
+        group_address_switch_red=red_switch,
+        group_address_switch_red_state=red_switch_state,
+        group_address_brightness_red=red_brightness,
+        group_address_brightness_red_state=red_brightness_state,
+        group_address_switch_green=green_switch,
+        group_address_switch_green_state=green_switch_state,
+        group_address_brightness_green=green_brightness,
+        group_address_brightness_green_state=green_brightness_state,
+        group_address_switch_blue=blue_switch,
+        group_address_switch_blue_state=blue_switch_state,
+        group_address_brightness_blue=blue_brightness,
+        group_address_brightness_blue_state=blue_brightness_state,
+        group_address_switch_white=white_switch,
+        group_address_switch_white_state=white_switch_state,
+        group_address_brightness_white=white_brightness,
+        group_address_brightness_white_state=white_brightness_state,
         min_kelvin=config[LightSchema.CONF_MIN_KELVIN],
         max_kelvin=config[LightSchema.CONF_MAX_KELVIN],
     )
diff --git a/home-assistant-plugin/custom_components/xknx/schema.py b/home-assistant-plugin/custom_components/xknx/schema.py
index 54fd3f1c..230f6aca 100644
--- a/home-assistant-plugin/custom_components/xknx/schema.py
+++ b/home-assistant-plugin/custom_components/xknx/schema.py
@@ -139,29 +139,70 @@ class LightSchema:
     DEFAULT_MIN_KELVIN = 2700  # 370 mireds
     DEFAULT_MAX_KELVIN = 6000  # 166 mireds
 
-    SCHEMA = vol.Schema(
+    CONF_INDIVIDUAL_COLORS = "individual_colors"
+    CONF_RED = "red"
+    CONF_GREEN = "green"
+    CONF_BLUE = "blue"
+    CONF_WHITE = "white"
+
+    COLOR_SCHEMA = vol.Schema(
         {
-            vol.Required(CONF_ADDRESS): cv.string,
-            vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+            vol.Optional(CONF_ADDRESS): cv.string,
             vol.Optional(CONF_STATE_ADDRESS): cv.string,
-            vol.Optional(CONF_BRIGHTNESS_ADDRESS): cv.string,
+            vol.Required(CONF_BRIGHTNESS_ADDRESS): cv.string,
             vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): cv.string,
-            vol.Optional(CONF_COLOR_ADDRESS): cv.string,
-            vol.Optional(CONF_COLOR_STATE_ADDRESS): cv.string,
-            vol.Optional(CONF_COLOR_TEMP_ADDRESS): cv.string,
-            vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): cv.string,
-            vol.Optional(
-                CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE
-            ): cv.enum(ColorTempModes),
-            vol.Optional(CONF_RGBW_ADDRESS): cv.string,
-            vol.Optional(CONF_RGBW_STATE_ADDRESS): cv.string,
-            vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All(
-                vol.Coerce(int), vol.Range(min=1)
+        }
+    )
+
+    SCHEMA = vol.All(
+        vol.Schema(
+            {
+                vol.Optional(CONF_ADDRESS): cv.string,
+                vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+                vol.Optional(CONF_STATE_ADDRESS): cv.string,
+                vol.Optional(CONF_BRIGHTNESS_ADDRESS): cv.string,
+                vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): cv.string,
+                vol.Exclusive(CONF_INDIVIDUAL_COLORS, "color"): {
+                    vol.Inclusive(CONF_RED, "colors"): COLOR_SCHEMA,
+                    vol.Inclusive(CONF_GREEN, "colors"): COLOR_SCHEMA,
+                    vol.Inclusive(CONF_BLUE, "colors"): COLOR_SCHEMA,
+                    vol.Optional(CONF_WHITE): COLOR_SCHEMA,
+                },
+                vol.Exclusive(CONF_COLOR_ADDRESS, "color"): cv.string,
+                vol.Optional(CONF_COLOR_STATE_ADDRESS): cv.string,
+                vol.Optional(CONF_COLOR_TEMP_ADDRESS): cv.string,
+                vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): cv.string,
+                vol.Optional(
+                    CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE
+                ): cv.enum(ColorTempModes),
+                vol.Exclusive(CONF_RGBW_ADDRESS, "color"): cv.string,
+                vol.Optional(CONF_RGBW_STATE_ADDRESS): cv.string,
+                vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All(
+                    vol.Coerce(int), vol.Range(min=1)
+                ),
+                vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All(
+                    vol.Coerce(int), vol.Range(min=1)
+                ),
+            }
+        ),
+        vol.Any(
+            vol.Schema(
+                {
+                    vol.Required(CONF_INDIVIDUAL_COLORS): {
+                        vol.Required(CONF_RED): {vol.Required(CONF_ADDRESS): object},
+                        vol.Required(CONF_GREEN): {vol.Required(CONF_ADDRESS): object},
+                        vol.Required(CONF_BLUE): {vol.Required(CONF_ADDRESS): object},
+                    },
+                },
+                extra=vol.ALLOW_EXTRA,
             ),
-            vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All(
-                vol.Coerce(int), vol.Range(min=1)
+            vol.Schema(
+                {
+                    vol.Required(CONF_ADDRESS): object,
+                },
+                extra=vol.ALLOW_EXTRA,
             ),
-        }
+        ),
     )
 
 
diff --git a/xknx.yaml b/xknx.yaml
index 18dd1142..a960c046 100644
--- a/xknx.yaml
+++ b/xknx.yaml
@@ -157,6 +157,41 @@ groups:
                 group_address_color: "1/6/7",
                 group_address_color_state: "1/6/8",
             }
+        # Light with color
+        Diningroom.Light_rgb:
+            {
+                individual_colors:
+                    {
+                        white:
+                            {
+                                group_address_switch: "1/6/4",
+                                group_address_switch_state: "1/6/5",
+                                group_address_brightness: "1/6/6",
+                                group_address_brightness_state: "1/6/7",
+                            },
+                        red:
+                            {
+                                group_address_switch: "1/6/14",
+                                group_address_switch_state: "1/6/15",
+                                group_address_brightness: "1/6/16",
+                                group_address_brightness_state: "1/6/17",
+                            },
+                        green:
+                            {
+                                group_address_switch: "1/6/24",
+                                group_address_switch_state: "1/6/25",
+                                group_address_brightness: "1/6/26",
+                                group_address_brightness_state: "1/6/27",
+                            },
+                        blue:
+                            {
+                                group_address_switch: "1/6/34",
+                                group_address_switch_state: "1/6/35",
+                                group_address_brightness: "1/6/36",
+                                group_address_brightness_state: "1/6/37",
+                            }
+                    }
+            }
         # Light with color temperature
         Living-Room.Light_CT:
             {
diff --git a/xknx/config/config_v1.py b/xknx/config/config_v1.py
index be3f36bb..02b9dfd2 100644
--- a/xknx/config/config_v1.py
+++ b/xknx/config/config_v1.py
@@ -23,6 +23,7 @@ from xknx.devices import (
 )
 from xknx.exceptions import XKNXException
 from xknx.io import ConnectionConfig, ConnectionType
+from xknx.telegram import IndividualAddress
 
 logger = logging.getLogger("xknx.log")
 
@@ -51,7 +52,7 @@ class ConfigV1:
         """Parse the general section of xknx.yaml."""
         if "general" in doc:
             if "own_address" in doc["general"]:
-                self.xknx.own_address = doc["general"]["own_address"]
+                self.xknx.own_address = IndividualAddress(doc["general"]["own_address"])
             if "rate_limit" in doc["general"]:
                 self.xknx.rate_limit = doc["general"]["rate_limit"]
             if "multicast_group" in doc["general"]:
diff --git a/xknx/config/schema.py b/xknx/config/schema.py
index 5cc6ef65..c49fd032 100644
--- a/xknx/config/schema.py
+++ b/xknx/config/schema.py
@@ -147,46 +147,99 @@ class LightSchema:
     CONF_RGBW = "rgbw"
     CONF_COLOR_TEMPERATURE = "color_temperature"
     CONF_COLOR = "color"
+    CONF_INDIVIDUAL_COLORS = "individual_colors"
 
     CONF_COLOR_TEMP_MODE = "mode"
     CONF_MIN_KELVIN = "min_kelvin"
     CONF_MAX_KELVIN = "max_kelvin"
 
+    CONF_RED = "red"
+    CONF_GREEN = "green"
+    CONF_BLUE = "blue"
+    CONF_WHITE = "white"
+
     DEFAULT_COLOR_TEMP_MODE = "absolute"
     DEFAULT_MIN_KELVIN = 2700  # 370 mireds
     DEFAULT_MAX_KELVIN = 6000  # 166 mireds
 
-    SCHEMA = BaseDeviceSchema.SCHEMA.extend(
+    COLOR_SCHEMA = vol.Schema(
         {
-            vol.Required(CONF_SWITCH): RemoteValueSchema.SCHEMA.extend(
-                {
-                    vol.Required(CONF_ADDRESS): ensure_group_address,
-                }
-            ),
-            vol.Optional(CONF_BRIGHTNESS): RemoteValueSchema.SCHEMA.extend(
-                {vol.Required(CONF_ADDRESS): ensure_group_address}
-            ),
-            vol.Optional(CONF_RGBW): RemoteValueSchema.SCHEMA.extend(
-                {vol.Required(CONF_ADDRESS): ensure_group_address}
-            ),
-            vol.Optional(CONF_COLOR): RemoteValueSchema.SCHEMA.extend(
+            vol.Optional(CONF_SWITCH): RemoteValueSchema.SCHEMA,
+            vol.Required(CONF_BRIGHTNESS): RemoteValueSchema.SCHEMA.extend(
                 {vol.Required(CONF_ADDRESS): ensure_group_address}
             ),
-            vol.Optional(CONF_COLOR_TEMPERATURE): RemoteValueSchema.SCHEMA.extend(
+        }
+    )
+
+    SCHEMA = vol.All(
+        BaseDeviceSchema.SCHEMA.extend(
+            {
+                vol.Optional(CONF_SWITCH): RemoteValueSchema.SCHEMA.extend(
+                    {vol.Required(CONF_ADDRESS): ensure_group_address}
+                ),
+                vol.Optional(CONF_BRIGHTNESS): RemoteValueSchema.SCHEMA.extend(
+                    {vol.Required(CONF_ADDRESS): ensure_group_address}
+                ),
+                vol.Exclusive(CONF_RGBW, "color"): RemoteValueSchema.SCHEMA.extend(
+                    {vol.Required(CONF_ADDRESS): ensure_group_address}
+                ),
+                vol.Exclusive(CONF_COLOR, "color"): RemoteValueSchema.SCHEMA.extend(
+                    {vol.Required(CONF_ADDRESS): ensure_group_address}
+                ),
+                vol.Exclusive(CONF_INDIVIDUAL_COLORS, "color"): {
+                    vol.Inclusive(CONF_RED, "colors"): COLOR_SCHEMA,
+                    vol.Inclusive(CONF_GREEN, "colors"): COLOR_SCHEMA,
+                    vol.Inclusive(CONF_BLUE, "colors"): COLOR_SCHEMA,
+                    vol.Optional(CONF_WHITE): COLOR_SCHEMA,
+                },
+                vol.Optional(CONF_COLOR_TEMPERATURE): RemoteValueSchema.SCHEMA.extend(
+                    {
+                        vol.Required(CONF_ADDRESS): ensure_group_address,
+                        vol.Optional(
+                            CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE
+                        ): enum(ColorTempModes),
+                        vol.Optional(
+                            CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN
+                        ): vol.All(vol.Coerce(int), vol.Range(min=1)),
+                        vol.Optional(
+                            CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN
+                        ): vol.All(vol.Coerce(int), vol.Range(min=1)),
+                    }
+                ),
+            },
+        ),
+        vol.Any(
+            vol.Schema(
                 {
-                    vol.Required(CONF_ADDRESS): ensure_group_address,
-                    vol.Optional(
-                        CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE
-                    ): enum(ColorTempModes),
-                    vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All(
-                        vol.Coerce(int), vol.Range(min=1)
-                    ),
-                    vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All(
-                        vol.Coerce(int), vol.Range(min=1)
-                    ),
-                }
+                    vol.Required(CONF_SWITCH): RemoteValueSchema.SCHEMA.extend(
+                        {vol.Required(CONF_ADDRESS): object}
+                    )
+                },
+                extra=vol.ALLOW_EXTRA,
             ),
-        }
+            vol.Schema(
+                {
+                    vol.Required(CONF_INDIVIDUAL_COLORS): {
+                        vol.Required(CONF_RED): {
+                            vol.Required(CONF_SWITCH): {
+                                vol.Required(CONF_ADDRESS): object
+                            }
+                        },
+                        vol.Required(CONF_GREEN): {
+                            vol.Required(CONF_SWITCH): {
+                                vol.Required(CONF_ADDRESS): object
+                            }
+                        },
+                        vol.Required(CONF_BLUE): {
+                            vol.Required(CONF_SWITCH): {
+                                vol.Required(CONF_ADDRESS): object
+                            }
+                        },
+                    },
+                },
+                extra=vol.ALLOW_EXTRA,
+            ),
+        ),
     )
 
 
diff --git a/xknx/devices/light.py b/xknx/devices/light.py
index 34480547..91be195a 100644
--- a/xknx/devices/light.py
+++ b/xknx/devices/light.py
@@ -12,7 +12,7 @@ It provides functionality for
 """
 from enum import Enum
 import logging
-from typing import Optional
+from typing import Optional, Tuple
 
 from xknx.remote_value import (
     RemoteValueColorRGB,
@@ -34,6 +34,57 @@ class ColorTempModes(Enum):
     relative = "DPT-5.001"
 
 
+class _SwitchAndBrightness:
+    def __init__(
+        self,
+        xknx,
+        name,
+        feature_name: str,
+        group_address_switch=None,
+        group_address_switch_state=None,
+        group_address_brightness=None,
+        group_address_brightness_state=None,
+        after_update_cb=None,
+    ):
+        self.switch = RemoteValueSwitch(
+            xknx,
+            group_address_switch,
+            group_address_switch_state,
+            device_name=name,
+            feature_name=feature_name + "_state",
+            after_update_cb=after_update_cb,
+        )
+        self.brightness = RemoteValueScaling(
+            xknx,
+            group_address_brightness,
+            group_address_brightness_state,
+            device_name=name,
+            feature_name=feature_name + "_brightness",
+            after_update_cb=after_update_cb,
+            range_from=0,
+            range_to=255,
+        )
+
+    @property
+    def is_on(self):
+        """Return if light is on."""
+        return self.switch.value
+
+    async def set_on(self):
+        """Switch light on."""
+        if self.switch.initialized:
+            await self.switch.on()
+
+    async def set_off(self):
+        """Switch light off."""
+        if self.switch.initialized:
+            await self.switch.off()
+
+    def __eq__(self, other):
+        """Compare for quality."""
+        return self.__dict__ == other.__dict__
+
+
 # pylint: disable=too-many-public-methods, too-many-instance-attributes
 class Light(Device):
     """Class for managing a light."""
@@ -58,6 +109,22 @@ class Light(Device):
         group_address_tunable_white_state=None,
         group_address_color_temperature=None,
         group_address_color_temperature_state=None,
+        group_address_switch_red=None,
+        group_address_switch_red_state=None,
+        group_address_brightness_red=None,
+        group_address_brightness_red_state=None,
+        group_address_switch_green=None,
+        group_address_switch_green_state=None,
+        group_address_brightness_green=None,
+        group_address_brightness_green_state=None,
+        group_address_switch_blue=None,
+        group_address_switch_blue_state=None,
+        group_address_brightness_blue=None,
+        group_address_brightness_blue_state=None,
+        group_address_switch_white=None,
+        group_address_switch_white_state=None,
+        group_address_brightness_white=None,
+        group_address_brightness_white_state=None,
         min_kelvin=None,
         max_kelvin=None,
         device_updated_cb=None,
@@ -122,6 +189,50 @@ class Light(Device):
             after_update_cb=self.after_update,
         )
 
+        self.red = _SwitchAndBrightness(
+            xknx,
+            self.name,
+            "red",
+            group_address_switch_red,
+            group_address_switch_red_state,
+            group_address_brightness_red,
+            group_address_brightness_red_state,
+            self.after_update,
+        )
+
+        self.green = _SwitchAndBrightness(
+            xknx,
+            self.name,
+            "green",
+            group_address_switch_green,
+            group_address_switch_green_state,
+            group_address_brightness_green,
+            group_address_brightness_green_state,
+            self.after_update,
+        )
+
+        self.blue = _SwitchAndBrightness(
+            xknx,
+            self.name,
+            "blue",
+            group_address_switch_blue,
+            group_address_switch_blue_state,
+            group_address_brightness_blue,
+            group_address_brightness_blue_state,
+            self.after_update,
+        )
+
+        self.white = _SwitchAndBrightness(
+            xknx,
+            self.name,
+            "white",
+            group_address_switch_white,
+            group_address_switch_white_state,
+            group_address_brightness_white,
+            group_address_brightness_white_state,
+            self.after_update,
+        )
+
         self.min_kelvin = min_kelvin
         self.max_kelvin = max_kelvin
 
@@ -135,6 +246,9 @@ class Light(Device):
             self.tunable_white,
             self.color_temperature,
         )
+        for color in (self.red, self.green, self.blue, self.white):
+            yield color.switch
+            yield color.brightness
 
     @property
     def supports_brightness(self):
@@ -144,12 +258,19 @@ class Light(Device):
     @property
     def supports_color(self):
         """Return if light supports color."""
-        return self.color.initialized
+        return self.color.initialized or all(
+            [c.brightness.initialized for c in (self.red, self.green, self.blue)]
+        )
 
     @property
     def supports_rgbw(self):
         """Return if light supports RGBW."""
-        return self.rgbw.initialized
+        return self.rgbw.initialized or all(
+            [
+                c.brightness.initialized
+                for c in (self.red, self.green, self.blue, self.white)
+            ]
+        )
 
     @property
     def supports_tunable_white(self):
@@ -161,6 +282,21 @@ class Light(Device):
         """Return if light supports absolute color temperature."""
         return self.color_temperature.initialized
 
+    @classmethod
+    def read_color_from_config(
+        cls, color, config
+    ) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
+        """Load color configuration from configuration structure."""
+        if "individual_colors" in config and color in config["individual_colors"]:
+            sub_config = config["individual_colors"][color]
+            return (
+                sub_config.get("group_address_switch"),
+                sub_config.get("group_address_switch_state"),
+                sub_config.get("group_address_brightness"),
+                sub_config.get("group_address_brightness_state"),
+            )
+        return None, None, None, None
+
     @classmethod
     def from_config(cls, xknx, name, config):
         """Initialize object from configuration structure."""
@@ -183,6 +319,31 @@ class Light(Device):
         min_kelvin = config.get("min_kelvin", Light.DEFAULT_MIN_KELVIN)
         max_kelvin = config.get("max_kelvin", Light.DEFAULT_MAX_KELVIN)
 
+        (
+            red_switch,
+            red_switch_state,
+            red_brightness,
+            red_brightness_state,
+        ) = cls.read_color_from_config("red", config)
+        (
+            green_switch,
+            green_switch_state,
+            green_brightness,
+            green_brightness_state,
+        ) = cls.read_color_from_config("green", config)
+        (
+            blue_switch,
+            blue_switch_state,
+            blue_brightness,
+            blue_brightness_state,
+        ) = cls.read_color_from_config("blue", config)
+        (
+            white_switch,
+            white_switch_state,
+            white_brightness,
+            white_brightness_state,
+        ) = cls.read_color_from_config("white", config)
+
         return cls(
             xknx,
             name,
@@ -198,6 +359,22 @@ class Light(Device):
             group_address_tunable_white_state=group_address_tunable_white_state,
             group_address_color_temperature=group_address_color_temperature,
             group_address_color_temperature_state=group_address_color_temperature_state,
+            group_address_switch_red=red_switch,
+            group_address_switch_red_state=red_switch_state,
+            group_address_brightness_red=red_brightness,
+            group_address_brightness_red_state=red_brightness_state,
+            group_address_switch_green=green_switch,
+            group_address_switch_green_state=green_switch_state,
+            group_address_brightness_green=green_brightness,
+            group_address_brightness_green_state=green_brightness_state,
+            group_address_switch_blue=blue_switch,
+            group_address_switch_blue_state=blue_switch_state,
+            group_address_brightness_blue=blue_brightness,
+            group_address_brightness_blue_state=blue_brightness_state,
+            group_address_switch_white=white_switch,
+            group_address_switch_white_state=white_switch_state,
+            group_address_brightness_white=white_brightness,
+            group_address_brightness_white_state=white_brightness_state,
             min_kelvin=min_kelvin,
             max_kelvin=max_kelvin,
         )
@@ -232,7 +409,51 @@ class Light(Device):
             )
         )
 
-        return '<Light name="{}" ' 'switch="{}"{}{}{}{}{} />'.format(
+        str_red_state = (
+            ""
+            if not self.red.switch.initialized
+            else f' red_state="{self.red.switch.group_addr_str()}"'
+        )
+        str_red_brightness = (
+            ""
+            if not self.red.brightness.initialized
+            else f' red_brightness="{self.red.brightness.group_addr_str()}"'
+        )
+
+        str_green_state = (
+            ""
+            if not self.green.switch.initialized
+            else f' green_state="{self.green.switch.group_addr_str()}"'
+        )
+        str_green_brightness = (
+            ""
+            if not self.green.brightness.initialized
+            else f' green_brightness="{self.green.brightness.group_addr_str()}"'
+        )
+
+        str_blue_state = (
+            ""
+            if not self.blue.switch.initialized
+            else f' blue_state="{self.blue.switch.group_addr_str()}"'
+        )
+        str_blue_brightness = (
+            ""
+            if not self.blue.brightness.initialized
+            else f' blue_brightness="{self.blue.brightness.group_addr_str()}"'
+        )
+
+        str_white_state = (
+            ""
+            if not self.white.switch.initialized
+            else f' white_state="{self.white.switch.group_addr_str()}"'
+        )
+        str_white_brightness = (
+            ""
+            if not self.white.brightness.initialized
+            else f' white_brightness="{self.white.brightness.group_addr_str()}"'
+        )
+
+        return '<Light name="{}" ' 'switch="{}"{}{}{}{}{}{}{}{}{}{}{}{}{} />'.format(
             self.name,
             self.switch.group_addr_str(),
             str_brightness,
@@ -240,20 +461,45 @@ class Light(Device):
             str_rgbw,
             str_tunable_white,
             str_color_temperature,
+            str_red_state,
+            str_red_brightness,
+            str_green_state,
+            str_green_brightness,
+            str_blue_state,
+            str_blue_brightness,
+            str_white_state,
+            str_white_brightness,
         )
 
     @property
     def state(self) -> Optional[bool]:
         """Return the current switch state of the device."""
-        return self.switch.value
+        if self.switch.value is not None:
+            return self.switch.value
+        if any(
+            [
+                c.switch.value is not None
+                for c in (self.red, self.green, self.blue, self.white)
+            ]
+        ):
+            return any(
+                [c.switch.value for c in (self.red, self.green, self.blue, self.white)]
+            )
+        return None
 
     async def set_on(self):
         """Switch light on."""
-        await self.switch.on()
+        if self.switch.initialized:
+            await self.switch.on()
+        for color in (self.red, self.green, self.blue, self.white):
+            await color.set_on()
 
     async def set_off(self):
         """Switch light off."""
-        await self.switch.off()
+        if self.switch.initialized:
+            await self.switch.off()
+        for color in (self.red, self.green, self.blue, self.white):
+            await color.set_off()
 
     @property
     def current_brightness(self):
@@ -275,10 +521,21 @@ class Light(Device):
         If the device supports RGBW, get the current RGB+White values instead.
         """
         if self.supports_rgbw:
-            if not self.rgbw.value:
-                return None, None
-            return self.rgbw.value[:3], self.rgbw.value[3]
-        return self.color.value, None
+            if self.rgbw.initialized:
+                if not self.rgbw.value:
+                    return None, None
+                return self.rgbw.value[:3], self.rgbw.value[3]
+        if self.color.initialized:
+            return self.color.value, None
+        # individual RGB addresses - white will return None when it is not initialized
+        colors = [
+            self.red.brightness.value,
+            self.green.brightness.value,
+            self.blue.brightness.value,
+        ]
+        if None in colors:
+            return None, self.white.brightness.value
+        return colors, self.white.brightness.value
 
     async def set_color(self, color, white=None):
         """
@@ -289,13 +546,36 @@ class Light(Device):
         """
         if white is not None:
             if self.supports_rgbw:
-                await self.rgbw.set(list(color) + [white])
-                return
+                if self.rgbw.initialized:
+                    await self.rgbw.set(list(color) + [white])
+                    return
+                if all(
+                    [
+                        c.brightness.initialized
+                        for c in (self.red, self.green, self.blue, self.white)
+                    ]
+                ):
+                    await self.red.brightness.set(color[0])
+                    await self.green.brightness.set(color[1])
+                    await self.blue.brightness.set(color[2])
+                    await self.white.brightness.set(white)
+                    return
             logger.warning("RGBW not supported for device %s", self.get_name())
         else:
             if self.supports_color:
-                await self.color.set(color)
-                return
+                if self.color.initialized:
+                    await self.color.set(color)
+                    return
+                if all(
+                    [
+                        c.brightness.initialized
+                        for c in (self.red, self.green, self.blue)
+                    ]
+                ):
+                    await self.red.brightness.set(color[0])
+                    await self.green.brightness.set(color[1])
+                    await self.blue.brightness.set(color[2])
+                    return
             logger.warning("Colors not supported for device %s", self.get_name())
 
     @property


Test patch:
diff --git a/requirements/testing.txt b/requirements/testing.txt
index 1f27f36b..5fc7de8a 100644
--- a/requirements/testing.txt
+++ b/requirements/testing.txt
@@ -10,6 +10,6 @@ pylint-strict-informational==0.1
 pytest==6.2.1
 pytest-cov==2.10.1
 pytest-timeout==1.4.2
-setuptools==51.0.0
+setuptools==51.1.0.post20201221
 tox==3.20.1
 mypy==0.790
diff --git a/test/config_tests/config_v1_test.py b/test/config_tests/config_v1_test.py
index 5479f439..be7b10bc 100644
--- a/test/config_tests/config_v1_test.py
+++ b/test/config_tests/config_v1_test.py
@@ -23,6 +23,7 @@ from xknx.devices import (
 )
 from xknx.exceptions import XKNXException
 from xknx.io import ConnectionConfig, ConnectionType
+from xknx.telegram import IndividualAddress
 import yaml
 
 
@@ -54,7 +55,7 @@ class TestConfig(unittest.TestCase):
     #
     def test_config_general(self):
         """Test reading general section from config file."""
-        self.assertEqual(TestConfig.xknx.own_address, "15.15.249")
+        self.assertEqual(TestConfig.xknx.own_address, IndividualAddress("15.15.249"))
         self.assertEqual(TestConfig.xknx.rate_limit, 18)
         self.assertEqual(TestConfig.xknx.multicast_group, "224.1.2.3")
         self.assertEqual(TestConfig.xknx.multicast_port, 1337)
@@ -197,6 +198,38 @@ class TestConfig(unittest.TestCase):
             ),
         )
 
+    def test_config_individual_rgb(self):
+        """Test reading Light with with dimming and color address."""
+        expected = Light(
+            TestConfig.xknx,
+            "Diningroom.Light_rgb",
+            group_address_switch_white="1/6/4",
+            group_address_switch_white_state="1/6/5",
+            group_address_brightness_white="1/6/6",
+            group_address_brightness_white_state="1/6/7",
+            group_address_switch_red="1/6/14",
+            group_address_switch_red_state="1/6/15",
+            group_address_brightness_red="1/6/16",
+            group_address_brightness_red_state="1/6/17",
+            group_address_switch_green="1/6/24",
+            group_address_switch_green_state="1/6/25",
+            group_address_brightness_green="1/6/26",
+            group_address_brightness_green_state="1/6/27",
+            group_address_switch_blue="1/6/34",
+            group_address_switch_blue_state="1/6/35",
+            group_address_brightness_blue="1/6/36",
+            group_address_brightness_blue_state="1/6/37",
+            min_kelvin=Light.DEFAULT_MIN_KELVIN,
+            max_kelvin=Light.DEFAULT_MAX_KELVIN,
+        )
+        result = TestConfig.xknx.devices["Diningroom.Light_rgb"]
+
+        print(f"result: {result} \nexpected: {expected}")
+        self.assertEqual(
+            result,
+            expected,
+        )
+
     def test_config_color_temperature(self):
         """Test reading Light with with dimming and color temperature address."""
         self.assertEqual(
diff --git a/test/config_tests/resources/light/invalid_3.yaml b/test/config_tests/resources/light/invalid_3.yaml
new file mode 100644
index 00000000..3b261fd6
--- /dev/null
+++ b/test/config_tests/resources/light/invalid_3.yaml
@@ -0,0 +1,25 @@
+name: "two or more values in the same group of exclusion 'color' @ .*"
+friendly_name: "Office"
+switch:
+  address: "2/1/5"
+  state_address: "2/4/5"
+  state_update: "expire 60"
+  passive_state_addresses: ["8/8/8"]
+brightness:
+  address: "2/5/5"
+  state_address: "2/5/6"
+  state_update: "expire 60"
+  passive_state_addresses: ["8/8/8"]
+rgbw:
+  address: "2/4/5"
+  state_address: "2/4/6"
+color:
+  address: "2/4/5"
+  state_address: "2/4/6"
+color_temperature:
+  address: "2/5/7"
+  state_address: "2/5/8"
+  state_update: "expire 60"
+  mode: absolute
+  min_kelvin: 2550
+  max_kelvin: 6200
\ No newline at end of file
diff --git a/test/config_tests/resources/light/valid_5.yaml b/test/config_tests/resources/light/valid_5.yaml
new file mode 100644
index 00000000..6e480fff
--- /dev/null
+++ b/test/config_tests/resources/light/valid_5.yaml
@@ -0,0 +1,10 @@
+name: "office"
+friendly_name: "Office"
+switch:
+  address: "2/1/5"
+  state_address: "2/4/5"
+  state_update: "expire 60"
+  passive_state_addresses: ["8/8/8"]
+color:
+  address: "2/4/5"
+  state_address: "2/4/6"
\ No newline at end of file
diff --git a/test/config_tests/resources/light/valid_rgb.yaml b/test/config_tests/resources/light/valid_rgb.yaml
new file mode 100644
index 00000000..d93861d8
--- /dev/null
+++ b/test/config_tests/resources/light/valid_rgb.yaml
@@ -0,0 +1,24 @@
+name: "office"
+friendly_name: "Office"
+individual_colors:
+  red:
+    switch:
+      address: "2/1/5"
+      state_address: "2/4/5"
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
+  green:
+    switch:
+      address: "2/1/5"
+      state_address: "2/4/5"
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
+  blue:
+    switch:
+      address: "2/1/5"
+      state_address: "2/4/5"
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
diff --git a/test/config_tests/resources/light/valid_rgbw.yaml b/test/config_tests/resources/light/valid_rgbw.yaml
new file mode 100644
index 00000000..47d1ad43
--- /dev/null
+++ b/test/config_tests/resources/light/valid_rgbw.yaml
@@ -0,0 +1,31 @@
+name: "office"
+friendly_name: "Office"
+individual_colors:
+  white:
+    switch:
+      address: "2/1/5"
+      state_address: "2/4/5"
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
+  red:
+    switch:
+      address: "2/1/5"
+      state_address: "2/4/5"
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
+  green:
+    switch:
+      address: "2/1/5"
+      state_address: "2/4/5"
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
+  blue:
+    switch:
+      address: "2/1/5"
+      state_address: "2/4/5"
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
diff --git a/test/config_tests/resources/light/valid_rgbw_no_switch.yaml b/test/config_tests/resources/light/valid_rgbw_no_switch.yaml
new file mode 100644
index 00000000..8b1a7934
--- /dev/null
+++ b/test/config_tests/resources/light/valid_rgbw_no_switch.yaml
@@ -0,0 +1,21 @@
+name: "office"
+friendly_name: "Office"
+switch:
+  address: "2/1/5"
+individual_colors:
+  white:
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
+  red:
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
+  green:
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
+  blue:
+    brightness:
+      address: "2/5/7"
+      state_address: "2/5/8"
diff --git a/test/devices_tests/light_test.py b/test/devices_tests/light_test.py
index 2e54fb9d..19ce18ab 100644
--- a/test/devices_tests/light_test.py
+++ b/test/devices_tests/light_test.py
@@ -65,6 +65,44 @@ class TestLight(unittest.TestCase):
         light = Light(xknx, "Diningroom.Light_1", group_address_switch="1/6/4")
         self.assertFalse(light.supports_color)
 
+    def test_supports_individual_color_true(self):
+        """Test supports_color true."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+        )
+        self.assertTrue(light.supports_color)
+
+    def test_supports_individual_color_false(self):
+        """Test supports_color false."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+        )
+        self.assertFalse(light.supports_color)
+
     #
     # TEST SUPPORT COLOR RGBW
     #
@@ -91,6 +129,52 @@ class TestLight(unittest.TestCase):
         )
         self.assertFalse(light.supports_rgbw)
 
+    def test_supports_individual_rgbw_true(self):
+        """Test supports_rgbw true."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+            group_address_switch_white="1/1/13",
+            group_address_switch_white_state="1/1/14",
+            group_address_brightness_white="1/1/15",
+            group_address_brightness_white_state="1/1/16",
+        )
+        self.assertTrue(light.supports_rgbw)
+
+    def test_supports_individual_rgbw_false(self):
+        """Test supports_color false."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+        )
+        self.assertFalse(light.supports_rgbw)
+
     #
     # TEST SUPPORT TUNABLE WHITE
     #
@@ -179,6 +263,73 @@ class TestLight(unittest.TestCase):
         self.assertEqual(len(telegrams), 6)
         self.assertListEqual(telegrams, test_telegrams)
 
+    def test_sync_individual_color(self):
+        """Test sync function / sending group reads to KNX bus. Testing with a Light without dimm functionality."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+            group_address_switch_white="1/1/13",
+            group_address_switch_white_state="1/1/14",
+            group_address_brightness_white="1/1/15",
+            group_address_brightness_white_state="1/1/16",
+        )
+        self.loop.run_until_complete(light.sync())
+
+        self.assertEqual(xknx.telegrams.qsize(), 8)
+
+        telegrams = [xknx.telegrams.get_nowait() for _ in range(8)]
+
+        test_telegrams = [
+            Telegram(
+                destination_address=GroupAddress("1/1/2"),
+                payload=GroupValueRead(),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/4"),
+                payload=GroupValueRead(),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/6"),
+                payload=GroupValueRead(),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/8"),
+                payload=GroupValueRead(),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/10"),
+                payload=GroupValueRead(),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/12"),
+                payload=GroupValueRead(),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/14"),
+                payload=GroupValueRead(),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/16"),
+                payload=GroupValueRead(),
+            ),
+        ]
+
+        self.assertEqual(len(set(telegrams)), 8)
+        self.assertEqual(set(telegrams), set(test_telegrams))
+
     #
     # SYNC WITH STATE ADDRESS
     #
@@ -256,6 +407,55 @@ class TestLight(unittest.TestCase):
             ),
         )
 
+    def test_set_on_individual_color(self):
+        """Test switching on a Light."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+            group_address_switch_white="1/1/13",
+            group_address_switch_white_state="1/1/14",
+            group_address_brightness_white="1/1/15",
+            group_address_brightness_white_state="1/1/16",
+        )
+        self.loop.run_until_complete(light.set_on())
+        self.assertEqual(xknx.telegrams.qsize(), 4)
+
+        telegrams = [xknx.telegrams.get_nowait() for _ in range(4)]
+
+        test_telegrams = [
+            Telegram(
+                destination_address=GroupAddress("1/1/1"),
+                payload=GroupValueWrite(DPTBinary(True)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/5"),
+                payload=GroupValueWrite(DPTBinary(True)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/9"),
+                payload=GroupValueWrite(DPTBinary(True)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/13"),
+                payload=GroupValueWrite(DPTBinary(True)),
+            ),
+        ]
+        self.assertEqual(len(set(telegrams)), 4)
+        self.assertEqual(set(telegrams), set(test_telegrams))
+
     #
     # TEST SET OFF
     #
@@ -279,6 +479,55 @@ class TestLight(unittest.TestCase):
             ),
         )
 
+    def test_set_off_individual_color(self):
+        """Test switching off a Light."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+            group_address_switch_white="1/1/13",
+            group_address_switch_white_state="1/1/14",
+            group_address_brightness_white="1/1/15",
+            group_address_brightness_white_state="1/1/16",
+        )
+        self.loop.run_until_complete(light.set_off())
+        self.assertEqual(xknx.telegrams.qsize(), 4)
+
+        telegrams = [xknx.telegrams.get_nowait() for _ in range(4)]
+
+        test_telegrams = [
+            Telegram(
+                destination_address=GroupAddress("1/1/1"),
+                payload=GroupValueWrite(DPTBinary(False)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/5"),
+                payload=GroupValueWrite(DPTBinary(False)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/9"),
+                payload=GroupValueWrite(DPTBinary(False)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/13"),
+                payload=GroupValueWrite(DPTBinary(False)),
+            ),
+        ]
+        self.assertEqual(len(set(telegrams)), 4)
+        self.assertEqual(set(telegrams), set(test_telegrams))
+
     #
     # TEST SET BRIGHTNESS
     #
@@ -351,6 +600,89 @@ class TestLight(unittest.TestCase):
                 "Colors not supported for device %s", "TestLight"
             )
 
+    def test_set_individual_color(self):
+        """Test setting the color of a Light."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+        )
+        self.loop.run_until_complete(light.set_color([23, 24, 25]))
+        self.assertEqual(xknx.telegrams.qsize(), 3)
+        telegrams = [xknx.telegrams.get_nowait() for _ in range(3)]
+
+        test_telegrams = [
+            Telegram(
+                destination_address=GroupAddress("1/1/3"),
+                payload=GroupValueWrite(DPTArray(23)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/7"),
+                payload=GroupValueWrite(DPTArray(24)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/11"),
+                payload=GroupValueWrite(DPTArray(25)),
+            ),
+        ]
+
+        self.assertEqual(len(set(telegrams)), 3)
+        self.assertEqual(set(telegrams), set(test_telegrams))
+
+        self.loop.run_until_complete(
+            xknx.devices.process(
+                Telegram(
+                    destination_address=GroupAddress("1/1/4"),
+                    payload=GroupValueWrite(DPTArray(23)),
+                )
+            )
+        )
+        self.loop.run_until_complete(
+            xknx.devices.process(
+                Telegram(
+                    destination_address=GroupAddress("1/1/8"),
+                    payload=GroupValueWrite(DPTArray(24)),
+                )
+            )
+        )
+        self.loop.run_until_complete(
+            xknx.devices.process(
+                Telegram(
+                    destination_address=GroupAddress("1/1/12"),
+                    payload=GroupValueWrite(DPTArray(25)),
+                )
+            )
+        )
+        self.assertEqual(light.current_color, ([23, 24, 25], None))
+
+    def test_set_individual_color_not_possible(self):
+        """Test setting the color of a non light without color."""
+        # pylint: disable=invalid-name
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "TestLight",
+            group_address_switch_red="1/1/1",
+        )
+        with patch("logging.Logger.warning") as mock_warn:
+            self.loop.run_until_complete(light.set_color((23, 24, 25)))
+            self.assertEqual(xknx.telegrams.qsize(), 0)
+            mock_warn.assert_called_with(
+                "Colors not supported for device %s", "TestLight"
+            )
+
     #
     # TEST SET COLOR AS RGBW
     #
@@ -395,6 +727,117 @@ class TestLight(unittest.TestCase):
                 "RGBW not supported for device %s", "TestLight"
             )
 
+    def test_set_individual_color_rgbw(self):
+        """Test setting RGBW value of a Light."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+            group_address_switch_white="1/1/13",
+            group_address_switch_white_state="1/1/14",
+            group_address_brightness_white="1/1/15",
+            group_address_brightness_white_state="1/1/16",
+        )
+        self.loop.run_until_complete(light.set_color([23, 24, 25], white=26))
+        self.assertEqual(xknx.telegrams.qsize(), 4)
+        telegrams = [xknx.telegrams.get_nowait() for _ in range(4)]
+
+        test_telegrams = [
+            Telegram(
+                destination_address=GroupAddress("1/1/3"),
+                payload=GroupValueWrite(DPTArray(23)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/7"),
+                payload=GroupValueWrite(DPTArray(24)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/11"),
+                payload=GroupValueWrite(DPTArray(25)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/15"),
+                payload=GroupValueWrite(DPTArray(26)),
+            ),
+        ]
+
+        self.assertEqual(len(set(telegrams)), 4)
+        self.assertEqual(set(telegrams), set(test_telegrams))
+
+        self.loop.run_until_complete(
+            xknx.devices.process(
+                Telegram(
+                    destination_address=GroupAddress("1/1/4"),
+                    payload=GroupValueWrite(DPTArray(23)),
+                )
+            )
+        )
+        self.loop.run_until_complete(
+            xknx.devices.process(
+                Telegram(
+                    destination_address=GroupAddress("1/1/8"),
+                    payload=GroupValueWrite(DPTArray(24)),
+                )
+            )
+        )
+        self.loop.run_until_complete(
+            xknx.devices.process(
+                Telegram(
+                    destination_address=GroupAddress("1/1/12"),
+                    payload=GroupValueWrite(DPTArray(25)),
+                )
+            )
+        )
+        self.loop.run_until_complete(
+            xknx.devices.process(
+                Telegram(
+                    destination_address=GroupAddress("1/1/16"),
+                    payload=GroupValueWrite(DPTArray(26)),
+                )
+            )
+        )
+        self.assertEqual(light.current_color, ([23, 24, 25], 26))
+
+    def test_set_individual_color_rgbw_not_possible(self):
+        """Test setting RGBW value of a non light without color."""
+        # pylint: disable=invalid-name
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "TestLight",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+        )
+        with patch("logging.Logger.warning") as mock_warn:
+            self.loop.run_until_complete(light.set_color((23, 24, 25), 26))
+
+            self.assertEqual(xknx.telegrams.qsize(), 0)
+            mock_warn.assert_called_with(
+                "RGBW not supported for device %s", "TestLight"
+            )
+
     #
     # TEST SET TUNABLE WHITE
     #
@@ -500,6 +943,41 @@ class TestLight(unittest.TestCase):
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.state, False)
 
+    def test_process_color_switch(self):
+        """Test process / reading telegrams from telegram queue. Test if switch position is processed correctly."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "TestLight",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+        )
+        self.assertEqual(light.state, None)
+
+        telegram = Telegram(
+            destination_address=GroupAddress("1/1/2"),
+            payload=GroupValueWrite(DPTBinary(True)),
+        )
+        self.loop.run_until_complete(light.process(telegram))
+        self.assertEqual(light.state, True)
+
+        telegram = Telegram(
+            destination_address=GroupAddress("1/1/2"),
+            payload=GroupValueWrite(DPTBinary(False)),
+        )
+        self.loop.run_until_complete(light.process(telegram))
+        self.assertEqual(light.state, False)
+
     def test_process_switch_callback(self):
         """Test process / reading telegrams from telegram queue. Test if callback is called."""
         # pylint: disable=no-self-use
@@ -595,6 +1073,46 @@ class TestLight(unittest.TestCase):
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.current_color, ((23, 24, 25), None))
 
+    def test_process_individual_color(self):
+        """Test process / reading telegrams from telegram queue. Test if color is processed."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "TestLight",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+        )
+        self.assertEqual(light.current_color, (None, None))
+
+        telegrams = [
+            Telegram(
+                destination_address=GroupAddress("1/1/4"),
+                payload=GroupValueWrite(DPTArray(42)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/8"),
+                payload=GroupValueWrite(DPTArray(43)),
+            ),
+            Telegram(
+                destination_address=GroupAddress("1/1/12"),
+                payload=GroupValueWrite(DPTArray(44)),
+            ),
+        ]
+
+        for telegram in telegrams:
+            self.loop.run_until_complete(light.process(telegram))
+        self.assertEqual(light.current_color, ([42, 43, 44], None))
+
     def test_process_color_rgbw(self):
         """Test process / reading telegrams from telegram queue. Test if RGBW is processed."""
         xknx = XKNX()
@@ -613,6 +1131,37 @@ class TestLight(unittest.TestCase):
         self.loop.run_until_complete(light.process(telegram))
         self.assertEqual(light.current_color, ([23, 24, 25], 26))
 
+    def test_process_individual_color_rgbw(self):
+        """Test process / reading telegrams from telegram queue. Test if RGBW is processed."""
+        xknx = XKNX()
+        light = Light(
+            xknx,
+            "Diningroom.Light_1",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+            group_address_switch_white="1/1/13",
+            group_address_switch_white_state="1/1/14",
+            group_address_brightness_white="1/1/15",
+            group_address_brightness_white_state="1/1/16",
+        )
+        self.assertEqual(light.current_color, (None, None))
+        telegram = Telegram(
+            destination_address=GroupAddress("1/1/16"),
+            payload=GroupValueWrite(DPTArray(42)),
+        )
+        self.loop.run_until_complete(light.process(telegram))
+        self.assertEqual(light.current_color, (None, 42))
+
     def test_process_tunable_white(self):
         """Test process / reading telegrams from telegram queue. Test if tunable white is processed."""
         xknx = XKNX()
@@ -786,6 +1335,22 @@ class TestLight(unittest.TestCase):
             group_address_color_temperature_state="1/7/10",
             group_address_rgbw="1/7/11",
             group_address_rgbw_state="1/7/12",
+            group_address_switch_red="1/1/1",
+            group_address_switch_red_state="1/1/2",
+            group_address_brightness_red="1/1/3",
+            group_address_brightness_red_state="1/1/4",
+            group_address_switch_green="1/1/5",
+            group_address_switch_green_state="1/1/6",
+            group_address_brightness_green="1/1/7",
+            group_address_brightness_green_state="1/1/8",
+            group_address_switch_blue="1/1/9",
+            group_address_switch_blue_state="1/1/10",
+            group_address_brightness_blue="1/1/11",
+            group_address_brightness_blue_state="1/1/12",
+            group_address_switch_white="1/1/13",
+            group_address_switch_white_state="1/1/14",
+            group_address_brightness_white="1/1/15",
+            group_address_brightness_white_state="1/1/16",
         )
         self.assertTrue(light.has_group_address(GroupAddress("1/7/1")))
         self.assertTrue(light.has_group_address(GroupAddress("1/7/2")))
@@ -799,4 +1364,22 @@ class TestLight(unittest.TestCase):
         self.assertTrue(light.has_group_address(GroupAddress("1/7/10")))
         self.assertTrue(light.has_group_address(GroupAddress("1/7/11")))
         self.assertTrue(light.has_group_address(GroupAddress("1/7/12")))
+
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/1")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/2")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/3")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/4")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/5")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/6")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/7")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/8")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/9")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/10")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/11")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/12")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/13")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/14")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/15")))
+        self.assertTrue(light.has_group_address(GroupAddress("1/1/16")))
+
         self.assertFalse(light.has_group_address(GroupAddress("1/7/13")))