Exemplo n.º 1
0
class Fan(Device):
    """Class for managing a fan."""

    # pylint: disable=too-many-instance-attributes
    # pylint: disable=too-many-public-methods

    def __init__(self,
                 xknx,
                 name,
                 group_address_speed=None,
                 group_address_speed_state=None,
                 device_updated_cb=None):
        """Initialize fan class."""
        # pylint: disable=too-many-arguments
        Device.__init__(self, xknx, name, device_updated_cb)

        self.speed = RemoteValueScaling(xknx,
                                        group_address_speed,
                                        group_address_speed_state,
                                        device_name=self.name,
                                        after_update_cb=self.after_update,
                                        range_from=0,
                                        range_to=100)

    @classmethod
    def from_config(cls, xknx, name, config):
        """Initialize object from configuration structure."""
        group_address_speed = \
            config.get('group_address_speed')
        group_address_speed_state = \
            config.get('group_address_speed_state')

        return cls(xknx,
                   name,
                   group_address_speed=group_address_speed,
                   group_address_speed_state=group_address_speed_state)

    def has_group_address(self, group_address):
        """Test if device has given group address."""
        return self.speed.has_group_address(group_address)

    def __str__(self):
        """Return object as readable string."""
        return '<Fan name="{0}" ' \
            'speed="{1}" />' \
            .format(
                self.name,
                self.speed.group_addr_str())

    async def set_speed(self, speed):
        """Set the fan to a desginated speed."""
        await self.speed.set(speed)

    async def do(self, action):
        """Execute 'do' commands."""
        if action.startswith("speed:"):
            await self.set_speed(int(action[6:]))
        else:
            self.xknx.logger.warning(
                "Could not understand action %s for device %s", action,
                self.get_name())

    def state_addresses(self):
        """Return group addresses which should be requested to sync state."""
        state_addresses = []
        state_addresses.extend(self.speed.state_addresses())
        return state_addresses

    async def process_group_write(self, telegram):
        """Process incoming GROUP WRITE telegram."""
        await self.speed.process(telegram)

    @property
    def current_speed(self):
        """Return current speed of fan."""
        return self.speed.value

    def __eq__(self, other):
        """Equal operator."""
        return self.__dict__ == other.__dict__
Exemplo n.º 2
0
class Cover(Device):
    """Class for managing a cover."""

    # pylint: disable=too-many-instance-attributes
    # pylint: disable=too-many-public-methods
    # pylint: disable=too-many-locals

    # Average typical travel time of a cover
    DEFAULT_TRAVEL_TIME_DOWN = 22
    DEFAULT_TRAVEL_TIME_UP = 22

    def __init__(self,
                 xknx,
                 name,
                 group_address_long=None,
                 group_address_short=None,
                 group_address_position=None,
                 group_address_position_state=None,
                 group_address_angle=None,
                 group_address_angle_state=None,
                 travel_time_down=DEFAULT_TRAVEL_TIME_DOWN,
                 travel_time_up=DEFAULT_TRAVEL_TIME_UP,
                 invert_position=False,
                 invert_angle=False,
                 device_updated_cb=None):
        """Initialize Cover class."""
        # pylint: disable=too-many-arguments
        super().__init__(xknx, name, device_updated_cb)

        self.updown = RemoteValueUpDown(xknx,
                                        group_address_long,
                                        device_name=self.name,
                                        after_update_cb=self.after_update,
                                        invert=invert_position)

        self.step = RemoteValueStep(xknx,
                                    group_address_short,
                                    device_name=self.name,
                                    after_update_cb=self.after_update,
                                    invert=invert_position)

        position_range_from = 0 if invert_position else 100
        position_range_to = 100 if invert_position else 0
        self.position = RemoteValueScaling(xknx,
                                           group_address_position,
                                           group_address_position_state,
                                           device_name=self.name,
                                           after_update_cb=self.after_update,
                                           range_from=position_range_from,
                                           range_to=position_range_to)

        angle_range_from = 0 if invert_angle else 100
        angle_range_to = 100 if invert_angle else 0
        self.angle = RemoteValueScaling(xknx,
                                        group_address_angle,
                                        group_address_angle_state,
                                        device_name=self.name,
                                        after_update_cb=self.after_update,
                                        range_from=angle_range_from,
                                        range_to=angle_range_to)

        self.travel_time_down = travel_time_down
        self.travel_time_up = travel_time_up

        self.travelcalculator = TravelCalculator(travel_time_down,
                                                 travel_time_up)

    @classmethod
    def from_config(cls, xknx, name, config):
        """Initialize object from configuration structure."""
        group_address_long = \
            config.get('group_address_long')
        group_address_short = \
            config.get('group_address_short')
        group_address_position = \
            config.get('group_address_position')
        group_address_position_state = \
            config.get('group_address_position_state')
        group_address_angle = \
            config.get('group_address_angle')
        group_address_angle_state = \
            config.get('group_address_angle_state')
        travel_time_down = \
            config.get('travel_time_down', cls.DEFAULT_TRAVEL_TIME_DOWN)
        travel_time_up = \
            config.get('travel_time_up', cls.DEFAULT_TRAVEL_TIME_UP)
        invert_position = \
            config.get('invert_position', False)
        invert_angle = \
            config.get('invert_angle', False)

        return cls(xknx,
                   name,
                   group_address_long=group_address_long,
                   group_address_short=group_address_short,
                   group_address_position=group_address_position,
                   group_address_position_state=group_address_position_state,
                   group_address_angle=group_address_angle,
                   group_address_angle_state=group_address_angle_state,
                   travel_time_down=travel_time_down,
                   travel_time_up=travel_time_up,
                   invert_position=invert_position,
                   invert_angle=invert_angle)

    def has_group_address(self, group_address):
        """Test if device has given group address."""
        return self.updown.has_group_address(group_address) \
            or self.step.has_group_address(group_address) \
            or self.position.has_group_address(group_address) \
            or self.angle.has_group_address(group_address)

    def __str__(self):
        """Return object as readable string."""
        return '<Cover name="{0}" ' \
            'updown="{1}" ' \
            'step="{2}" ' \
            'position="{3}" ' \
            'angle="{4}" '\
            'travel_time_down="{5}" ' \
            'travel_time_up="{6}" />' \
            .format(
                self.name,
                self.updown.group_addr_str(),
                self.step.group_addr_str(),
                self.position.group_addr_str(),
                self.angle.group_addr_str(),
                self.travel_time_down,
                self.travel_time_up)

    async def set_down(self):
        """Move cover down."""
        await self.updown.down()
        self.travelcalculator.start_travel_down()

    async def set_up(self):
        """Move cover up."""
        await self.updown.up()
        self.travelcalculator.start_travel_up()

    async def set_short_down(self):
        """Move cover short down."""
        await self.step.increase()

    async def set_short_up(self):
        """Move cover short up."""
        await self.step.decrease()

    async def stop(self):
        """Stop cover."""
        # Thats the KNX way of doing this. electrical engineers ... m-)
        await self.step.increase()
        self.travelcalculator.stop()

    async def set_position(self, position):
        """Move cover to a desginated postion."""
        # No direct positioning group address defined
        if not self.position.group_address:
            current_position = self.current_position()
            if position < current_position:
                await self.updown.down()
            elif position > current_position:
                await self.updown.up()
            self.travelcalculator.start_travel(position)
            return

        await self.position.set(position)
        self.travelcalculator.start_travel(position)

    async def set_angle(self, angle):
        """Move cover to designated angle."""
        if not self.supports_angle:
            self.xknx.logger.warning('Angle not supported for device %s',
                                     self.get_name())
            return
        await self.angle.set(angle)
        await self.after_update()

    async def auto_stop_if_necessary(self):
        """Do auto stop if necessary."""
        # If device does not support auto_positioning,
        # we have to stop the device when position is reached.
        # unless device was traveling to fully open
        # or fully closed state
        if (not self.position.group_address and self.position_reached()
                and not self.is_open() and not self.is_closed()):
            await self.stop()

    async def do(self, action):
        """Execute 'do' commands."""
        if action == "up":
            await self.set_up()
        elif action == "short_up":
            await self.set_short_up()
        elif action == "down":
            await self.set_down()
        elif action == "short_down":
            await self.set_short_down()
        elif action == "stop":
            await self.stop()
        else:
            self.xknx.logger.warning(
                "Could not understand action %s for device %s", action,
                self.get_name())

    def state_addresses(self):
        """Return group addresses which should be requested to sync state."""
        if self.travelcalculator.is_traveling():
            # Cover is traveling, requesting state will return false results
            return []
        state_addresses = []
        state_addresses.extend(self.position.state_addresses())
        state_addresses.extend(self.angle.state_addresses())
        return state_addresses

    async def process_group_write(self, telegram):
        """Process incoming GROUP WRITE telegram."""
        position_processed = await self.position.process(telegram)
        if position_processed:
            self.travelcalculator.set_position(self.position.value)
            await self.after_update()

        await self.angle.process(telegram)

    def current_position(self):
        """Return current position of cover."""
        return self.travelcalculator.current_position()

    def current_angle(self):
        """Return current tilt angle of cover."""
        return self.angle.value

    def is_traveling(self):
        """Return if cover is traveling at the moment."""
        return self.travelcalculator.is_traveling()

    def position_reached(self):
        """Return if cover has reached its final position."""
        return self.travelcalculator.position_reached()

    def is_open(self):
        """Return if cover is open."""
        return self.travelcalculator.is_open()

    def is_closed(self):
        """Return if cover is closed."""
        return self.travelcalculator.is_closed()

    @property
    def supports_position(self):
        """Return if cover supports direct positioning."""
        return self.position.initialized

    @property
    def supports_angle(self):
        """Return if cover supports tilt angle."""
        return self.angle.initialized

    def __eq__(self, other):
        """Equal operator."""
        return self.__dict__ == other.__dict__
Exemplo n.º 3
0
class Group(Device):
    """Class for managing a light."""

    # pylint: disable=too-many-locals

    def __init__(
        self,
        xknx,
        name,
        addr,
        sw_cb=None,
        #
        val_cb=None,
        val_dim_cb=None,
        #
        clr_rgb_cb=None,
        clr_rgb_dim=None,
        #
        clr_r_cb=None,
        clr_r_dim_cb=None,
        clr_r_sw_cb=None,
        #
        clr_g_cb=None,
        clr_g_dim_cb=None,
        clr_g_sw_cb=None,
        #
        clr_b_cb=None,
        clr_b_dim_cb=None,
        clr_b_sw_cb=None,
        #
        clr_cct_cb=None,
        clr_cct_dim_cb=None,
        #
        clr_h_cb=None,
        clr_h_dim_cb=None,
        #
        clr_s_cb=None,
        clr_s_dim_cb=None,
    ):
        """Initialize Group class."""
        # pylint: disable=too-many-arguments
        super().__init__(xknx, name)
        self.xknx = xknx

        self.sw = RV_SWITCH(xknx, addr["SW"], None, self.name, sw_cb)
        self.sw_stat = RV_SWITCH(xknx, addr["SW_STAT"], None, self.name)

        self.val = RV_SCALE(
            xknx,
            addr["VAL"],
            None,
            self.name,
            val_cb,
            0,
            255,
        )
        self.val_dim = RV_DIM(xknx, addr["VAL_DIM"], None, self.name,
                              val_dim_cb)
        self.val_stat = RV_SCALE(xknx, addr["VAL_STAT"], None, self.name, None,
                                 0, 255)
        #
        self.clr_xyy = RV_XYY(xknx, addr["CLR_xyY"], None, self.name)
        self.clr_xyy_stat = RV_XYY(xknx, addr["CLR_xyY_STAT"], None, self.name)
        self.clr_cct_abs = RV_ABS(xknx, addr["CLR_CCT_ABS"], None, self.name)
        #
        self.clr_rgb = RV_RGB(xknx, addr["CLR_RGB"], None, self.name,
                              clr_rgb_cb)
        self.clr_rgb_dim = RV_DIM(xknx, addr["CLR_RGB_DIM"], None, self.name,
                                  val_dim_cb)
        self.clr_rgb_stat = RV_RGB(xknx, addr["CLR_RGB_STAT"], None, self.name)
        #
        self.clr_r = RV_SCALE(xknx, addr["CLR_R"], None, self.name, clr_r_cb,
                              0, 255)
        self.clr_r_dim = RV_DIM(xknx, addr["CLR_R_DIM"], None, self.name,
                                clr_r_dim_cb)
        self.clr_r_stat = RV_SCALE(xknx, addr["CLR_R_STAT"], None, self.name,
                                   None, 0, 255)
        self.clr_r_sw = RV_SWITCH(xknx, addr["CLR_R_SW"], None, self.name,
                                  clr_r_sw_cb)
        self.clr_r_sw_stat = RV_SWITCH(xknx, addr["CLR_R_SW_STAT"], None,
                                       self.name)
        #
        self.clr_g = RV_SCALE(xknx, addr["CLR_G"], None, self.name, clr_g_cb,
                              0, 255)
        self.clr_g_dim = RV_DIM(xknx, addr["CLR_G_DIM"], None, self.name,
                                clr_g_dim_cb)
        self.clr_g_stat = RV_SCALE(xknx, addr["CLR_G_STAT"], None, self.name,
                                   None, 0, 255)
        self.clr_g_sw = RV_SWITCH(xknx, addr["CLR_G_SW"], None, self.name,
                                  clr_g_sw_cb)
        self.clr_g_sw_stat = RV_SWITCH(xknx, addr["CLR_G_SW_STAT"], None,
                                       self.name)
        #
        self.clr_b = RV_SCALE(xknx, addr["CLR_B"], None, self.name, clr_b_cb,
                              0, 255)
        self.clr_b_dim = RV_DIM(xknx, addr["CLR_B_DIM"], None, self.name,
                                clr_b_dim_cb)
        self.clr_b_stat = RV_SCALE(xknx, addr["CLR_B_STAT"], None, self.name,
                                   None, 0, 255)
        self.clr_b_sw = RV_SWITCH(xknx, addr["CLR_B_SW"], None, self.name,
                                  clr_b_sw_cb)
        self.clr_b_sw_stat = RV_SWITCH(xknx, addr["CLR_B_SW_STAT"], None,
                                       self.name)
        #
        self.clr_cct = RV_SCALE(xknx, addr["CLR_CCT"], None, self.name,
                                clr_cct_cb, 0, 255)
        self.clr_cct_dim = RV_DIM(xknx, addr["CLR_CCT_DIM"], None, self.name,
                                  clr_cct_dim_cb)
        self.clr_cct_stat = RV_SCALE(xknx, addr["CLR_CCT_STAT"], None,
                                     self.name, None, 0, 255)
        #
        self.clr_h = RV_SCALE(
            xknx,
            addr["CLR_H"],
            None,
            self.name,
            clr_h_cb,
            0,
            360,
        )
        self.clr_h_dim = RV_DIM(xknx, addr["CLR_H_DIM"], None, self.name,
                                clr_h_dim_cb)
        self.clr_h_stat = RV_SCALE(
            xknx,
            addr["CLR_H_STAT"],
            None,
            self.name,
            None,
            0,
            360,
        )
        #
        self.clr_s = RV_SCALE(xknx, addr["CLR_S"], None, self.name, clr_s_cb,
                              0, 255)
        self.clr_s_dim = RV_DIM(xknx, addr["CLR_S_DIM"], None, self.name,
                                clr_s_dim_cb)
        self.clr_s_stat = RV_SCALE(xknx, addr["CLR_S_STAT"], None, self.name,
                                   None, 0, 255)

    def update(self, addresses):
        self.sw.group_addresses = addresses["SW"]
        self.sw_stat.group_addresses = addresses["SW_STAT"]
        #
        self.val.group_addresses = addresses["VAL"]
        self.val_dim.group_addresses = addresses["VAL_DIM"]
        self.val_stat.group_addresses = addresses["VAL_STAT"]
        #
        self.clr_xyy.group_addresses = addresses["CLR_xyY"]
        self.clr_xyy_stat.group_addresses = addresses["CLR_xyY_STAT"]
        self.clr_cct_abs.group_addresses = addresses["CLR_CCT_ABS"]
        #
        self.clr_rgb.group_addresses = addresses["CLR_RGB"]
        self.clr_rgb_dim.group_addresses = addresses["CLR_RGB"]
        self.clr_rgb_stat.group_addresses = addresses["CLR_RGB_STAT"]
        #
        self.clr_r.group_addresses = addresses["CLR_R"]
        self.clr_r_dim.group_addresses = addresses["CLR_R_DIM"]
        self.clr_r_stat.group_addresses = addresses["CLR_R_STAT"]
        self.clr_r_sw.group_addresses = addresses["CLR_R_SW"]
        self.clr_r_sw_stat.group_addresses = addresses["CLR_R_SW_STAT"]
        #
        self.clr_g.group_addresses = addresses["CLR_G"]
        self.clr_g_dim.group_addresses = addresses["CLR_G_DIM"]
        self.clr_g_stat.group_addresses = addresses["CLR_G_STAT"]
        self.clr_g_sw.group_addresses = addresses["CLR_G_SW"]
        self.clr_g_sw_stat.group_addresses = addresses["CLR_G_SW_STAT"]
        #
        self.clr_b.group_addresses = addresses["CLR_B"]
        self.clr_b_dim.group_addresses = addresses["CLR_B_DIM"]
        self.clr_b_stat.group_addresses = addresses["CLR_B_STAT"]
        self.clr_b_sw.group_addresses = addresses["CLR_B_SW"]
        self.clr_b_sw_stat.group_addresses = addresses["CLR_B_SW_STAT"]
        #
        self.clr_cct.group_addresses = addresses["CLR_CCT"]
        self.clr_cct_dim.group_addresses = addresses["CLR_CCT_DIM"]
        self.clr_cct_stat.group_addresses = addresses["CLR_CCT_STAT"]
        #
        self.clr_h.group_addresses = addresses["CLR_H"]
        self.clr_h_dim.group_addresses = addresses["CLR_H_DIM"]
        self.clr_h_stat.group_addresses = addresses["CLR_H_STAT"]
        #
        self.clr_s.group_addresses = addresses["CLR_S"]
        self.clr_s_dim.group_addresses = addresses["CLR_S_DIM"]
        self.clr_s_stat.group_addresses = addresses["CLR_S_STAT"]

    @property
    def supports_dimming(self):
        return self.dimming_val.initialized

    @property
    def supports_brightness(self):
        return self.brightness.initialized

    @property
    def supports_color(self):
        return self.color_rgb.initialized

    @property
    def supports_color_xyY(self):
        return self.color_xyY.initialized

    def has_group_address(self, group_address):
        """Test if device has given group address. Not used for Status"""
        return (
            self.sw.has_group_address(group_address)
            or self.val.has_group_address(group_address)  # noqa W503
            or self.val_dim.has_group_address(group_address)  # noqa W503
            #
            or self.clr_xyy.has_group_address(group_address)  # noqa W503
            or self.clr_cct_abs.has_group_address(group_address)  # noqa W503
            #
            or self.clr_rgb.has_group_address(group_address)  # noqa W503
            or self.clr_rgb_dim.has_group_address(group_address)  # noqa W503
            #
            or self.clr_r.has_group_address(group_address)  # noqa W503
            or self.clr_r_dim.has_group_address(group_address)  # noqa W503
            or self.clr_r_sw.has_group_address(group_address)  # noqa W503
            #
            or self.clr_g.has_group_address(group_address)  # noqa W503
            or self.clr_g_dim.has_group_address(group_address)  # noqa W503
            or self.clr_g_sw.has_group_address(group_address)  # noqa W503
            #
            or self.clr_b.has_group_address(group_address)  # noqa W503
            or self.clr_b_dim.has_group_address(group_address)  # noqa W503
            or self.clr_b_sw.has_group_address(group_address)  # noqa W503
            #
            or self.clr_h.has_group_address(group_address)  # noqa W503
            or self.clr_h_dim.has_group_address(group_address)  # noqa W503
            #
            or self.clr_s.has_group_address(group_address)  # noqa W503
            or self.clr_s_dim.has_group_address(group_address)  # noqa W503
            #
            or self.clr_cct.has_group_address(group_address)  # noqa W503
            or self.clr_cct_dim.has_group_address(group_address)  # noqa W503
        )

    def __repr__(self):
        """Return object as readable string."""
        return (
            f"KNX_Group(name={self.name}, sw:{self.sw.group_address}"
            f", sw_stat: {self.sw_stat.group_address}"
            f", val_dim:{self.val_dim.group_address}, val:{self.val.group_address}"
            f", clr_xyy: {self.clr_xyy.group_address}, clr_rgb: {self.clr_rgb.group_address}"
            f", clr_rgb_stat:{self.clr_rgb_stat.group_address}")

    async def process_group_write(self, telegram):
        """Process incoming GROUP WRITE telegram."""
        await self.sw.process(telegram)
        await self.val_dim.process(telegram)
        await self.val.process(telegram)
        #
        # await self.clr_xyy.process(telegram)
        #
        await self.clr_rgb.process(telegram)
        await self.clr_rgb_dim.process(telegram)
        #
        await self.clr_r.process(telegram)
        await self.clr_r_dim.process(telegram)
        await self.clr_r_sw.process(telegram)
        #
        await self.clr_g.process(telegram)
        await self.clr_g_dim.process(telegram)
        await self.clr_g_sw.process(telegram)
        #
        await self.clr_b.process(telegram)
        await self.clr_b_dim.process(telegram)
        await self.clr_b_sw.process(telegram)
        #
        await self.clr_h.process(telegram)
        await self.clr_h_dim.process(telegram)
        #
        await self.clr_s.process(telegram)
        await self.clr_s_dim.process(telegram)
        #
        await self.clr_cct.process(telegram)
        await self.clr_cct_dim.process(telegram)

    def __eq__(self, other):
        """Equal operator."""
        return self.__dict__ == other.__dict__
Exemplo n.º 4
0
Arquivo: light.py Projeto: vosc/xknx
class Light(Device):
    """Class for managing a light."""

    # pylint: disable=too-many-locals
    DEFAULT_MIN_KELVIN = 2700  # 370 mireds
    DEFAULT_MAX_KELVIN = 6000  # 166 mireds

    def __init__(self,
                 xknx,
                 name,
                 group_address_switch=None,
                 group_address_switch_state=None,
                 group_address_brightness=None,
                 group_address_brightness_state=None,
                 group_address_color=None,
                 group_address_color_state=None,
                 group_address_rgbw=None,
                 group_address_rgbw_state=None,
                 group_address_tunable_white=None,
                 group_address_tunable_white_state=None,
                 group_address_color_temperature=None,
                 group_address_color_temperature_state=None,
                 min_kelvin=None,
                 max_kelvin=None,
                 device_updated_cb=None):
        """Initialize Light class."""
        # pylint: disable=too-many-arguments
        super().__init__(xknx, name, device_updated_cb)

        self.switch = RemoteValueSwitch(xknx,
                                        group_address_switch,
                                        group_address_switch_state,
                                        device_name=self.name,
                                        after_update_cb=self.after_update)

        self.brightness = RemoteValueScaling(xknx,
                                             group_address_brightness,
                                             group_address_brightness_state,
                                             device_name=self.name,
                                             after_update_cb=self.after_update,
                                             range_from=0,
                                             range_to=255)

        self.color = RemoteValueColorRGB(xknx,
                                         group_address_color,
                                         group_address_color_state,
                                         device_name=self.name,
                                         after_update_cb=self.after_update)

        self.rgbw = RemoteValueColorRGBW(xknx,
                                         group_address_rgbw,
                                         group_address_rgbw_state,
                                         device_name=self.name,
                                         after_update_cb=self.after_update)

        self.tunable_white = RemoteValueScaling(
            xknx,
            group_address_tunable_white,
            group_address_tunable_white_state,
            device_name=self.name,
            after_update_cb=self.after_update,
            range_from=0,
            range_to=255)

        self.color_temperature = RemoteValueDpt2ByteUnsigned(
            xknx,
            group_address_color_temperature,
            group_address_color_temperature_state,
            device_name=self.name,
            after_update_cb=self.after_update)

        self.min_kelvin = min_kelvin
        self.max_kelvin = max_kelvin

    @property
    def supports_brightness(self):
        """Return if light supports brightness."""
        return self.brightness.initialized

    @property
    def supports_color(self):
        """Return if light supports color."""
        return self.color.initialized

    @property
    def supports_rgbw(self):
        """Return if light supports RGBW."""
        return self.rgbw.initialized

    @property
    def supports_tunable_white(self):
        """Return if light supports tunable white / relative color temperature."""
        return self.tunable_white.initialized

    @property
    def supports_color_temperature(self):
        """Return if light supports absolute color temperature."""
        return self.color_temperature.initialized

    @classmethod
    def from_config(cls, xknx, name, config):
        """Initialize object from configuration structure."""
        group_address_switch = \
            config.get('group_address_switch')
        group_address_switch_state = \
            config.get('group_address_switch_state')
        group_address_brightness = \
            config.get('group_address_brightness')
        group_address_brightness_state = \
            config.get('group_address_brightness_state')
        group_address_color = \
            config.get('group_address_color')
        group_address_color_state = \
            config.get('group_address_color_state')
        group_address_rgbw = \
            config.get('group_address_rgbw')
        group_address_rgbw_state = \
            config.get('group_address_rgbw_state')
        group_address_tunable_white = \
            config.get('group_address_tunable_white')
        group_address_tunable_white_state = \
            config.get('group_address_tunable_white_state')
        group_address_color_temperature = \
            config.get('group_address_color_temperature')
        group_address_color_temperature_state = \
            config.get('group_address_color_temperature_state')
        min_kelvin = \
            config.get('min_kelvin', Light.DEFAULT_MIN_KELVIN)
        max_kelvin = \
            config.get('max_kelvin', Light.DEFAULT_MAX_KELVIN)

        return cls(
            xknx,
            name,
            group_address_switch=group_address_switch,
            group_address_switch_state=group_address_switch_state,
            group_address_brightness=group_address_brightness,
            group_address_brightness_state=group_address_brightness_state,
            group_address_color=group_address_color,
            group_address_color_state=group_address_color_state,
            group_address_rgbw=group_address_rgbw,
            group_address_rgbw_state=group_address_rgbw_state,
            group_address_tunable_white=group_address_tunable_white,
            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,
            min_kelvin=min_kelvin,
            max_kelvin=max_kelvin)

    def has_group_address(self, group_address):
        """Test if device has given group address."""
        return (self.switch.has_group_address(group_address)
                or self.brightness.has_group_address(group_address)
                or self.color.has_group_address(group_address)
                or self.rgbw.has_group_address(group_address)
                or self.tunable_white.has_group_address(group_address)
                or self.color_temperature.has_group_address(group_address))

    def __str__(self):
        """Return object as readable string."""
        str_brightness = '' if not self.supports_brightness else \
            ' brightness="{0}"'.format(
                self.brightness.group_addr_str())

        str_color = '' if not self.supports_color else \
            ' color="{0}"'.format(
                self.color.group_addr_str())

        str_rgbw = '' if not self.supports_rgbw else \
            ' rgbw="{0}"'.format(
                self.rgbw.group_addr_str())

        str_tunable_white = '' if not self.supports_tunable_white else \
            ' tunable white="{0}"'.format(
                self.tunable_white.group_addr_str())

        str_color_temperature = '' if not self.supports_color_temperature else \
            ' color temperature="{0}"'.format(
                self.color_temperature.group_addr_str())

        return '<Light name="{0}" ' \
            'switch="{1}"{2}{3}{4}{5}{6} />' \
            .format(
                self.name,
                self.switch.group_addr_str(),
                str_brightness,
                str_color,
                str_rgbw,
                str_tunable_white,
                str_color_temperature)

    @property
    def state(self):
        """Return the current switch state of the device."""
        # None will return False
        return bool(self.switch.value)

    async def set_on(self):
        """Switch light on."""
        await self.switch.on()

    async def set_off(self):
        """Switch light off."""
        await self.switch.off()

    @property
    def current_brightness(self):
        """Return current brightness of light."""
        return self.brightness.value

    async def set_brightness(self, brightness):
        """Set brightness of light."""
        if not self.supports_brightness:
            self.xknx.logger.warning("Dimming not supported for device %s",
                                     self.get_name())
            return
        await self.brightness.set(brightness)

    @property
    def current_color(self):
        """
        Return current color of light.

        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

    async def set_color(self, color, white=None):
        """
        Set color of a light device.

        If also the white value is given and the device supports RGBW,
        set all four values.
        """
        if white is not None:
            if self.supports_rgbw:
                await self.rgbw.set(list(color) + [white])
                return
            self.xknx.logger.warning("RGBW not supported for device %s",
                                     self.get_name())
        else:
            if self.supports_color:
                await self.color.set(color)
                return
            self.xknx.logger.warning("Colors not supported for device %s",
                                     self.get_name())

    @property
    def current_tunable_white(self):
        """Return current relative color temperature of light."""
        return self.tunable_white.value

    async def set_tunable_white(self, tunable_white):
        """Set relative color temperature of light."""
        if not self.supports_tunable_white:
            self.xknx.logger.warning(
                "Tunable white not supported for device %s", self.get_name())
            return
        await self.tunable_white.set(tunable_white)

    @property
    def current_color_temperature(self):
        """Return current absolute color temperature of light."""
        return self.color_temperature.value

    async def set_color_temperature(self, color_temperature):
        """Set absolute color temperature of light."""
        if not self.supports_color_temperature:
            self.xknx.logger.warning(
                "Absolute Color Temperature not supported for device %s",
                self.get_name())
            return
        await self.color_temperature.set(color_temperature)

    async def do(self, action):
        """Execute 'do' commands."""
        if action == "on":
            await self.set_on()
        elif action == "off":
            await self.set_off()
        elif action.startswith("brightness:"):
            await self.set_brightness(int(action[11:]))
        elif action.startswith("tunable_white:"):
            await self.set_tunable_white(int(action[14:]))
        elif action.startswith("color_temperature:"):
            await self.set_color_temperature(int(action[18:]))
        else:
            self.xknx.logger.warning(
                "Could not understand action %s for device %s", action,
                self.get_name())

    def state_addresses(self):
        """Return group addresses which should be requested to sync state."""
        state_addresses = []
        state_addresses.extend(self.switch.state_addresses())
        state_addresses.extend(self.color.state_addresses())
        state_addresses.extend(self.rgbw.state_addresses())
        state_addresses.extend(self.brightness.state_addresses())
        state_addresses.extend(self.tunable_white.state_addresses())
        state_addresses.extend(self.color_temperature.state_addresses())
        return state_addresses

    async def process_group_write(self, telegram):
        """Process incoming GROUP WRITE telegram."""
        await self.switch.process(telegram)
        await self.color.process(telegram)
        await self.rgbw.process(telegram)
        await self.brightness.process(telegram)
        await self.tunable_white.process(telegram)
        await self.color_temperature.process(telegram)

    def __eq__(self, other):
        """Equal operator."""
        return self.__dict__ == other.__dict__