File: dpt_251.py

package info (click to toggle)
python-xknx 3.10.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,044 kB
  • sloc: python: 40,087; javascript: 8,556; makefile: 32; sh: 12
file content (108 lines) | stat: -rw-r--r-- 3,721 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""Implementation of KNX RGBW color data point type."""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass

from .dpt import DPTComplex, DPTComplexData
from .payload import DPTArray, DPTBinary


@dataclass(slots=True)
class RGBWColor(DPTComplexData):
    """
    Representation of RGBW color.

    `red`: int 0..255; None if invalid
    `green`: int 0..255; None if invalid
    `blue`: int 0..255; None if invalid
    `white`: int 0..255; None if invalid
    """

    red: int | None = None
    green: int | None = None
    blue: int | None = None
    white: int | None = None

    @classmethod
    def from_dict(cls, data: Mapping[str, int]) -> RGBWColor:
        """Init from a dictionary."""
        result = {}
        for color in ("red", "green", "blue", "white"):
            try:
                value = data.get(color)
                result[color] = int(value) if value is not None else None
            except AttributeError as err:
                raise ValueError(f"Invalid value for color {color}: {err}") from err
            except ValueError as err:
                raise ValueError(f"Invalid value for color {color}: {err}") from err
        return cls(**result)

    def as_dict(self) -> dict[str, int | None]:
        """Create a JSON serializable dictionary."""
        return {
            "red": self.red,
            "green": self.green,
            "blue": self.blue,
            "white": self.white,
        }

    def __or__(self, other: RGBWColor) -> RGBWColor:
        """Merge two RGBWColor objects using only valid values."""
        return RGBWColor(
            red=other.red if other.red is not None else self.red,
            green=other.green if other.green is not None else self.green,
            blue=other.blue if other.blue is not None else self.blue,
            white=other.white if other.white is not None else self.white,
        )


class DPTColorRGBW(DPTComplex[RGBWColor]):
    """Abstraction for KNX 6 octet color RGBW (DPT 251.600)."""

    data_type = RGBWColor
    payload_type = DPTArray
    payload_length = 6
    dpt_main_number = 251
    dpt_sub_number = 600
    value_type = "color_rgbw"

    @classmethod
    def from_knx(cls, payload: DPTArray | DPTBinary) -> RGBWColor:
        """Parse/deserialize from KNX/IP raw data."""
        raw = cls.validate_payload(payload)
        red_valid = raw[5] >> 3 & 0b1
        green_valid = raw[5] >> 2 & 0b1
        blue_valid = raw[5] >> 1 & 0b1
        white_valid = raw[5] & 0b1
        return RGBWColor(
            red=raw[0] if red_valid else None,
            green=raw[1] if green_valid else None,
            blue=raw[2] if blue_valid else None,
            white=raw[3] if white_valid else None,
        )

    @classmethod
    def _to_knx(cls, value: RGBWColor) -> DPTArray:
        """Serialize to KNX/IP raw data."""
        for color in (value.red, value.green, value.blue, value.white):
            if color is None:
                continue
            if not 0 <= color <= 255:
                raise ValueError(f"Color value out of range 0..255: {value}")
        return DPTArray(
            (
                value.red if value.red is not None else 0x00,
                value.green if value.green is not None else 0x00,
                value.blue if value.blue is not None else 0x00,
                value.white if value.white is not None else 0x00,
                0x00,  # reserved
                (
                    (value.red is not None) << 3
                    | (value.green is not None) << 2
                    | (value.blue is not None) << 1
                    | (value.white is not None)
                ),
            )
        )