Example #1
0
    def __init__(
        self,
        xknx: XKNX,
        name: str,
        group_address_long: GroupAddressesType | None = None,
        group_address_short: GroupAddressesType | None = None,
        group_address_stop: GroupAddressesType | None = None,
        group_address_position: GroupAddressesType | None = None,
        group_address_position_state: GroupAddressesType | None = None,
        group_address_angle: GroupAddressesType | None = None,
        group_address_angle_state: GroupAddressesType | None = None,
        group_address_locked_state: GroupAddressesType | None = None,
        travel_time_down: float = DEFAULT_TRAVEL_TIME_DOWN,
        travel_time_up: float = DEFAULT_TRAVEL_TIME_UP,
        invert_position: bool = False,
        invert_angle: bool = False,
        device_updated_cb: DeviceCallbackType | None = None,
        device_class: str | None = None,
    ):
        """Initialize Cover class."""
        super().__init__(xknx, name, device_updated_cb)
        # self.after_update for position changes is called after updating the
        # travelcalculator (in process_group_write and set_*) - angle changes
        # are updated from RemoteValue objects
        self.updown = RemoteValueUpDown(
            xknx,
            group_address_long,
            device_name=self.name,
            after_update_cb=None,
            invert=invert_position,
        )

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

        self.stop_ = RemoteValueSwitch(
            xknx,
            group_address=group_address_stop,
            device_name=self.name,
            after_update_cb=None,
        )

        position_range_from = 100 if invert_position else 0
        position_range_to = 0 if invert_position else 100
        self.position_current = RemoteValueScaling(
            xknx,
            group_address_state=group_address_position_state,
            device_name=self.name,
            feature_name="Position",
            after_update_cb=self._current_position_from_rv,
            range_from=position_range_from,
            range_to=position_range_to,
        )
        self.position_target = RemoteValueScaling(
            xknx,
            group_address=group_address_position,
            device_name=self.name,
            feature_name="Target position",
            after_update_cb=self._target_position_from_rv,
            range_from=position_range_from,
            range_to=position_range_to,
        )

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

        self.locked = RemoteValueSwitch(
            xknx,
            group_address_state=group_address_locked_state,
            device_name=self.name,
            feature_name="Locked",
            after_update_cb=self.after_update,
        )

        self.travel_time_down = travel_time_down
        self.travel_time_up = travel_time_up

        self.travelcalculator = TravelCalculator(travel_time_down,
                                                 travel_time_up)
        self.travel_direction_tilt: TravelStatus | None = None

        self.device_class = device_class
Example #2
0
    def __init__(
        self,
        xknx: XKNX,
        name: str,
        group_address_switch: GroupAddressesType | None = None,
        group_address_switch_state: GroupAddressesType | None = None,
        group_address_brightness: GroupAddressesType | None = None,
        group_address_brightness_state: GroupAddressesType | None = None,
        group_address_color: GroupAddressesType | None = None,
        group_address_color_state: GroupAddressesType | None = None,
        group_address_rgbw: GroupAddressesType | None = None,
        group_address_rgbw_state: GroupAddressesType | None = None,
        group_address_xyy_color: GroupAddressesType | None = None,
        group_address_xyy_color_state: GroupAddressesType | None = None,
        group_address_tunable_white: GroupAddressesType | None = None,
        group_address_tunable_white_state: GroupAddressesType | None = None,
        group_address_color_temperature: GroupAddressesType | None = None,
        group_address_color_temperature_state: GroupAddressesType | None = None,
        group_address_switch_red: GroupAddressesType | None = None,
        group_address_switch_red_state: GroupAddressesType | None = None,
        group_address_brightness_red: GroupAddressesType | None = None,
        group_address_brightness_red_state: GroupAddressesType | None = None,
        group_address_switch_green: GroupAddressesType | None = None,
        group_address_switch_green_state: GroupAddressesType | None = None,
        group_address_brightness_green: GroupAddressesType | None = None,
        group_address_brightness_green_state: GroupAddressesType | None = None,
        group_address_switch_blue: GroupAddressesType | None = None,
        group_address_switch_blue_state: GroupAddressesType | None = None,
        group_address_brightness_blue: GroupAddressesType | None = None,
        group_address_brightness_blue_state: GroupAddressesType | None = None,
        group_address_switch_white: GroupAddressesType | None = None,
        group_address_switch_white_state: GroupAddressesType | None = None,
        group_address_brightness_white: GroupAddressesType | None = None,
        group_address_brightness_white_state: GroupAddressesType | None = None,
        min_kelvin: int | None = None,
        max_kelvin: int | None = None,
        device_updated_cb: DeviceCallbackType | None = None,
    ):
        """Initialize Light class."""
        super().__init__(xknx, name, device_updated_cb)

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

        self.brightness = RemoteValueScaling(
            xknx,
            group_address_brightness,
            group_address_brightness_state,
            device_name=self.name,
            feature_name="Brightness",
            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._xyy_color_valid: XYYColor | None = None
        self.xyy_color = RemoteValueColorXYY(
            xknx,
            group_address_xyy_color,
            group_address_xyy_color_state,
            device_name=self.name,
            after_update_cb=self._xyy_color_from_rv,
        )

        self.tunable_white = RemoteValueScaling(
            xknx,
            group_address_tunable_white,
            group_address_tunable_white_state,
            device_name=self.name,
            feature_name="Tunable white",
            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,
            feature_name="Color temperature",
            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
Example #3
0
class Fan(Device):
    """Class for managing a fan."""
    def __init__(
        self,
        xknx: XKNX,
        name: str,
        group_address_speed: GroupAddressesType | None = None,
        group_address_speed_state: GroupAddressesType | None = None,
        group_address_oscillation: GroupAddressesType | None = None,
        group_address_oscillation_state: GroupAddressesType | None = None,
        device_updated_cb: DeviceCallbackType | None = None,
        max_step: int | None = None,
    ):
        """Initialize fan class."""
        super().__init__(xknx, name, device_updated_cb)

        self.speed: RemoteValueDptValue1Ucount | RemoteValueScaling
        self.mode = FanSpeedMode.STEP if max_step else FanSpeedMode.PERCENT
        self.max_step = max_step

        if self.mode == FanSpeedMode.STEP:
            self.speed = RemoteValueDptValue1Ucount(
                xknx,
                group_address_speed,
                group_address_speed_state,
                device_name=self.name,
                feature_name="Speed",
                after_update_cb=self.after_update,
            )
        else:
            self.speed = RemoteValueScaling(
                xknx,
                group_address_speed,
                group_address_speed_state,
                device_name=self.name,
                feature_name="Speed",
                after_update_cb=self.after_update,
                range_from=0,
                range_to=100,
            )

        self.oscillation = RemoteValueSwitch(
            xknx,
            group_address_oscillation,
            group_address_oscillation_state,
            device_name=self.name,
            feature_name="Oscillation",
            after_update_cb=self.after_update,
        )

    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
        """Iterate the devices RemoteValue classes."""
        yield from (self.speed, self.oscillation)

    @property
    def supports_oscillation(self) -> bool:
        """Return if fan supports oscillation."""
        return self.oscillation.initialized

    def __str__(self) -> str:
        """Return object as readable string."""

        str_oscillation = ("" if not self.supports_oscillation else
                           f" oscillation={self.oscillation.group_addr_str()}")

        return '<Fan name="{}" speed={}{} />'.format(
            self.name, self.speed.group_addr_str(), str_oscillation)

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

    async def set_oscillation(self, oscillation: bool) -> None:
        """Set the fan oscillation mode on or off."""
        await self.oscillation.set(oscillation)

    async def process_group_write(self, telegram: "Telegram") -> None:
        """Process incoming and outgoing GROUP WRITE telegram."""
        await self.speed.process(telegram)
        await self.oscillation.process(telegram)

    @property
    def current_speed(self) -> int | None:
        """Return current speed of fan."""
        return self.speed.value

    @property
    def current_oscillation(self) -> bool | None:
        """Return true if the fan is oscillating."""
        return self.oscillation.value
Example #4
0
class Cover(Device):
    """Class for managing a cover."""

    DEFAULT_TRAVEL_TIME_DOWN = 22
    DEFAULT_TRAVEL_TIME_UP = 22

    def __init__(
        self,
        xknx: XKNX,
        name: str,
        group_address_long: GroupAddressesType | None = None,
        group_address_short: GroupAddressesType | None = None,
        group_address_stop: GroupAddressesType | None = None,
        group_address_position: GroupAddressesType | None = None,
        group_address_position_state: GroupAddressesType | None = None,
        group_address_angle: GroupAddressesType | None = None,
        group_address_angle_state: GroupAddressesType | None = None,
        group_address_locked_state: GroupAddressesType | None = None,
        travel_time_down: float = DEFAULT_TRAVEL_TIME_DOWN,
        travel_time_up: float = DEFAULT_TRAVEL_TIME_UP,
        invert_position: bool = False,
        invert_angle: bool = False,
        device_updated_cb: DeviceCallbackType | None = None,
        device_class: str | None = None,
    ):
        """Initialize Cover class."""
        super().__init__(xknx, name, device_updated_cb)
        # self.after_update for position changes is called after updating the
        # travelcalculator (in process_group_write and set_*) - angle changes
        # are updated from RemoteValue objects
        self.updown = RemoteValueUpDown(
            xknx,
            group_address_long,
            device_name=self.name,
            after_update_cb=None,
            invert=invert_position,
        )

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

        self.stop_ = RemoteValueSwitch(
            xknx,
            group_address=group_address_stop,
            device_name=self.name,
            after_update_cb=None,
        )

        position_range_from = 100 if invert_position else 0
        position_range_to = 0 if invert_position else 100
        self.position_current = RemoteValueScaling(
            xknx,
            group_address_state=group_address_position_state,
            device_name=self.name,
            feature_name="Position",
            after_update_cb=self._current_position_from_rv,
            range_from=position_range_from,
            range_to=position_range_to,
        )
        self.position_target = RemoteValueScaling(
            xknx,
            group_address=group_address_position,
            device_name=self.name,
            feature_name="Target position",
            after_update_cb=self._target_position_from_rv,
            range_from=position_range_from,
            range_to=position_range_to,
        )

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

        self.locked = RemoteValueSwitch(
            xknx,
            group_address_state=group_address_locked_state,
            device_name=self.name,
            feature_name="Locked",
            after_update_cb=self.after_update,
        )

        self.travel_time_down = travel_time_down
        self.travel_time_up = travel_time_up

        self.travelcalculator = TravelCalculator(travel_time_down,
                                                 travel_time_up)
        self.travel_direction_tilt: TravelStatus | None = None

        self.device_class = device_class

    def _iter_remote_values(self) -> Iterator[RemoteValue[Any, Any]]:
        """Iterate the devices RemoteValue classes."""
        yield self.updown
        yield self.step
        yield self.stop_
        yield self.position_current
        yield self.position_target
        yield self.angle
        yield self.locked

    @property
    def unique_id(self) -> str | None:
        """Return unique id for this device."""
        return f"{self.updown.group_address}"

    def __str__(self) -> str:
        """Return object as readable string."""
        return ('<Cover name="{}" '
                "updown={} "
                "step={} "
                "stop_={} "
                "position_current={} "
                "position_target={} "
                "angle={} "
                "locked={} "
                'travel_time_down="{}" '
                'travel_time_up="{}" />'.format(
                    self.name,
                    self.updown.group_addr_str(),
                    self.step.group_addr_str(),
                    self.stop_.group_addr_str(),
                    self.position_current.group_addr_str(),
                    self.position_target.group_addr_str(),
                    self.angle.group_addr_str(),
                    self.locked.group_addr_str(),
                    self.travel_time_down,
                    self.travel_time_up,
                ))

    async def set_down(self) -> None:
        """Move cover down."""
        await self.updown.down()
        self.travelcalculator.start_travel_down()
        self.travel_direction_tilt = None
        await self.after_update()

    async def set_up(self) -> None:
        """Move cover up."""
        await self.updown.up()
        self.travelcalculator.start_travel_up()
        self.travel_direction_tilt = None
        await self.after_update()

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

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

    async def stop(self) -> None:
        """Stop cover."""
        if self.stop_.writable:
            await self.stop_.on()
        elif self.step.writable:
            if (self.travel_direction_tilt == TravelStatus.DIRECTION_UP
                    or self.travelcalculator.travel_direction
                    == TravelStatus.DIRECTION_UP):
                await self.step.decrease()
            elif (self.travel_direction_tilt == TravelStatus.DIRECTION_DOWN
                  or self.travelcalculator.travel_direction
                  == TravelStatus.DIRECTION_DOWN):
                await self.step.increase()
        else:
            logger.warning("Stop not supported for device %s", self.get_name())
            return
        self.travelcalculator.stop()
        self.travel_direction_tilt = None
        await self.after_update()

    async def set_position(self, position: int) -> None:
        """Move cover to a desginated postion."""
        if not self.position_target.writable:
            # No direct positioning group address defined
            # fully open or close is always possible even if current position is not known
            current_position = self.current_position()
            if current_position is None:
                if position == self.travelcalculator.position_open:
                    await self.updown.up()
                elif position == self.travelcalculator.position_closed:
                    await self.updown.down()
                else:
                    logger.warning(
                        "Current position unknown. Initialize cover by moving to end position."
                    )
                    return
            elif position < current_position:
                await self.updown.up()
            elif position > current_position:
                await self.updown.down()
            self.travelcalculator.start_travel(position)
            await self.after_update()
        else:
            await self.position_target.set(position)

    async def _target_position_from_rv(self) -> None:
        """Update the target postion from RemoteValue (Callback)."""
        new_target = self.position_target.value
        if new_target is not None:
            self.travelcalculator.start_travel(new_target)
            await self.after_update()

    async def _current_position_from_rv(self) -> None:
        """Update the current postion from RemoteValue (Callback)."""
        position_before_update = self.travelcalculator.current_position()
        new_position = self.position_current.value
        if new_position is not None:
            if self.is_traveling():
                self.travelcalculator.update_position(new_position)
            else:
                self.travelcalculator.set_position(new_position)
            if position_before_update != self.travelcalculator.current_position(
            ):
                await self.after_update()

    async def set_angle(self, angle: int) -> None:
        """Move cover to designated angle."""
        if not self.supports_angle:
            logger.warning("Angle not supported for device %s",
                           self.get_name())
            return

        current_angle = self.current_angle()
        self.travel_direction_tilt = (
            TravelStatus.DIRECTION_DOWN if current_angle is not None
            and angle >= current_angle else TravelStatus.DIRECTION_UP)

        await self.angle.set(angle)

    async def auto_stop_if_necessary(self) -> None:
        """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 (self.supports_stop and not self.position_target.writable
                and self.position_reached() and not self.is_open()
                and not self.is_closed()):
            await self.stop()

    async def sync(self, wait_for_result: bool = False) -> None:
        """Read states of device from KNX bus."""
        await self.position_current.read_state(wait_for_result=wait_for_result)
        await self.angle.read_state(wait_for_result=wait_for_result)

    async def process_group_write(self, telegram: "Telegram") -> None:
        """Process incoming and outgoing GROUP WRITE telegram."""
        # call after_update to account for travelcalculator changes
        if await self.updown.process(telegram):
            if (not self.is_opening()
                    and self.updown.value == RemoteValueUpDown.Direction.UP):
                self.travelcalculator.start_travel_up()
                await self.after_update()
            elif (not self.is_closing()
                  and self.updown.value == RemoteValueUpDown.Direction.DOWN):
                self.travelcalculator.start_travel_down()
                await self.after_update()
        # stop from bus
        if await self.stop_.process(telegram) or await self.step.process(
                telegram):
            if self.is_traveling():
                self.travelcalculator.stop()
                await self.after_update()

        await self.position_current.process(telegram, always_callback=True)
        await self.position_target.process(telegram)
        await self.angle.process(telegram)
        await self.locked.process(telegram)

    def current_position(self) -> int | None:
        """Return current position of cover."""
        return self.travelcalculator.current_position()

    def current_angle(self) -> int | None:
        """Return current tilt angle of cover."""
        return self.angle.value

    def is_locked(self) -> bool | None:
        """Return if the cover is currently locked for manual movement."""
        return self.locked.value

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

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

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

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

    def is_opening(self) -> bool:
        """Return if the cover is opening or not."""
        return self.travelcalculator.is_opening()

    def is_closing(self) -> bool:
        """Return if the cover is closing or not."""
        return self.travelcalculator.is_closing()

    @property
    def supports_stop(self) -> bool:
        """Return if cover supports manual stopping."""
        return self.stop_.writable or self.step.writable

    @property
    def supports_locked(self) -> bool:
        """Return if cover supports locking."""
        return self.locked.initialized

    @property
    def supports_position(self) -> bool:
        """Return if cover supports direct positioning."""
        return self.position_target.initialized

    @property
    def supports_angle(self) -> bool:
        """Return if cover supports tilt angle."""
        return self.angle.initialized
Example #5
0
    def test_calc_100_0(self):
        """Test if from/to calculations work with negative range."""
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 0), 255)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 1), 252)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 2), 250)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 3), 247)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 30), 178)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 50), 128)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 70), 76)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 97), 8)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 98), 5)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 99), 3)
        self.assertEqual(RemoteValueScaling._calc_to_knx(100, 0, 100), 0)

        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 0), 100)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 1), 100)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 2), 99)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 3), 99)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 4), 98)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 5), 98)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 76), 70)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 128), 50)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 178), 30)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 251), 2)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 252), 1)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 253), 1)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 254), 0)
        self.assertEqual(RemoteValueScaling._calc_from_knx(100, 0, 255), 0)
Example #6
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,
                                        feature_name="Speed",
                                        after_update_cb=self.after_update,
                                        range_from=0,
                                        range_to=100)

    def _iter_remote_values(self):
        """Iterate the devices RemoteValue classes."""
        yield self.speed

    @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 __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())

    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__
Example #7
0
    def test_calc_0_100(self):
        """Test if from/to calculations work range 0-100 with many test cases."""
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 0), 0)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 1), 3)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 2), 5)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 3), 8)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 30), 76)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 50), 128)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 70), 178)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 97), 247)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 98), 250)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 99), 252)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 100, 100), 255)

        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 0), 0)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 1), 0)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 2), 1)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 3), 1)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 4), 2)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 5), 2)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 76), 30)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 128), 50)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 178), 70)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 251), 98)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 252), 99)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 253), 99)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 254), 100)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 100, 255), 100)
Example #8
0
    def test_calc_0_1000(self):
        """Test if from/to calculations work with large range."""
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 0), 0)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 1), 0)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 2), 1)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 3), 1)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 500), 128)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 997), 254)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 998), 254)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 999), 255)
        self.assertEqual(RemoteValueScaling._calc_to_knx(0, 1000, 1000), 255)

        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 0), 0)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 1), 4)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 2), 8)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 128), 502)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 251), 984)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 252), 988)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 253), 992)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 254), 996)
        self.assertEqual(RemoteValueScaling._calc_from_knx(0, 1000, 255), 1000)
Example #9
0
 def test_value_unit(self):
     """Test for the unit_of_measurement."""
     xknx = XKNX(loop=self.loop)
     remote_value = RemoteValueScaling(xknx)
     self.assertEqual(remote_value.unit_of_measurement, "%")
Example #10
0
    def __init__(
        self,
        xknx: XKNX,
        name: str,
        group_address_speed: GroupAddressesType | None = None,
        group_address_speed_state: GroupAddressesType | None = None,
        group_address_oscillation: GroupAddressesType | None = None,
        group_address_oscillation_state: GroupAddressesType | None = None,
        group_address_switch: GroupAddressesType | None = None,
        group_address_switch_state: GroupAddressesType | None = None,
        sync_state: bool | int | float | str = True,
        device_updated_cb: DeviceCallbackType[Fan] | None = None,
        max_step: int | None = None,
    ):
        """Initialize fan class."""
        super().__init__(xknx, name, device_updated_cb)

        self.speed: RemoteValueDptValue1Ucount | RemoteValueScaling
        self.mode = FanSpeedMode.STEP if max_step else FanSpeedMode.PERCENT
        self.max_step = max_step

        # If there is a dedicated switch GA, it controls the on/off behavior of the fan.
        # Otherwise the speed GA of the fan implicitly controls the on/off behavior instead.
        # `self.switch.initialized`` can be used to check which setup is used.
        self.switch = RemoteValueSwitch(
            xknx,
            group_address_switch,
            group_address_switch_state,
            sync_state=sync_state,
            device_name=self.name,
            feature_name="Switch",
            after_update_cb=self.after_update,
        )

        if self.mode == FanSpeedMode.STEP:
            self.speed = RemoteValueDptValue1Ucount(
                xknx,
                group_address_speed,
                group_address_speed_state,
                sync_state=sync_state,
                device_name=self.name,
                feature_name="Speed",
                after_update_cb=self.after_update,
            )
        else:
            self.speed = RemoteValueScaling(
                xknx,
                group_address_speed,
                group_address_speed_state,
                sync_state=sync_state,
                device_name=self.name,
                feature_name="Speed",
                after_update_cb=self.after_update,
                range_from=0,
                range_to=100,
            )

        self.oscillation = RemoteValueSwitch(
            xknx,
            group_address_oscillation,
            group_address_oscillation_state,
            sync_state=sync_state,
            device_name=self.name,
            feature_name="Oscillation",
            after_update_cb=self.after_update,
        )
Example #11
0
File: light.py Project: cian/xknx
    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,
            feature_name="State",
            after_update_cb=self.after_update,
        )

        self.brightness = RemoteValueScaling(
            xknx,
            group_address_brightness,
            group_address_brightness_state,
            device_name=self.name,
            feature_name="Brightness",
            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,
            feature_name="Tunable white",
            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,
            feature_name="Color temperature",
            after_update_cb=self.after_update,
        )

        self.min_kelvin = min_kelvin
        self.max_kelvin = max_kelvin
Example #12
0
File: light.py Project: 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__
Example #13
0
    def __init__(
        self,
        xknx: "XKNX",
        name: str,
        group_address_switch: Optional["GroupAddressableType"] = None,
        group_address_switch_state: Optional["GroupAddressableType"] = None,
        group_address_brightness: Optional["GroupAddressableType"] = None,
        group_address_brightness_state: Optional[
            "GroupAddressableType"] = None,
        group_address_color: Optional["GroupAddressableType"] = None,
        group_address_color_state: Optional["GroupAddressableType"] = None,
        group_address_rgbw: Optional["GroupAddressableType"] = None,
        group_address_rgbw_state: Optional["GroupAddressableType"] = None,
        group_address_tunable_white: Optional["GroupAddressableType"] = None,
        group_address_tunable_white_state: Optional[
            "GroupAddressableType"] = None,
        group_address_color_temperature: Optional[
            "GroupAddressableType"] = None,
        group_address_color_temperature_state: Optional[
            "GroupAddressableType"] = None,
        group_address_switch_red: Optional["GroupAddressableType"] = None,
        group_address_switch_red_state: Optional[
            "GroupAddressableType"] = None,
        group_address_brightness_red: Optional["GroupAddressableType"] = None,
        group_address_brightness_red_state: Optional[
            "GroupAddressableType"] = None,
        group_address_switch_green: Optional["GroupAddressableType"] = None,
        group_address_switch_green_state: Optional[
            "GroupAddressableType"] = None,
        group_address_brightness_green: Optional[
            "GroupAddressableType"] = None,
        group_address_brightness_green_state: Optional[
            "GroupAddressableType"] = None,
        group_address_switch_blue: Optional["GroupAddressableType"] = None,
        group_address_switch_blue_state: Optional[
            "GroupAddressableType"] = None,
        group_address_brightness_blue: Optional["GroupAddressableType"] = None,
        group_address_brightness_blue_state: Optional[
            "GroupAddressableType"] = None,
        group_address_switch_white: Optional["GroupAddressableType"] = None,
        group_address_switch_white_state: Optional[
            "GroupAddressableType"] = None,
        group_address_brightness_white: Optional[
            "GroupAddressableType"] = None,
        group_address_brightness_white_state: Optional[
            "GroupAddressableType"] = None,
        min_kelvin: Optional[int] = None,
        max_kelvin: Optional[int] = None,
        device_updated_cb: Optional[DeviceCallbackType] = 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,
            feature_name="State",
            after_update_cb=self.after_update,
        )

        self.brightness = RemoteValueScaling(
            xknx,
            group_address_brightness,
            group_address_brightness_state,
            device_name=self.name,
            feature_name="Brightness",
            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,
            feature_name="Tunable white",
            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,
            feature_name="Color temperature",
            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