-
-
Notifications
You must be signed in to change notification settings - Fork 33.4k
/
Copy pathclimate.py
255 lines (210 loc) · 8.99 KB
/
climate.py
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
"""Plugwise Climate component for Home Assistant."""
from __future__ import annotations
from typing import Any
from homeassistant.components.climate import (
ATTR_HVAC_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ClimateEntity,
ClimateEntityFeature,
HVACAction,
HVACMode,
)
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import PlugwiseConfigEntry
from .const import DOMAIN, MASTER_THERMOSTATS
from .coordinator import PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity
from .util import plugwise_command
async def async_setup_entry(
hass: HomeAssistant,
entry: PlugwiseConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Smile Thermostats from a config entry."""
coordinator = entry.runtime_data
@callback
def _add_entities() -> None:
"""Add Entities."""
if not coordinator.new_devices:
return
if coordinator.data.gateway["smile_name"] == "Adam":
async_add_entities(
PlugwiseClimateEntity(coordinator, device_id)
for device_id in coordinator.new_devices
if coordinator.data.devices[device_id]["dev_class"] == "climate"
)
else:
async_add_entities(
PlugwiseClimateEntity(coordinator, device_id)
for device_id in coordinator.new_devices
if coordinator.data.devices[device_id]["dev_class"]
in MASTER_THERMOSTATS
)
_add_entities()
entry.async_on_unload(coordinator.async_add_listener(_add_entities))
class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
"""Representation of a Plugwise thermostat."""
_attr_name = None
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_translation_key = DOMAIN
_previous_mode: str = "heating"
def __init__(
self,
coordinator: PlugwiseDataUpdateCoordinator,
device_id: str,
) -> None:
"""Set up the Plugwise API."""
super().__init__(coordinator, device_id)
self._attr_unique_id = f"{device_id}-climate"
self._devices = coordinator.data.devices
self._gateway = coordinator.data.gateway
gateway_id: str = self._gateway["gateway_id"]
self._gateway_data = self._devices[gateway_id]
self._location = device_id
if (location := self.device.get("location")) is not None:
self._location = location
# Determine supported features
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
if self._gateway["cooling_present"] and self._gateway["smile_name"] != "Adam":
self._attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
)
if HVACMode.OFF in self.hvac_modes:
self._attr_supported_features |= (
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
)
if presets := self.device.get("preset_modes"):
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
self._attr_preset_modes = presets
self._attr_min_temp = self.device["thermostat"]["lower_bound"]
self._attr_max_temp = min(self.device["thermostat"]["upper_bound"], 35.0)
# Ensure we don't drop below 0.1
self._attr_target_temperature_step = max(
self.device["thermostat"]["resolution"], 0.1
)
def _previous_action_mode(self, coordinator: PlugwiseDataUpdateCoordinator) -> None:
"""Return the previous action-mode when the regulation-mode is not heating or cooling.
Helper for set_hvac_mode().
"""
# When no cooling available, _previous_mode is always heating
if (
"regulation_modes" in self._gateway_data
and "cooling" in self._gateway_data["regulation_modes"]
):
mode = self._gateway_data["select_regulation_mode"]
if mode in ("cooling", "heating"):
self._previous_mode = mode
@property
def current_temperature(self) -> float:
"""Return the current temperature."""
return self.device["sensors"]["temperature"]
@property
def target_temperature(self) -> float:
"""Return the temperature we try to reach.
Connected to the HVACMode combination of AUTO-HEAT.
"""
return self.device["thermostat"]["setpoint"]
@property
def target_temperature_high(self) -> float:
"""Return the temperature we try to reach in case of cooling.
Connected to the HVACMode combination of AUTO-HEAT_COOL.
"""
return self.device["thermostat"]["setpoint_high"]
@property
def target_temperature_low(self) -> float:
"""Return the heating temperature we try to reach in case of heating.
Connected to the HVACMode combination AUTO-HEAT_COOL.
"""
return self.device["thermostat"]["setpoint_low"]
@property
def hvac_mode(self) -> HVACMode:
"""Return HVAC operation ie. auto, cool, heat, heat_cool, or off mode."""
if (
mode := self.device.get("climate_mode")
) is None or mode not in self.hvac_modes:
return HVACMode.HEAT
return HVACMode(mode)
@property
def hvac_modes(self) -> list[HVACMode]:
"""Return a list of available HVACModes."""
hvac_modes: list[HVACMode] = []
if "regulation_modes" in self._gateway_data:
hvac_modes.append(HVACMode.OFF)
if "available_schedules" in self.device:
hvac_modes.append(HVACMode.AUTO)
if self._gateway["cooling_present"]:
if "regulation_modes" in self._gateway_data:
if self._gateway_data["select_regulation_mode"] == "cooling":
hvac_modes.append(HVACMode.COOL)
if self._gateway_data["select_regulation_mode"] == "heating":
hvac_modes.append(HVACMode.HEAT)
else:
hvac_modes.append(HVACMode.HEAT_COOL)
else:
hvac_modes.append(HVACMode.HEAT)
return hvac_modes
@property
def hvac_action(self) -> HVACAction:
"""Return the current running hvac operation if supported."""
# Keep track of the previous action-mode
self._previous_action_mode(self.coordinator)
# Adam provides the hvac_action for each thermostat
if (action := self.device.get("control_state")) is not None:
return HVACAction(action)
# Anna
heater: str = self._gateway["heater_id"]
heater_data = self._devices[heater]
if heater_data["binary_sensors"]["heating_state"]:
return HVACAction.HEATING
if heater_data["binary_sensors"].get("cooling_state", False):
return HVACAction.COOLING
return HVACAction.IDLE
@property
def preset_mode(self) -> str | None:
"""Return the current preset mode."""
return self.device.get("active_preset")
@plugwise_command
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
data: dict[str, Any] = {}
if ATTR_TEMPERATURE in kwargs:
data["setpoint"] = kwargs.get(ATTR_TEMPERATURE)
if ATTR_TARGET_TEMP_HIGH in kwargs:
data["setpoint_high"] = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if ATTR_TARGET_TEMP_LOW in kwargs:
data["setpoint_low"] = kwargs.get(ATTR_TARGET_TEMP_LOW)
if mode := kwargs.get(ATTR_HVAC_MODE):
await self.async_set_hvac_mode(mode)
await self.coordinator.api.set_temperature(self._location, data)
@plugwise_command
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the hvac mode."""
if hvac_mode not in self.hvac_modes:
hvac_modes = ", ".join(self.hvac_modes)
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="unsupported_hvac_mode_requested",
translation_placeholders={
"hvac_mode": hvac_mode,
"hvac_modes": hvac_modes,
},
)
if hvac_mode == self.hvac_mode:
return
if hvac_mode == HVACMode.OFF:
await self.coordinator.api.set_regulation_mode(hvac_mode)
else:
await self.coordinator.api.set_schedule_state(
self._location,
"on" if hvac_mode == HVACMode.AUTO else "off",
)
if self.hvac_mode == HVACMode.OFF:
await self.coordinator.api.set_regulation_mode(self._previous_mode)
@plugwise_command
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode."""
await self.coordinator.api.set_preset(self._location, preset_mode)