plugwise 1.6.3__py3-none-any.whl → 1.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plugwise/__init__.py +89 -63
- plugwise/common.py +20 -13
- plugwise/constants.py +3 -4
- plugwise/data.py +44 -20
- plugwise/helper.py +174 -100
- plugwise/legacy/data.py +13 -0
- plugwise/legacy/helper.py +12 -13
- plugwise/legacy/smile.py +36 -17
- plugwise/smile.py +23 -12
- plugwise/util.py +5 -7
- {plugwise-1.6.3.dist-info → plugwise-1.6.4.dist-info}/METADATA +1 -1
- plugwise-1.6.4.dist-info/RECORD +17 -0
- plugwise-1.6.3.dist-info/RECORD +0 -17
- {plugwise-1.6.3.dist-info → plugwise-1.6.4.dist-info}/LICENSE +0 -0
- {plugwise-1.6.3.dist-info → plugwise-1.6.4.dist-info}/WHEEL +0 -0
- {plugwise-1.6.3.dist-info → plugwise-1.6.4.dist-info}/top_level.txt +0 -0
plugwise/__init__.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
Plugwise backend module for Home Assistant Core.
|
4
4
|
"""
|
5
|
+
|
5
6
|
from __future__ import annotations
|
6
7
|
|
7
8
|
from plugwise.constants import (
|
@@ -63,7 +64,7 @@ class Smile(SmileComm):
|
|
63
64
|
self._timeout,
|
64
65
|
self._username,
|
65
66
|
self._websession,
|
66
|
-
|
67
|
+
)
|
67
68
|
|
68
69
|
self._cooling_present = False
|
69
70
|
self._elga = False
|
@@ -125,52 +126,56 @@ class Smile(SmileComm):
|
|
125
126
|
# Determine smile specifics
|
126
127
|
await self._smile_detect(result, dsmrmain)
|
127
128
|
|
128
|
-
self._smile_api =
|
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
|
-
self.
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
129
|
+
self._smile_api = (
|
130
|
+
SmileAPI(
|
131
|
+
self._host,
|
132
|
+
self._password,
|
133
|
+
self._request,
|
134
|
+
self._websession,
|
135
|
+
self._cooling_present,
|
136
|
+
self._elga,
|
137
|
+
self._is_thermostat,
|
138
|
+
self._last_active,
|
139
|
+
self._loc_data,
|
140
|
+
self._on_off_device,
|
141
|
+
self._opentherm_device,
|
142
|
+
self._schedule_old_states,
|
143
|
+
self.gateway_id,
|
144
|
+
self.smile_fw_version,
|
145
|
+
self.smile_hostname,
|
146
|
+
self.smile_hw_version,
|
147
|
+
self.smile_mac_address,
|
148
|
+
self.smile_model,
|
149
|
+
self.smile_model_id,
|
150
|
+
self.smile_name,
|
151
|
+
self.smile_type,
|
152
|
+
self.smile_version,
|
153
|
+
self._port,
|
154
|
+
self._username,
|
155
|
+
)
|
156
|
+
if not self.smile_legacy
|
157
|
+
else SmileLegacyAPI(
|
158
|
+
self._host,
|
159
|
+
self._password,
|
160
|
+
self._request,
|
161
|
+
self._websession,
|
162
|
+
self._is_thermostat,
|
163
|
+
self._loc_data,
|
164
|
+
self._on_off_device,
|
165
|
+
self._opentherm_device,
|
166
|
+
self._stretch_v2,
|
167
|
+
self._target_smile,
|
168
|
+
self.smile_fw_version,
|
169
|
+
self.smile_hostname,
|
170
|
+
self.smile_hw_version,
|
171
|
+
self.smile_mac_address,
|
172
|
+
self.smile_model,
|
173
|
+
self.smile_name,
|
174
|
+
self.smile_type,
|
175
|
+
self.smile_zigbee_mac_address,
|
176
|
+
self._port,
|
177
|
+
self._username,
|
178
|
+
)
|
174
179
|
)
|
175
180
|
|
176
181
|
# Update all endpoints on first connect
|
@@ -203,7 +208,7 @@ class Smile(SmileComm):
|
|
203
208
|
)
|
204
209
|
raise UnsupportedDeviceError
|
205
210
|
|
206
|
-
version_major= str(self.smile_fw_version.major)
|
211
|
+
version_major = str(self.smile_fw_version.major)
|
207
212
|
self._target_smile = f"{model}_v{version_major}"
|
208
213
|
LOGGER.debug("Plugwise identified as %s", self._target_smile)
|
209
214
|
if self._target_smile not in SMILES:
|
@@ -318,9 +323,9 @@ class Smile(SmileComm):
|
|
318
323
|
|
319
324
|
return data
|
320
325
|
|
321
|
-
########################################################################################################
|
322
|
-
### API Set and HA Service-related Functions ###
|
323
|
-
########################################################################################################
|
326
|
+
########################################################################################################
|
327
|
+
### API Set and HA Service-related Functions ###
|
328
|
+
########################################################################################################
|
324
329
|
|
325
330
|
async def set_select(
|
326
331
|
self,
|
@@ -333,7 +338,9 @@ class Smile(SmileComm):
|
|
333
338
|
try:
|
334
339
|
await self._smile_api.set_select(key, loc_id, option, state)
|
335
340
|
except ConnectionFailedError as exc:
|
336
|
-
raise ConnectionFailedError(
|
341
|
+
raise ConnectionFailedError(
|
342
|
+
f"Failed to set select option '{option}': {str(exc)}"
|
343
|
+
) from exc
|
337
344
|
|
338
345
|
async def set_schedule_state(
|
339
346
|
self,
|
@@ -345,8 +352,9 @@ class Smile(SmileComm):
|
|
345
352
|
try:
|
346
353
|
await self._smile_api.set_schedule_state(loc_id, state, name)
|
347
354
|
except ConnectionFailedError as exc: # pragma no cover
|
348
|
-
raise ConnectionFailedError(
|
349
|
-
|
355
|
+
raise ConnectionFailedError(
|
356
|
+
f"Failed to set schedule state: {str(exc)}"
|
357
|
+
) from exc # pragma no cover
|
350
358
|
|
351
359
|
async def set_preset(self, loc_id: str, preset: str) -> None:
|
352
360
|
"""Set the given Preset on the relevant Thermostat."""
|
@@ -360,7 +368,9 @@ class Smile(SmileComm):
|
|
360
368
|
try:
|
361
369
|
await self._smile_api.set_temperature(loc_id, items)
|
362
370
|
except ConnectionFailedError as exc:
|
363
|
-
raise ConnectionFailedError(
|
371
|
+
raise ConnectionFailedError(
|
372
|
+
f"Failed to set temperature: {str(exc)}"
|
373
|
+
) from exc
|
364
374
|
|
365
375
|
async def set_number(
|
366
376
|
self,
|
@@ -372,14 +382,18 @@ class Smile(SmileComm):
|
|
372
382
|
try:
|
373
383
|
await self._smile_api.set_number(dev_id, key, temperature)
|
374
384
|
except ConnectionFailedError as exc:
|
375
|
-
raise ConnectionFailedError(
|
385
|
+
raise ConnectionFailedError(
|
386
|
+
f"Failed to set number '{key}': {str(exc)}"
|
387
|
+
) from exc
|
376
388
|
|
377
389
|
async def set_temperature_offset(self, dev_id: str, offset: float) -> None:
|
378
390
|
"""Set the Temperature offset for thermostats that support this feature."""
|
379
391
|
try: # pragma no cover
|
380
392
|
await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
|
381
393
|
except ConnectionFailedError as exc: # pragma no cover
|
382
|
-
raise ConnectionFailedError(
|
394
|
+
raise ConnectionFailedError(
|
395
|
+
f"Failed to set temperature offset: {str(exc)}"
|
396
|
+
) from exc # pragma no cover
|
383
397
|
|
384
398
|
async def set_switch_state(
|
385
399
|
self, appl_id: str, members: list[str] | None, model: str, state: str
|
@@ -388,39 +402,51 @@ class Smile(SmileComm):
|
|
388
402
|
try:
|
389
403
|
await self._smile_api.set_switch_state(appl_id, members, model, state)
|
390
404
|
except ConnectionFailedError as exc:
|
391
|
-
raise ConnectionFailedError(
|
405
|
+
raise ConnectionFailedError(
|
406
|
+
f"Failed to set switch state: {str(exc)}"
|
407
|
+
) from exc
|
392
408
|
|
393
409
|
async def set_gateway_mode(self, mode: str) -> None:
|
394
410
|
"""Set the gateway mode."""
|
395
411
|
try: # pragma no cover
|
396
412
|
await self._smile_api.set_gateway_mode(mode) # pragma: no cover
|
397
413
|
except ConnectionFailedError as exc: # pragma no cover
|
398
|
-
raise ConnectionFailedError(
|
414
|
+
raise ConnectionFailedError(
|
415
|
+
f"Failed to set gateway mode: {str(exc)}"
|
416
|
+
) from exc # pragma no cover
|
399
417
|
|
400
418
|
async def set_regulation_mode(self, mode: str) -> None:
|
401
419
|
"""Set the heating regulation mode."""
|
402
420
|
try: # pragma no cover
|
403
421
|
await self._smile_api.set_regulation_mode(mode) # pragma: no cover
|
404
422
|
except ConnectionFailedError as exc: # pragma no cover
|
405
|
-
raise ConnectionFailedError(
|
423
|
+
raise ConnectionFailedError(
|
424
|
+
f"Failed to set regulation mode: {str(exc)}"
|
425
|
+
) from exc # pragma no cover
|
406
426
|
|
407
427
|
async def set_dhw_mode(self, mode: str) -> None:
|
408
428
|
"""Set the domestic hot water heating regulation mode."""
|
409
429
|
try: # pragma no cover
|
410
430
|
await self._smile_api.set_dhw_mode(mode) # pragma: no cover
|
411
431
|
except ConnectionFailedError as exc: # pragma no cover
|
412
|
-
raise ConnectionFailedError(
|
432
|
+
raise ConnectionFailedError(
|
433
|
+
f"Failed to set dhw mode: {str(exc)}"
|
434
|
+
) from exc # pragma no cover
|
413
435
|
|
414
436
|
async def delete_notification(self) -> None:
|
415
437
|
"""Delete the active Plugwise Notification."""
|
416
438
|
try:
|
417
439
|
await self._smile_api.delete_notification()
|
418
440
|
except ConnectionFailedError as exc:
|
419
|
-
raise ConnectionFailedError(
|
441
|
+
raise ConnectionFailedError(
|
442
|
+
f"Failed to delete notification: {str(exc)}"
|
443
|
+
) from exc
|
420
444
|
|
421
445
|
async def reboot_gateway(self) -> None:
|
422
446
|
"""Reboot the Plugwise Gateway."""
|
423
447
|
try:
|
424
448
|
await self._smile_api.reboot_gateway()
|
425
449
|
except ConnectionFailedError as exc:
|
426
|
-
raise ConnectionFailedError(
|
450
|
+
raise ConnectionFailedError(
|
451
|
+
f"Failed to reboot gateway: {str(exc)}"
|
452
|
+
) from exc
|
plugwise/common.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
Plugwise Smile protocol helpers.
|
4
4
|
"""
|
5
|
+
|
5
6
|
from __future__ import annotations
|
6
7
|
|
7
8
|
from typing import cast
|
@@ -83,21 +84,23 @@ class SmileCommon:
|
|
83
84
|
appl.hardware = module_data["hardware_version"]
|
84
85
|
appl.model_id = module_data["vendor_model"] if not legacy else None
|
85
86
|
appl.model = (
|
86
|
-
"Generic heater/cooler"
|
87
|
-
if self._cooling_present
|
88
|
-
else "Generic heater"
|
87
|
+
"Generic heater/cooler" if self._cooling_present else "Generic heater"
|
89
88
|
)
|
90
89
|
|
91
90
|
return appl
|
92
91
|
|
93
|
-
def _appl_thermostat_info(
|
92
|
+
def _appl_thermostat_info(
|
93
|
+
self, appl: Munch, xml_1: etree, xml_2: etree = None
|
94
|
+
) -> Munch:
|
94
95
|
"""Helper-function for _appliance_info_finder()."""
|
95
96
|
locator = "./logs/point_log[type='thermostat']/thermostat"
|
96
97
|
xml_2 = return_valid(xml_2, self._domain_objects)
|
97
98
|
module_data = self._get_module_data(xml_1, locator, xml_2)
|
98
99
|
appl.vendor_name = module_data["vendor_name"]
|
99
100
|
appl.model = module_data["vendor_model"]
|
100
|
-
if
|
101
|
+
if (
|
102
|
+
appl.model != "ThermoTouch"
|
103
|
+
): # model_id for Anna not present as stand-alone device
|
101
104
|
appl.model_id = appl.model
|
102
105
|
appl.model = check_model(appl.model, appl.vendor_name)
|
103
106
|
|
@@ -108,7 +111,9 @@ class SmileCommon:
|
|
108
111
|
|
109
112
|
return appl
|
110
113
|
|
111
|
-
def _collect_power_values(
|
114
|
+
def _collect_power_values(
|
115
|
+
self, data: GwEntityData, loc: Munch, tariff: str, legacy: bool = False
|
116
|
+
) -> None:
|
112
117
|
"""Something."""
|
113
118
|
for loc.peak_select in ("nl_peak", "nl_offpeak"):
|
114
119
|
loc.locator = (
|
@@ -220,9 +225,7 @@ class SmileCommon:
|
|
220
225
|
self.gw_entities[appl.entity_id][appl_key] = value
|
221
226
|
self._count += 1
|
222
227
|
|
223
|
-
def _entity_switching_group(
|
224
|
-
self, entity: GwEntityData, data: GwEntityData
|
225
|
-
) -> None:
|
228
|
+
def _entity_switching_group(self, entity: GwEntityData, data: GwEntityData) -> None:
|
226
229
|
"""Helper-function for _get_device_zone_data().
|
227
230
|
|
228
231
|
Determine switching group device data.
|
@@ -268,7 +271,9 @@ class SmileCommon:
|
|
268
271
|
|
269
272
|
return switch_groups
|
270
273
|
|
271
|
-
def _get_lock_state(
|
274
|
+
def _get_lock_state(
|
275
|
+
self, xml: etree, data: GwEntityData, stretch_v2: bool = False
|
276
|
+
) -> None:
|
272
277
|
"""Helper-function for _get_measurement_data().
|
273
278
|
|
274
279
|
Adam & Stretches: obtain the relay-switch lock state.
|
@@ -323,7 +328,9 @@ class SmileCommon:
|
|
323
328
|
|
324
329
|
return module_data
|
325
330
|
|
326
|
-
def _get_zigbee_data(
|
331
|
+
def _get_zigbee_data(
|
332
|
+
self, module: etree, module_data: ModuleData, legacy: bool
|
333
|
+
) -> None:
|
327
334
|
"""Helper-function for _get_module_data()."""
|
328
335
|
if legacy:
|
329
336
|
# Stretches
|
@@ -334,5 +341,5 @@ class SmileCommon:
|
|
334
341
|
module_data["zigbee_mac_address"] = coord.find("mac_address").text
|
335
342
|
# Adam
|
336
343
|
elif (zb_node := module.find("./protocols/zig_bee_node")) is not None:
|
337
|
-
|
338
|
-
|
344
|
+
module_data["zigbee_mac_address"] = zb_node.find("mac_address").text
|
345
|
+
module_data["reachable"] = zb_node.find("reachable").text == "true"
|
plugwise/constants.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Plugwise Smile constants."""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
from collections import namedtuple
|
@@ -84,6 +85,7 @@ MIN_SETPOINT: Final[float] = 4.0
|
|
84
85
|
MODULE_LOCATOR: Final = "./logs/point_log/*[@id]"
|
85
86
|
NONE: Final = "None"
|
86
87
|
OFF: Final = "off"
|
88
|
+
PRIORITY_DEVICE_CLASSES = ("heater_central", "gateway")
|
87
89
|
|
88
90
|
# XML data paths
|
89
91
|
APPLIANCES: Final = "/core/appliances"
|
@@ -167,9 +169,7 @@ HEATER_CENTRAL_MEASUREMENTS: Final[dict[str, DATA | UOM]] = {
|
|
167
169
|
"intended_boiler_state": DATA(
|
168
170
|
"heating_state", NONE
|
169
171
|
), # Legacy Anna: shows when heating is active, we don't show dhw_state, cannot be determined reliably
|
170
|
-
"flame_state": UOM(
|
171
|
-
NONE
|
172
|
-
), # Also present when there is a single gas-heater
|
172
|
+
"flame_state": UOM(NONE), # Also present when there is a single gas-heater
|
173
173
|
"intended_boiler_temperature": UOM(
|
174
174
|
TEMP_CELSIUS
|
175
175
|
), # Non-zero when heating, zero when dhw-heating
|
@@ -585,4 +585,3 @@ class PlugwiseData:
|
|
585
585
|
|
586
586
|
devices: dict[str, GwEntityData]
|
587
587
|
gateway: GatewayData
|
588
|
-
|
plugwise/data.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
Plugwise Smile protocol data-collection helpers.
|
4
4
|
"""
|
5
|
+
|
5
6
|
from __future__ import annotations
|
6
7
|
|
7
8
|
import re
|
@@ -27,7 +28,6 @@ class SmileData(SmileHelper):
|
|
27
28
|
"""Init."""
|
28
29
|
SmileHelper.__init__(self)
|
29
30
|
|
30
|
-
|
31
31
|
def _all_entity_data(self) -> None:
|
32
32
|
"""Helper-function for get_all_gateway_entities().
|
33
33
|
|
@@ -78,7 +78,13 @@ class SmileData(SmileHelper):
|
|
78
78
|
mac_list
|
79
79
|
and "low_battery" in entity["binary_sensors"]
|
80
80
|
and entity["zigbee_mac_address"] in mac_list
|
81
|
-
and entity["dev_class"]
|
81
|
+
and entity["dev_class"]
|
82
|
+
in (
|
83
|
+
"thermo_sensor",
|
84
|
+
"thermostatic_radiator_valve",
|
85
|
+
"zone_thermometer",
|
86
|
+
"zone_thermostat",
|
87
|
+
)
|
82
88
|
)
|
83
89
|
if is_battery_low:
|
84
90
|
entity["binary_sensors"]["low_battery"] = True
|
@@ -98,7 +104,11 @@ class SmileData(SmileHelper):
|
|
98
104
|
message: str | None = notification.get("message")
|
99
105
|
warning: str | None = notification.get("warning")
|
100
106
|
notify = message or warning
|
101
|
-
if
|
107
|
+
if (
|
108
|
+
notify is not None
|
109
|
+
and all(x in notify for x in matches)
|
110
|
+
and (mac_addresses := mac_pattern.findall(notify))
|
111
|
+
):
|
102
112
|
mac_address = mac_addresses[0] # re.findall() outputs a list
|
103
113
|
|
104
114
|
if mac_address is not None:
|
@@ -114,9 +124,7 @@ class SmileData(SmileHelper):
|
|
114
124
|
"""Helper-function adding or updating the Plugwise notifications."""
|
115
125
|
if (
|
116
126
|
entity_id == self.gateway_id
|
117
|
-
and (
|
118
|
-
self._is_thermostat or self.smile_type == "power"
|
119
|
-
)
|
127
|
+
and (self._is_thermostat or self.smile_type == "power")
|
120
128
|
) or (
|
121
129
|
"binary_sensors" in entity
|
122
130
|
and "plugwise_notification" in entity["binary_sensors"]
|
@@ -152,7 +160,6 @@ class SmileData(SmileHelper):
|
|
152
160
|
sensors["setpoint_high"] = temp_dict["setpoint_high"]
|
153
161
|
self._count += 2 # add 4, remove 2
|
154
162
|
|
155
|
-
|
156
163
|
def _get_location_data(self, loc_id: str) -> GwEntityData:
|
157
164
|
"""Helper-function for _all_entity_data() and async_update().
|
158
165
|
|
@@ -160,13 +167,14 @@ class SmileData(SmileHelper):
|
|
160
167
|
"""
|
161
168
|
zone = self._zones[loc_id]
|
162
169
|
data = self._get_zone_data(loc_id)
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
+
data["control_state"] = "idle"
|
171
|
+
self._count += 1
|
172
|
+
if (ctrl_state := self._control_state(data, loc_id)) and str(ctrl_state) in (
|
173
|
+
"cooling",
|
174
|
+
"heating",
|
175
|
+
"preheating",
|
176
|
+
):
|
177
|
+
data["control_state"] = str(ctrl_state)
|
170
178
|
|
171
179
|
data["sensors"].pop("setpoint") # remove, only used in _control_state()
|
172
180
|
self._count -= 1
|
@@ -204,6 +212,7 @@ class SmileData(SmileHelper):
|
|
204
212
|
# Thermostat data for Anna (presets, temperatures etc)
|
205
213
|
if self.smile(ANNA) and entity["dev_class"] == "thermostat":
|
206
214
|
self._climate_data(entity_id, entity, data)
|
215
|
+
self._get_anna_control_state(data)
|
207
216
|
|
208
217
|
return data
|
209
218
|
|
@@ -235,7 +244,10 @@ class SmileData(SmileHelper):
|
|
235
244
|
data["binary_sensors"]["heating_state"] = self._heating_valves() != 0
|
236
245
|
# Add cooling_enabled binary_sensor
|
237
246
|
if "binary_sensors" in data:
|
238
|
-
if
|
247
|
+
if (
|
248
|
+
"cooling_enabled" not in data["binary_sensors"]
|
249
|
+
and self._cooling_present
|
250
|
+
):
|
239
251
|
data["binary_sensors"]["cooling_enabled"] = self._cooling_enabled
|
240
252
|
|
241
253
|
# Show the allowed regulation_modes and gateway_modes
|
@@ -248,10 +260,7 @@ class SmileData(SmileHelper):
|
|
248
260
|
self._count += 1
|
249
261
|
|
250
262
|
def _climate_data(
|
251
|
-
self,
|
252
|
-
location_id: str,
|
253
|
-
entity: GwEntityData,
|
254
|
-
data: GwEntityData
|
263
|
+
self, location_id: str, entity: GwEntityData, data: GwEntityData
|
255
264
|
) -> None:
|
256
265
|
"""Helper-function for _get_entity_data().
|
257
266
|
|
@@ -282,7 +291,9 @@ class SmileData(SmileHelper):
|
|
282
291
|
if sel_schedule in (NONE, OFF):
|
283
292
|
data["climate_mode"] = "heat"
|
284
293
|
if self._cooling_present:
|
285
|
-
data["climate_mode"] =
|
294
|
+
data["climate_mode"] = (
|
295
|
+
"cool" if self.check_reg_mode("cooling") else "heat_cool"
|
296
|
+
)
|
286
297
|
|
287
298
|
if self.check_reg_mode("off"):
|
288
299
|
data["climate_mode"] = "off"
|
@@ -299,6 +310,19 @@ class SmileData(SmileHelper):
|
|
299
310
|
"regulation_modes" in gateway and gateway["select_regulation_mode"] == mode
|
300
311
|
)
|
301
312
|
|
313
|
+
def _get_anna_control_state(self, data: GwEntityData) -> None:
|
314
|
+
"""Set the thermostat control_state based on the opentherm/onoff device state."""
|
315
|
+
data["control_state"] = "idle"
|
316
|
+
for entity in self.gw_entities.values():
|
317
|
+
if entity["dev_class"] != "heater_central":
|
318
|
+
continue
|
319
|
+
|
320
|
+
binary_sensors = entity["binary_sensors"]
|
321
|
+
if binary_sensors["heating_state"]:
|
322
|
+
data["control_state"] = "heating"
|
323
|
+
if binary_sensors.get("cooling_state"):
|
324
|
+
data["control_state"] = "cooling"
|
325
|
+
|
302
326
|
def _get_schedule_states_with_off(
|
303
327
|
self, location: str, schedules: list[str], selected: str, data: GwEntityData
|
304
328
|
) -> None:
|