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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
"""Implementation of different KNX DPT HVAC Operation modes."""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any
from .dpt import DPTComplex, DPTComplexData, DPTEnum, DPTEnumData
from .dpt_1 import HeatCool
from .payload import DPTArray, DPTBinary
class HVACOperationMode(DPTEnumData):
"""Enum for the different KNX HVAC operation modes."""
AUTO = 0
COMFORT = 1
STANDBY = 2
ECONOMY = 3
BUILDING_PROTECTION = 4
class DPTHVACMode(DPTEnum[HVACOperationMode]):
"""
Abstraction for KNX HVAC mode.
DPT 20.102
"""
dpt_main_number = 20
dpt_sub_number = 102
value_type = "hvac_mode"
data_type = HVACOperationMode
@classmethod
def _to_knx(cls, value: HVACOperationMode) -> DPTArray:
"""Return the raw KNX value for an Enum member."""
return DPTArray(value.value)
class HVACControllerMode(DPTEnumData):
"""Enum for the different KNX HVAC controller modes."""
AUTO = 0
HEAT = 1
MORNING_WARMUP = 2
COOL = 3
NIGHT_PURGE = 4
PRECOOL = 5
OFF = 6
TEST = 7
EMERGENCY_HEAT = 8
FAN_ONLY = 9
FREE_COOL = 10
ICE = 11
MAXIMUM_HEATING_MODE = 12
ECONOMIC_HEAT_COOL_MODE = 13
DEHUMIDIFICATION = 14
CALIBRATION_MODE = 15
EMERGENCY_COOL_MODE = 16
EMERGENCY_STEAM_MODE = 17
NODEM = 20
class DPTHVACContrMode(DPTEnum[HVACControllerMode]):
"""
Abstraction for KNX HVAC controller mode.
DPT 20.105
"""
dpt_main_number = 20
dpt_sub_number = 105
value_type = "hvac_controller_mode"
data_type = HVACControllerMode
@classmethod
def _to_knx(cls, value: HVACControllerMode) -> DPTArray:
"""Return the raw KNX value for an Enum member."""
return DPTArray(value.value)
@dataclass(slots=True)
class HVACStatus(DPTComplexData):
"""Class for HVACStatus."""
mode: HVACOperationMode
dew_point: bool
heat_cool: HeatCool
inactive: bool
frost_alarm: bool
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> HVACStatus:
"""Init from a dictionary."""
try:
_mode = data["mode"]
_heat_cool = data["heat_cool"]
try:
mode = HVACOperationMode[_mode.upper()]
heat_cool = HeatCool[_heat_cool.upper()]
except AttributeError as err:
raise ValueError(
f"Invalid type for HVAC mode or heat_cool: {err}"
) from err
dew_point = data["dew_point"]
inactive = data["inactive"]
frost_alarm = data["frost_alarm"]
except KeyError as err:
raise ValueError(f"Missing required key: {err}") from err
if (
not isinstance(dew_point, bool)
or not isinstance(inactive, bool)
or not isinstance(frost_alarm, bool)
):
raise ValueError(
f"Invalid value for HVACStatus boolean fields: {dew_point=}, {inactive=}, {frost_alarm=}"
)
return cls(
mode=mode,
dew_point=dew_point,
heat_cool=heat_cool,
inactive=inactive,
frost_alarm=frost_alarm,
)
def as_dict(self) -> dict[str, str | bool]:
"""Create a JSON serializable dictionary."""
return {
"mode": self.mode.name.lower(),
"dew_point": self.dew_point,
"heat_cool": self.heat_cool.name.lower(),
"inactive": self.inactive,
"frost_alarm": self.frost_alarm,
}
class DPTHVACStatus(DPTComplex[HVACStatus]):
"""
Abstraction for KNX HVACStatus.
Non-standardised DP type (in accordance with KNX AN097 “Eberle Status Byte”).
"""
data_type = HVACStatus
payload_type = DPTArray
payload_length = 1
dpt_main_number = 20
dpt_sub_number = 60102
value_type = "hvac_status"
@classmethod
def from_knx(cls, payload: DPTArray | DPTBinary) -> HVACStatus:
"""Parse/deserialize from KNX/IP raw data."""
raw = cls.validate_payload(payload)[0]
mode: HVACOperationMode
if raw & 0x1:
mode = HVACOperationMode.COMFORT
elif raw & 0x2:
mode = HVACOperationMode.STANDBY
elif raw & 0x4:
mode = HVACOperationMode.ECONOMY
elif raw & 0x8:
mode = HVACOperationMode.BUILDING_PROTECTION
else:
mode = HVACOperationMode.AUTO # not sure if this is possible / valid
return HVACStatus(
mode=mode,
dew_point=bool(raw & 0x10),
heat_cool=HeatCool.HEAT if raw & 0x20 else HeatCool.COOL,
inactive=bool(raw & 0x40),
frost_alarm=bool(raw & 0x80),
)
@classmethod
def _to_knx(cls, value: HVACStatus) -> DPTArray:
"""Serialize to KNX/IP raw data."""
raw = 0
if value.mode is HVACOperationMode.COMFORT:
raw |= 0x1
elif value.mode is HVACOperationMode.STANDBY:
raw |= 0x2
elif value.mode is HVACOperationMode.ECONOMY:
raw |= 0x4
elif value.mode is HVACOperationMode.BUILDING_PROTECTION:
raw |= 0x8
if value.dew_point:
raw |= 0x10
if value.heat_cool is HeatCool.HEAT:
raw |= 0x20
if value.inactive:
raw |= 0x40
if value.frost_alarm:
raw |= 0x80
return DPTArray(raw)
|