def test_to_process_error_operation_mode(self):
     """Test process errornous telegram."""
     xknx = XKNX()
     remote_value = RemoteValueClimateMode(
         xknx,
         group_address=GroupAddress("1/2/3"),
         climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE,
     )
     with self.assertRaises(CouldNotParseTelegram):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             payload=GroupValueWrite(DPTBinary(1)),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
     with self.assertRaises(CouldNotParseTelegram):
         telegram = Telegram(
             destination_address=GroupAddress("1/2/3"),
             payload=GroupValueWrite(
                 DPTArray(
                     (
                         0x64,
                         0x65,
                     )
                 )
             ),
         )
         self.loop.run_until_complete(remote_value.process(telegram))
 def test_set_operation_mode(self):
     """Test setting value."""
     xknx = XKNX()
     remote_value = RemoteValueClimateMode(
         xknx,
         group_address=GroupAddress("1/2/3"),
         climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE,
     )
     self.loop.run_until_complete(remote_value.set(HVACOperationMode.NIGHT))
     self.assertEqual(xknx.telegrams.qsize(), 1)
     telegram = xknx.telegrams.get_nowait()
     self.assertEqual(
         telegram,
         Telegram(
             destination_address=GroupAddress("1/2/3"),
             payload=GroupValueWrite(DPTArray((0x03,))),
         ),
     )
     self.loop.run_until_complete(
         remote_value.set(HVACOperationMode.FROST_PROTECTION)
     )
     self.assertEqual(xknx.telegrams.qsize(), 1)
     telegram = xknx.telegrams.get_nowait()
     self.assertEqual(
         telegram,
         Telegram(
             destination_address=GroupAddress("1/2/3"),
             payload=GroupValueWrite(DPTArray((0x04,))),
         ),
     )
 def test_from_knx_operation_mode(self):
     """Test from_knx function with normal operation."""
     xknx = XKNX()
     remote_value = RemoteValueClimateMode(
         xknx,
         climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE)
     self.assertEqual(remote_value.from_knx(DPTArray((0x02, ))),
                      HVACOperationMode.STANDBY)
 def test_to_knx_operation_mode(self):
     """Test to_knx function with normal operation."""
     xknx = XKNX()
     remote_value = RemoteValueClimateMode(
         xknx,
         climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE)
     self.assertEqual(remote_value.to_knx(HVACOperationMode.COMFORT),
                      DPTArray((0x01, )))
 def test_process_operation_mode(self):
     """Test process telegram."""
     xknx = XKNX()
     remote_value = RemoteValueClimateMode(
         xknx,
         group_address=GroupAddress("1/2/3"),
         climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE,
     )
     telegram = Telegram(group_address=GroupAddress("1/2/3"),
                         payload=DPTArray((0x00, )))
     self.loop.run_until_complete(remote_value.process(telegram))
     self.assertEqual(remote_value.value, HVACOperationMode.AUTO)
 def test_to_knx_error_operation_mode(self):
     """Test to_knx function with wrong parameter."""
     xknx = XKNX()
     remote_value = RemoteValueClimateMode(
         xknx,
         climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE)
     with self.assertRaises(ConversionError):
         remote_value.to_knx(256)
     with self.assertRaises(ConversionError):
         remote_value.to_knx("256")
Example #7
0
    def __init__(
        self,
        xknx,
        name,
        group_address_operation_mode=None,
        group_address_operation_mode_state=None,
        group_address_operation_mode_protection=None,
        group_address_operation_mode_night=None,
        group_address_operation_mode_comfort=None,
        group_address_operation_mode_standby=None,
        group_address_controller_status=None,
        group_address_controller_status_state=None,
        group_address_controller_mode=None,
        group_address_controller_mode_state=None,
        group_address_heat_cool=None,
        group_address_heat_cool_state=None,
        operation_modes=None,
        controller_modes=None,
        device_updated_cb=None,
    ):
        """Initialize ClimateMode class."""
        # pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements
        super().__init__(xknx, name, device_updated_cb)

        self.remote_value_operation_mode = RemoteValueClimateMode(
            xknx,
            group_address=group_address_operation_mode,
            group_address_state=group_address_operation_mode_state,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE,
            after_update_cb=None,
        )
        self.remote_value_controller_mode = RemoteValueClimateMode(
            xknx,
            group_address=group_address_controller_mode,
            group_address_state=group_address_controller_mode_state,
            sync_state=True,
            device_name=name,
            feature_name="Controller mode",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.
            HVAC_CONTR_MODE,
            after_update_cb=None,
        )
        self.remote_value_controller_status = RemoteValueClimateMode(
            xknx,
            group_address=group_address_controller_status,
            group_address_state=group_address_controller_status_state,
            sync_state=True,
            device_name=name,
            feature_name="Controller status",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.
            CONTROLLER_STATUS,
            after_update_cb=None,
        )

        self.remote_value_operation_mode_comfort = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_comfort,
            group_address_state=group_address_operation_mode_comfort,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Comfort",
            operation_mode=HVACOperationMode.COMFORT,
            after_update_cb=None,
        )
        self.remote_value_operation_mode_standby = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_standby,
            group_address_state=group_address_operation_mode_standby,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Standby",
            operation_mode=HVACOperationMode.STANDBY,
            after_update_cb=None,
        )
        self.remote_value_operation_mode_night = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_night,
            group_address_state=group_address_operation_mode_night,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Night",
            operation_mode=HVACOperationMode.NIGHT,
            after_update_cb=None,
        )
        self.remote_value_operation_mode_protection = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_protection,
            group_address_state=group_address_operation_mode_protection,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Protection",
            operation_mode=HVACOperationMode.FROST_PROTECTION,
            after_update_cb=None,
        )
        self.remote_value_heat_cool = RemoteValueBinaryHeatCool(
            xknx,
            group_address=group_address_heat_cool,
            group_address_state=group_address_heat_cool_state,
            sync_state=True,
            device_name=name,
            feature_name="Heat/Cool",
            controller_mode=HVACControllerMode.HEAT,
            after_update_cb=None,
        )

        self.operation_mode = HVACOperationMode.STANDBY
        self.controller_mode = HVACControllerMode.HEAT

        self._operation_modes = []
        if operation_modes is None:
            self._operation_modes = self.gather_operation_modes()
        else:
            for mode in operation_modes:
                if isinstance(mode, str):
                    self._operation_modes.append(HVACOperationMode(mode))
                elif isinstance(mode, HVACOperationMode):
                    self._operation_modes.append(mode)

        self._controller_modes = []
        if controller_modes is None:
            self._controller_modes = self.gather_controller_modes()
        else:
            for mode in controller_modes:
                if isinstance(mode, str):
                    self._controller_modes.append(HVACControllerMode(mode))
                elif isinstance(mode, HVACControllerMode):
                    self._controller_modes.append(mode)

        self.supports_operation_mode = any(
            operation_mode.initialized
            for operation_mode in self._iter_byte_operation_modes()) or any(
                operation_mode.initialized
                for operation_mode in self._iter_binary_operation_modes())
        self.supports_controller_mode = any(
            operation_mode.initialized
            for operation_mode in self._iter_controller_remote_values())

        self._use_binary_operation_modes = any(
            operation_mode.initialized
            for operation_mode in self._iter_binary_operation_modes())
Example #8
0
class ClimateMode(Device):
    """Class for managing the climate mode."""

    # pylint: disable=invalid-name,too-many-instance-attributes

    def __init__(
        self,
        xknx,
        name,
        group_address_operation_mode=None,
        group_address_operation_mode_state=None,
        group_address_operation_mode_protection=None,
        group_address_operation_mode_night=None,
        group_address_operation_mode_comfort=None,
        group_address_operation_mode_standby=None,
        group_address_controller_status=None,
        group_address_controller_status_state=None,
        group_address_controller_mode=None,
        group_address_controller_mode_state=None,
        group_address_heat_cool=None,
        group_address_heat_cool_state=None,
        operation_modes=None,
        controller_modes=None,
        device_updated_cb=None,
    ):
        """Initialize ClimateMode class."""
        # pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements
        super().__init__(xknx, name, device_updated_cb)

        self.remote_value_operation_mode = RemoteValueClimateMode(
            xknx,
            group_address=group_address_operation_mode,
            group_address_state=group_address_operation_mode_state,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE,
            after_update_cb=None,
        )
        self.remote_value_controller_mode = RemoteValueClimateMode(
            xknx,
            group_address=group_address_controller_mode,
            group_address_state=group_address_controller_mode_state,
            sync_state=True,
            device_name=name,
            feature_name="Controller mode",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.
            HVAC_CONTR_MODE,
            after_update_cb=None,
        )
        self.remote_value_controller_status = RemoteValueClimateMode(
            xknx,
            group_address=group_address_controller_status,
            group_address_state=group_address_controller_status_state,
            sync_state=True,
            device_name=name,
            feature_name="Controller status",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.
            CONTROLLER_STATUS,
            after_update_cb=None,
        )

        self.remote_value_operation_mode_comfort = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_comfort,
            group_address_state=group_address_operation_mode_comfort,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Comfort",
            operation_mode=HVACOperationMode.COMFORT,
            after_update_cb=None,
        )
        self.remote_value_operation_mode_standby = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_standby,
            group_address_state=group_address_operation_mode_standby,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Standby",
            operation_mode=HVACOperationMode.STANDBY,
            after_update_cb=None,
        )
        self.remote_value_operation_mode_night = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_night,
            group_address_state=group_address_operation_mode_night,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Night",
            operation_mode=HVACOperationMode.NIGHT,
            after_update_cb=None,
        )
        self.remote_value_operation_mode_protection = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_protection,
            group_address_state=group_address_operation_mode_protection,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Protection",
            operation_mode=HVACOperationMode.FROST_PROTECTION,
            after_update_cb=None,
        )
        self.remote_value_heat_cool = RemoteValueBinaryHeatCool(
            xknx,
            group_address=group_address_heat_cool,
            group_address_state=group_address_heat_cool_state,
            sync_state=True,
            device_name=name,
            feature_name="Heat/Cool",
            controller_mode=HVACControllerMode.HEAT,
            after_update_cb=None,
        )

        self.operation_mode = HVACOperationMode.STANDBY
        self.controller_mode = HVACControllerMode.HEAT

        self._operation_modes = []
        if operation_modes is None:
            self._operation_modes = self.gather_operation_modes()
        else:
            for mode in operation_modes:
                if isinstance(mode, str):
                    self._operation_modes.append(HVACOperationMode(mode))
                elif isinstance(mode, HVACOperationMode):
                    self._operation_modes.append(mode)

        self._controller_modes = []
        if controller_modes is None:
            self._controller_modes = self.gather_controller_modes()
        else:
            for mode in controller_modes:
                if isinstance(mode, str):
                    self._controller_modes.append(HVACControllerMode(mode))
                elif isinstance(mode, HVACControllerMode):
                    self._controller_modes.append(mode)

        self.supports_operation_mode = any(
            operation_mode.initialized
            for operation_mode in self._iter_byte_operation_modes()) or any(
                operation_mode.initialized
                for operation_mode in self._iter_binary_operation_modes())
        self.supports_controller_mode = any(
            operation_mode.initialized
            for operation_mode in self._iter_controller_remote_values())

        self._use_binary_operation_modes = any(
            operation_mode.initialized
            for operation_mode in self._iter_binary_operation_modes())

    @classmethod
    def from_config(cls, xknx, name, config):
        """Initialize object from configuration structure."""
        # pylint: disable=too-many-locals
        group_address_operation_mode = config.get(
            "group_address_operation_mode")
        group_address_operation_mode_state = config.get(
            "group_address_operation_mode_state")
        group_address_operation_mode_protection = config.get(
            "group_address_operation_mode_protection")
        group_address_operation_mode_night = config.get(
            "group_address_operation_mode_night")
        group_address_operation_mode_comfort = config.get(
            "group_address_operation_mode_comfort")
        group_address_operation_mode_standby = config.get(
            "group_address_operation_mode_standby")
        group_address_controller_status = config.get(
            "group_address_controller_status")
        group_address_controller_status_state = config.get(
            "group_address_controller_status_state")
        group_address_controller_mode = config.get(
            "group_address_controller_mode")
        group_address_controller_mode_state = config.get(
            "group_address_controller_mode_state")
        group_address_heat_cool = config.get("group_address_heat_cool")
        group_address_heat_cool_state = config.get(
            "group_address_heat_cool_state")

        return cls(
            xknx,
            name,
            group_address_operation_mode=group_address_operation_mode,
            group_address_operation_mode_state=
            group_address_operation_mode_state,
            group_address_operation_mode_protection=
            group_address_operation_mode_protection,
            group_address_operation_mode_night=
            group_address_operation_mode_night,
            group_address_operation_mode_comfort=
            group_address_operation_mode_comfort,
            group_address_operation_mode_standby=
            group_address_operation_mode_standby,
            group_address_controller_status=group_address_controller_status,
            group_address_controller_status_state=
            group_address_controller_status_state,
            group_address_controller_mode=group_address_controller_mode,
            group_address_controller_mode_state=
            group_address_controller_mode_state,
            group_address_heat_cool=group_address_heat_cool,
            group_address_heat_cool_state=group_address_heat_cool_state,
        )

    def _iter_remote_values(self):
        """Iterate climate mode RemoteValue classes."""
        return chain(
            self._iter_byte_operation_modes(),
            self._iter_controller_remote_values(),
            self._iter_binary_operation_modes(),
        )

    def _iter_byte_operation_modes(self):
        """Iterate normal DPT 20.102 operation mode remote values."""
        yield from (
            self.remote_value_operation_mode,
            self.remote_value_controller_status,
        )

    def _iter_controller_remote_values(self):
        """Iterate DPT 20.105 controller remote values."""
        yield from (
            self.remote_value_controller_mode,
            self.remote_value_heat_cool,
        )

    def _iter_binary_operation_modes(self):
        """Iterate DPT 1 binary operation modes."""
        yield from (
            self.remote_value_operation_mode_comfort,
            self.remote_value_operation_mode_night,
            self.remote_value_operation_mode_protection,
            self.remote_value_operation_mode_standby,
        )

    async def _set_internal_operation_mode(self, operation_mode):
        """Set internal value of operation mode. Call hooks if operation mode was changed."""
        if operation_mode != self.operation_mode:
            self.operation_mode = operation_mode
            await self.after_update()

    async def _set_internal_controller_mode(self, controller_mode):
        """Set internal value of controller mode. Call hooks if controller mode was changed."""
        if controller_mode != self.controller_mode:
            self.controller_mode = controller_mode
            await self.after_update()

    async def set_operation_mode(self, operation_mode):
        """Set the operation mode of a thermostat. Send new operation_mode to BUS and update internal state."""
        if (not self.supports_operation_mode
                or operation_mode not in self._operation_modes):
            raise DeviceIllegalValue("operation (preset) mode not supported",
                                     operation_mode)

        for rv in chain(self._iter_byte_operation_modes(),
                        self._iter_binary_operation_modes()):
            if rv.writable and operation_mode in rv.supported_operation_modes(
            ):
                await rv.set(operation_mode)

        await self._set_internal_operation_mode(operation_mode)

    async def set_controller_mode(self, controller_mode):
        """Set the controller mode of a thermostat. Send new controller mode to the bus and update internal state."""
        if (not self.supports_controller_mode
                or controller_mode not in self._controller_modes):
            raise DeviceIllegalValue("controller (HVAC) mode not supported",
                                     controller_mode)

        for rv in self._iter_controller_remote_values():
            if rv.writable and controller_mode in rv.supported_operation_modes(
            ):
                await rv.set(controller_mode)

        await self._set_internal_controller_mode(controller_mode)

    @property
    def operation_modes(self):
        """Return all configured operation modes."""
        if not self.supports_operation_mode:
            return []
        return self._operation_modes

    @property
    def controller_modes(self):
        """Return all configured controller modes."""
        if not self.supports_controller_mode:
            return []
        return self._controller_modes

    def gather_operation_modes(self):
        """Gather operation modes from RemoteValues."""
        operation_modes = []
        for rv in chain(self._iter_binary_operation_modes(),
                        self._iter_byte_operation_modes()):
            if rv.writable:
                operation_modes.extend(rv.supported_operation_modes())

        # remove duplicates
        return list(set(operation_modes))

    def gather_controller_modes(self):
        """Gather controller modes from RemoteValues."""
        operation_modes = []
        for rv in self._iter_controller_remote_values():
            if rv.writable:
                operation_modes.extend(rv.supported_operation_modes())

        # remove duplicates
        return list(set(operation_modes))

    async def process_group_write(self, telegram):
        """Process incoming and outgoing GROUP WRITE telegram."""
        if self.supports_operation_mode:
            for rv in self._iter_remote_values():
                if await rv.process(telegram):
                    #  ignore inactive RemoteValueBinaryOperationMode
                    if rv.value:
                        await self._set_internal_operation_mode(rv.value)
                        return

        if self.supports_controller_mode:
            for rv in self._iter_controller_remote_values():
                if await rv.process(telegram):
                    await self._set_internal_controller_mode(rv.value)
                    return

    def __str__(self):
        """Return object as readable string."""
        return ('<ClimateMode name="{}" '
                'operation_mode="{}" '
                'controller_mode="{}" '
                'controller_status="{}" '
                "/>".format(
                    self.name,
                    self.remote_value_operation_mode.group_addr_str(),
                    self.remote_value_controller_mode.group_addr_str(),
                    self.remote_value_controller_status.group_addr_str(),
                ))
 def test_from_knx_operation_mode_error(self):
     """Test from_knx function with invalid payload."""
     xknx = XKNX()
     with self.assertRaises(ConversionError):
         RemoteValueClimateMode(xknx, climate_mode_type=None)
Example #10
0
class ClimateMode(Device):
    """Class for managing the climate mode."""

    # pylint: disable=invalid-name,too-many-instance-attributes

    def __init__(self,
                 xknx,
                 name,
                 group_address_operation_mode=None,
                 group_address_operation_mode_state=None,
                 group_address_operation_mode_protection=None,
                 group_address_operation_mode_night=None,
                 group_address_operation_mode_comfort=None,
                 group_address_operation_mode_standby=None,
                 group_address_controller_status=None,
                 group_address_controller_status_state=None,
                 group_address_controller_mode=None,
                 group_address_controller_mode_state=None,
                 group_address_heat_cool=None,
                 group_address_heat_cool_state=None,
                 operation_modes=None,
                 device_updated_cb=None):
        """Initialize ClimateMode class."""
        # pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements
        super().__init__(xknx, name, device_updated_cb)

        self.remote_value_operation_mode = RemoteValueClimateMode(
            xknx,
            group_address=group_address_operation_mode,
            group_address_state=group_address_operation_mode_state,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.HVAC_MODE,
            after_update_cb=None)
        self.remote_value_controller_mode = RemoteValueClimateMode(
            xknx,
            group_address=group_address_controller_mode,
            group_address_state=group_address_controller_mode_state,
            sync_state=True,
            device_name=name,
            feature_name="Controller mode",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.
            HVAC_CONTR_MODE,
            after_update_cb=None)
        self.remote_value_controller_status = RemoteValueClimateMode(
            xknx,
            group_address=group_address_controller_status,
            group_address_state=group_address_controller_status_state,
            sync_state=True,
            device_name=name,
            feature_name="Controller status",
            climate_mode_type=RemoteValueClimateMode.ClimateModeType.
            CONTROLLER_STATUS,
            after_update_cb=None)

        self.remote_value_operation_mode_comfort = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_comfort,
            group_address_state=group_address_operation_mode_comfort,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Comfort",
            operation_mode=HVACOperationMode.COMFORT,
            after_update_cb=None)
        self.remote_value_operation_mode_standby = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_standby,
            group_address_state=group_address_operation_mode_standby,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Standby",
            operation_mode=HVACOperationMode.STANDBY,
            after_update_cb=None)
        self.remote_value_operation_mode_night = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_night,
            group_address_state=group_address_operation_mode_night,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Night",
            operation_mode=HVACOperationMode.NIGHT,
            after_update_cb=None)
        self.remote_value_operation_mode_protection = RemoteValueBinaryOperationMode(
            xknx,
            group_address=group_address_operation_mode_protection,
            group_address_state=group_address_operation_mode_protection,
            sync_state=True,
            device_name=name,
            feature_name="Operation mode Protection",
            operation_mode=HVACOperationMode.FROST_PROTECTION,
            after_update_cb=None)
        self.remote_value_heat_cool = RemoteValueBinaryHeatCool(
            xknx,
            group_address=group_address_heat_cool,
            group_address_state=group_address_heat_cool_state,
            sync_state=True,
            device_name=name,
            feature_name="Heat/Cool",
            operation_mode=HVACOperationMode.HEAT,
            after_update_cb=None)

        self.operation_mode = HVACOperationMode.STANDBY

        self.operation_modes_ = []
        if operation_modes is None:
            self.operation_modes_ = self.gather_operation_modes()
        else:
            for mode in operation_modes:
                if isinstance(mode, str):
                    self.operation_modes_.append(HVACOperationMode(mode))
                elif isinstance(mode, HVACOperationMode):
                    self.operation_modes_.append(mode)

        self.supports_operation_mode = \
            group_address_operation_mode is not None or \
            group_address_operation_mode_state is not None or \
            group_address_operation_mode_protection is not None or \
            group_address_operation_mode_night is not None or \
            group_address_operation_mode_comfort is not None or \
            group_address_operation_mode_standby is not None or \
            group_address_controller_status is not None or \
            group_address_controller_status_state is not None or \
            group_address_controller_mode is not None or \
            group_address_controller_mode_state is not None or \
            group_address_heat_cool is not None or \
            group_address_heat_cool_state is not None

    @classmethod
    def from_config(cls, xknx, name, config):
        """Initialize object from configuration structure."""
        # pylint: disable=too-many-locals
        group_address_operation_mode = config.get(
            'group_address_operation_mode')
        group_address_operation_mode_state = config.get(
            'group_address_operation_mode_state')
        group_address_operation_mode_protection = config.get(
            'group_address_operation_mode_protection')
        group_address_operation_mode_night = config.get(
            'group_address_operation_mode_night')
        group_address_operation_mode_comfort = config.get(
            'group_address_operation_mode_comfort')
        group_address_operation_mode_standby = config.get(
            'group_address_operation_mode_standby')
        group_address_controller_status = config.get(
            'group_address_controller_status')
        group_address_controller_status_state = config.get(
            'group_address_controller_status_state')
        group_address_controller_mode = config.get(
            'group_address_controller_mode')
        group_address_controller_mode_state = config.get(
            'group_address_controller_mode_state')
        group_address_heat_cool = config.get('group_address_heat_cool')
        group_address_heat_cool_state = config.get(
            'group_address_heat_cool_state')

        return cls(
            xknx,
            name,
            group_address_operation_mode=group_address_operation_mode,
            group_address_operation_mode_state=
            group_address_operation_mode_state,
            group_address_operation_mode_protection=
            group_address_operation_mode_protection,
            group_address_operation_mode_night=
            group_address_operation_mode_night,
            group_address_operation_mode_comfort=
            group_address_operation_mode_comfort,
            group_address_operation_mode_standby=
            group_address_operation_mode_standby,
            group_address_controller_status=group_address_controller_status,
            group_address_controller_status_state=
            group_address_controller_status_state,
            group_address_controller_mode=group_address_controller_mode,
            group_address_controller_mode_state=
            group_address_controller_mode_state,
            group_address_heat_cool=group_address_heat_cool,
            group_address_heat_cool_state=group_address_heat_cool_state)

    def _iter_remote_values(self):
        """Iterate climate mode RemoteValue classes."""
        yield from (self.remote_value_controller_mode,
                    self.remote_value_controller_status,
                    self.remote_value_operation_mode,
                    self.remote_value_operation_mode_comfort,
                    self.remote_value_operation_mode_night,
                    self.remote_value_operation_mode_protection,
                    self.remote_value_operation_mode_standby,
                    self.remote_value_heat_cool)

    async def _set_internal_operation_mode(self, operation_mode):
        """Set internal value of operation mode. Call hooks if operation mode was changed."""
        if operation_mode != self.operation_mode:
            self.operation_mode = operation_mode
            await self.after_update()

    async def set_operation_mode(self, operation_mode):
        """Set the operation mode of a thermostat. Send new operation_mode to BUS and update internal state."""
        if not self.supports_operation_mode or \
                operation_mode not in self.operation_modes_:
            raise DeviceIllegalValue("operation mode not supported",
                                     operation_mode)

        for rv in self._iter_remote_values():
            if rv.writable and \
                    operation_mode in rv.supported_operation_modes():
                await rv.set(operation_mode)

        await self._set_internal_operation_mode(operation_mode)

    @property
    def operation_modes(self):
        """Return all configured operation modes."""
        if not self.supports_operation_mode:
            return []
        return self.operation_modes_

    def gather_operation_modes(self):
        """Gather operation modes from RemoteValues."""
        operation_modes = []
        for rv in self._iter_remote_values():
            if rv.writable:
                operation_modes.extend(rv.supported_operation_modes())
        # remove duplicates
        return list(set(operation_modes))

    async def process_group_write(self, telegram):
        """Process incoming GROUP WRITE telegram."""
        if self.supports_operation_mode:
            for rv in self._iter_remote_values():
                if await rv.process(telegram):
                    # don't set when binary climate mode rv is False
                    if rv.value:
                        await self._set_internal_operation_mode(rv.value)
                        return
            # if no operation mode has been set and all binary operation modes are False
            await self._set_internal_operation_mode(HVACOperationMode.STANDBY)

    async def sync(self):
        """Read states of device from KNX bus."""
        if self.supports_operation_mode:
            for rv in self._iter_remote_values():
                await rv.read_state()

    def __str__(self):
        """Return object as readable string."""
        return '<ClimateMode name="{0}" ' \
            'operation_mode="{1}" ' \
            'controller_mode="{2}" ' \
            'controller_status="{3}" ' \
            '/>' \
            .format(
                self.name,
                self.remote_value_operation_mode.group_addr_str(),
                self.remote_value_controller_mode.group_addr_str(),
                self.remote_value_controller_status.group_addr_str(),)

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