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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
"""Implementation of Basic KNX Time."""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
import datetime
from typing import Any
from xknx.exceptions import ConversionError
from .dpt import DPTComplex, DPTComplexData, DPTEnumData
from .payload import DPTArray, DPTBinary
class KNXDay(DPTEnumData):
"""Enum for the different KNX days."""
NO_DAY = 0
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 3
THURSDAY = 4
FRIDAY = 5
SATURDAY = 6
SUNDAY = 7
@dataclass(slots=True)
class KNXTime(DPTComplexData):
"""Class for KNX Time."""
hour: int
minutes: int
seconds: int
day: KNXDay = KNXDay.NO_DAY
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> KNXTime:
"""Init from a dictionary."""
try:
hour = int(data["hour"])
minutes = int(data["minutes"])
seconds = int(data["seconds"])
day = KNXDay.parse(data.get("day", KNXDay.NO_DAY))
except (KeyError, TypeError, ValueError) as err:
raise ValueError(f"Invalid value for KNXTime: {err}") from err
return cls(hour=hour, minutes=minutes, seconds=seconds, day=day)
def as_dict(self) -> dict[str, int | str]:
"""Create a JSON serializable dictionary."""
return {
"hour": self.hour,
"minutes": self.minutes,
"seconds": self.seconds,
"day": self.day.name.lower(),
}
def as_time(self) -> datetime.time:
"""Return time object. Ignoring day field."""
return datetime.time(self.hour, self.minutes, self.seconds)
@classmethod
def from_time(cls, time: datetime.time) -> KNXTime:
"""Return KNXTime object from time object. Day field is set to NO_DAY."""
return cls(time.hour, time.minute, time.second)
class DPTTime(DPTComplex[KNXTime]):
"""
Abstraction for KNX 3 Octet Time.
DPT 10.001
"""
data_type = KNXTime
payload_type = DPTArray
payload_length = 3
dpt_main_number = 10
dpt_sub_number = 1
value_type = "time"
@classmethod
def from_knx(cls, payload: DPTArray | DPTBinary) -> KNXTime:
"""Parse/deserialize from KNX/IP raw data."""
raw = cls.validate_payload(payload)
weekday = (raw[0] & 0xE0) >> 5 # can not be out of range - 3 bits 0..7
hours = raw[0] & 0x1F
minutes = raw[1] & 0x3F
seconds = raw[2] & 0x3F
try:
DPTTime._test_range(hours, minutes, seconds)
except ValueError as err:
raise ConversionError(f"Could not parse {cls.dpt_name()}: {err}") from err
return KNXTime(
hour=hours, minutes=minutes, seconds=seconds, day=KNXDay(weekday)
)
@classmethod
def _to_knx(cls, value: KNXTime) -> DPTArray:
"""Serialize to KNX/IP raw data."""
try:
DPTTime._test_range(value.hour, value.minutes, value.seconds)
except ValueError as err:
raise ConversionError(
f"Could not serialize {cls.dpt_name()}: {err}"
) from err
return DPTArray(
(
value.day.value << 5 | value.hour,
value.minutes,
value.seconds,
)
)
@staticmethod
def _test_range(hour: int, minutes: int, seconds: int) -> None:
"""Test if values are in the correct value range."""
if not 0 <= hour <= 23:
raise ValueError(f"Hour out of range 0..23: {hour}")
if not 0 <= minutes <= 59:
raise ValueError(f"Minutes out of range 0..59: {minutes}")
if not 0 <= seconds <= 59:
raise ValueError(f"Seconds out of range 0..59: {seconds}")
|