File: my_zone.py

package info (click to toggle)
python-tado 0.19.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 720 kB
  • sloc: python: 2,671; sh: 29; makefile: 3
file content (215 lines) | stat: -rw-r--r-- 8,857 bytes parent folder | download
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"""
Adapter to represent a tado zones and state for my.tado.com API.
"""

import dataclasses
import logging
from typing import Any, Self

from PyTado.const import (
    CONST_FAN_AUTO,
    CONST_FAN_OFF,
    CONST_FAN_SPEED_AUTO,
    CONST_FAN_SPEED_OFF,
    CONST_HORIZONTAL_SWING_OFF,
    CONST_HVAC_COOL,
    CONST_HVAC_HEAT,
    CONST_HVAC_IDLE,
    CONST_HVAC_OFF,
    CONST_LINK_OFFLINE,
    CONST_MODE_OFF,
    CONST_MODE_SMART_SCHEDULE,
    CONST_VERTICAL_SWING_OFF,
    DEFAULT_TADO_PRECISION,
    TADO_HVAC_ACTION_TO_MODES,
    TADO_MODES_TO_HVAC_ACTION,
    TYPE_AIR_CONDITIONING,
)

_LOGGER = logging.getLogger(__name__)


@dataclasses.dataclass(frozen=True, kw_only=True)
class TadoZone:
    """Tado Zone data structure for my.tado.com."""

    zone_id: int
    current_temp: float | None = None
    current_temp_timestamp: str | None = None
    current_humidity: float | None = None
    current_humidity_timestamp: str | None = None
    is_away: bool | None = None
    current_hvac_action: str = CONST_HVAC_OFF
    current_fan_speed: str | None = None
    current_fan_level: str | None = None
    current_hvac_mode: str | None = None
    current_swing_mode: str | None = None
    current_vertical_swing_mode: str | None = None
    current_horizontal_swing_mode: str | None = None
    target_temp: float | None = None
    available: bool = False
    power: str | None = None
    link: str | None = None
    connection: str | None = None
    ac_power_timestamp: str | None = None
    heating_power_timestamp: str | None = None
    ac_power: str | None = None
    heating_power: str | None = None
    heating_power_percentage: float | None = None
    tado_mode: str | None = None
    overlay_termination_type: str | None = None
    overlay_termination_timestamp: str | None = None
    default_overlay_termination_type: str | None = None
    default_overlay_termination_duration: str | None = None
    preparation: bool = False
    open_window: bool = False
    open_window_detected: bool = False
    open_window_attr: dict[str, Any] = dataclasses.field(default_factory=dict)
    precision: float = DEFAULT_TADO_PRECISION

    @property
    def overlay_active(self) -> bool:
        """Overlay active."""
        return self.current_hvac_mode != CONST_MODE_SMART_SCHEDULE

    @classmethod
    def from_data(cls, zone_id: int, data: dict[str, Any]) -> Self:
        """Handle update callbacks."""
        _LOGGER.debug("Processing data from zone %d", zone_id)
        kwargs: dict[str, Any] = {}
        if "sensorDataPoints" in data:
            sensor_data = data["sensorDataPoints"]

            if "insideTemperature" in sensor_data:
                kwargs["current_temp"] = float(sensor_data["insideTemperature"]["celsius"])
                kwargs["current_temp_timestamp"] = sensor_data["insideTemperature"]["timestamp"]
                if "precision" in sensor_data["insideTemperature"]:
                    kwargs["precision"] = sensor_data["insideTemperature"]["precision"]["celsius"]

            if "humidity" in sensor_data:
                humidity = float(sensor_data["humidity"]["percentage"])
                kwargs["current_humidity"] = humidity
                kwargs["current_humidity_timestamp"] = sensor_data["humidity"]["timestamp"]

        if "tadoMode" in data:
            kwargs["is_away"] = data["tadoMode"] == "AWAY"
            kwargs["tado_mode"] = data["tadoMode"]

        if "link" in data:
            kwargs["link"] = data["link"]["state"]

        if "connection" in data:
            kwargs["connection"] = data["connection"]["state"]

        if "setting" in data:
            # temperature setting will not exist when device is off
            if "temperature" in data["setting"] and data["setting"]["temperature"] is not None:
                kwargs["target_temp"] = float(data["setting"]["temperature"]["celsius"])

            setting = data["setting"]

            # Reset modes and settings
            kwargs.update(
                {
                    "current_fan_speed": None,
                    "current_fan_level": None,
                    "current_hvac_mode": CONST_MODE_OFF,
                    "current_swing_mode": CONST_MODE_OFF,
                    "current_vertical_swing_mode": CONST_VERTICAL_SWING_OFF,
                    "current_horizontal_swing_mode": CONST_HORIZONTAL_SWING_OFF,
                }
            )

            if "mode" in setting:
                # v3 devices use mode
                kwargs["current_hvac_mode"] = setting["mode"]

            if "swing" in setting:
                kwargs["current_swing_mode"] = setting["swing"]

            if "verticalSwing" in setting:
                kwargs["current_vertical_swing_mode"] = setting["verticalSwing"]

            if "horizontalSwing" in setting:
                kwargs["current_horizontal_swing_mode"] = setting["horizontalSwing"]

            power = setting["power"]
            kwargs["power"] = power
            if power == "ON":
                kwargs["current_hvac_action"] = CONST_HVAC_IDLE
                if (
                    "mode" not in setting
                    and "type" in setting
                    and setting["type"] in TADO_HVAC_ACTION_TO_MODES
                ):
                    # v2 devices do not have mode so we have to figure it out
                    # from type
                    kwargs["current_hvac_mode"] = TADO_HVAC_ACTION_TO_MODES[setting["type"]]

            # Not all devices have fans
            if "fanSpeed" in setting:
                kwargs["current_fan_speed"] = setting.get(
                    "fanSpeed",
                    CONST_FAN_AUTO if power == "ON" else CONST_FAN_OFF,
                )
            elif "type" in setting and setting["type"] == TYPE_AIR_CONDITIONING:
                kwargs["current_fan_speed"] = CONST_FAN_AUTO if power == "ON" else CONST_FAN_OFF

            if "fanLevel" in setting:
                kwargs["current_fan_level"] = setting.get(
                    "fanLevel",
                    (CONST_FAN_SPEED_AUTO if power == "ON" else CONST_FAN_SPEED_OFF),
                )

        kwargs["preparation"] = "preparation" in data and data["preparation"] is not None
        open_window = data.get("openWindow") is not None
        kwargs["open_window"] = open_window
        kwargs["open_window_detected"] = data.get("openWindowDetected", False)
        kwargs["open_window_attr"] = data.get("openWindow") or {}

        if "activityDataPoints" in data:
            activity_data = data["activityDataPoints"]
            if "acPower" in activity_data and activity_data["acPower"] is not None:
                kwargs["ac_power"] = activity_data["acPower"]["value"]
                kwargs["ac_power_timestamp"] = activity_data["acPower"]["timestamp"]
                if activity_data["acPower"]["value"] == "ON" and power == "ON":
                    # acPower means the unit has power so we need to map the
                    # mode
                    kwargs["current_hvac_action"] = TADO_MODES_TO_HVAC_ACTION.get(
                        kwargs["current_hvac_mode"], CONST_HVAC_COOL
                    )
            if "heatingPower" in activity_data and activity_data["heatingPower"] is not None:
                kwargs["heating_power"] = activity_data["heatingPower"].get("value", None)
                kwargs["heating_power_timestamp"] = activity_data["heatingPower"]["timestamp"]
                kwargs["heating_power_percentage"] = float(
                    activity_data["heatingPower"].get("percentage", 0)
                )

                if kwargs["heating_power_percentage"] > 0.0 and power == "ON":
                    kwargs["current_hvac_action"] = CONST_HVAC_HEAT

        # If there is no overlay
        # then we are running the smart schedule
        if "overlay" in data and data["overlay"] is not None:
            if "termination" in data["overlay"] and "type" in data["overlay"]["termination"]:
                kwargs["overlay_termination_type"] = data["overlay"]["termination"]["type"]
                kwargs["overlay_termination_timestamp"] = data["overlay"]["termination"].get(
                    "expiry", None
                )
        else:
            kwargs["current_hvac_mode"] = CONST_MODE_SMART_SCHEDULE

        kwargs["connection"] = (
            data["connectionState"]["value"] if "connectionState" in data else None
        )
        kwargs["available"] = kwargs["link"] != CONST_LINK_OFFLINE

        if "terminationCondition" in data:
            kwargs["default_overlay_termination_type"] = data["terminationCondition"].get(
                "type", None
            )
            kwargs["default_overlay_termination_duration"] = data["terminationCondition"].get(
                "durationInSeconds", None
            )

        return cls(zone_id=zone_id, **kwargs)