plugwise 0.37.1a2__tar.gz → 0.37.3__tar.gz
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-0.37.1a2 → plugwise-0.37.3}/PKG-INFO +1 -5
- plugwise-0.37.3/plugwise/common.py +329 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/constants.py +47 -44
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/data.py +57 -73
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/helper.py +430 -614
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/legacy/data.py +28 -44
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/legacy/helper.py +174 -357
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/legacy/smile.py +45 -41
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/util.py +62 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise.egg-info/PKG-INFO +1 -5
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise.egg-info/requires.txt +0 -3
- {plugwise-0.37.1a2 → plugwise-0.37.3}/pyproject.toml +2 -6
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_init.py +18 -34
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_legacy_anna.py +0 -2
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_legacy_p1.py +0 -2
- plugwise-0.37.1a2/plugwise/common.py +0 -151
- {plugwise-0.37.1a2 → plugwise-0.37.3}/LICENSE +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/README.md +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/__init__.py +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/exceptions.py +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/py.typed +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise/smile.py +130 -130
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise.egg-info/SOURCES.txt +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise.egg-info/dependency_links.txt +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/plugwise.egg-info/top_level.txt +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/setup.cfg +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/setup.py +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_adam.py +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_anna.py +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_generic.py +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_legacy_generic.py +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_legacy_stretch.py +0 -0
- {plugwise-0.37.1a2 → plugwise-0.37.3}/tests/test_p1.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: plugwise
|
3
|
-
Version: 0.37.
|
3
|
+
Version: 0.37.3
|
4
4
|
Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
|
5
5
|
Home-page: https://github.com/plugwise/python-plugwise
|
6
6
|
Author: Plugwise device owners
|
@@ -35,18 +35,14 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
35
35
|
Classifier: Intended Audience :: Developers
|
36
36
|
Classifier: License :: OSI Approved :: MIT License
|
37
37
|
Classifier: Operating System :: OS Independent
|
38
|
-
Classifier: Programming Language :: Python :: 3.11
|
39
38
|
Classifier: Programming Language :: Python :: 3.12
|
40
39
|
Classifier: Topic :: Home Automation
|
41
40
|
Requires-Python: >=3.11.0
|
42
41
|
Description-Content-Type: text/markdown
|
43
42
|
License-File: LICENSE
|
44
43
|
Requires-Dist: aiohttp
|
45
|
-
Requires-Dist: async_timeout
|
46
|
-
Requires-Dist: crcmod
|
47
44
|
Requires-Dist: defusedxml
|
48
45
|
Requires-Dist: munch
|
49
|
-
Requires-Dist: pyserial
|
50
46
|
Requires-Dist: python-dateutil
|
51
47
|
|
52
48
|
# Plugwise python module
|
@@ -0,0 +1,329 @@
|
|
1
|
+
"""Use of this source code is governed by the MIT license found in the LICENSE file.
|
2
|
+
|
3
|
+
Plugwise Smile protocol helpers.
|
4
|
+
"""
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from typing import cast
|
8
|
+
|
9
|
+
from plugwise.constants import (
|
10
|
+
ANNA,
|
11
|
+
SPECIAL_PLUG_TYPES,
|
12
|
+
SWITCH_GROUP_TYPES,
|
13
|
+
ApplianceType,
|
14
|
+
DeviceData,
|
15
|
+
ModelData,
|
16
|
+
SensorType,
|
17
|
+
)
|
18
|
+
from plugwise.util import (
|
19
|
+
check_alternative_location,
|
20
|
+
check_heater_central,
|
21
|
+
check_model,
|
22
|
+
get_vendor_name,
|
23
|
+
power_data_local_format,
|
24
|
+
return_valid,
|
25
|
+
)
|
26
|
+
|
27
|
+
from defusedxml import ElementTree as etree
|
28
|
+
from munch import Munch
|
29
|
+
|
30
|
+
|
31
|
+
class SmileCommon:
|
32
|
+
"""The SmileCommon class."""
|
33
|
+
|
34
|
+
def __init__(self) -> None:
|
35
|
+
"""Init."""
|
36
|
+
self._appliances: etree
|
37
|
+
self._count: int
|
38
|
+
self._domain_objects: etree
|
39
|
+
self._cooling_present: bool
|
40
|
+
self._heater_id: str
|
41
|
+
self._on_off_device: bool
|
42
|
+
self._opentherm_device: bool
|
43
|
+
self.gw_devices: dict[str, DeviceData]
|
44
|
+
self.smile_name: str
|
45
|
+
self.smile_type: str
|
46
|
+
|
47
|
+
def smile(self, name: str) -> bool:
|
48
|
+
"""Helper-function checking the smile-name."""
|
49
|
+
return self.smile_name == name
|
50
|
+
|
51
|
+
def _appl_heater_central_info(
|
52
|
+
self,
|
53
|
+
appl: Munch,
|
54
|
+
xml_1: etree,
|
55
|
+
xml_2: etree = None,
|
56
|
+
xml_3: etree = None,
|
57
|
+
) -> Munch:
|
58
|
+
"""Helper-function for _appliance_info_finder()."""
|
59
|
+
# Remove heater_central when no active device present
|
60
|
+
if not self._opentherm_device and not self._on_off_device:
|
61
|
+
return None
|
62
|
+
|
63
|
+
# Find the valid heater_central
|
64
|
+
# xml_2 self._appliances for legacy, self._domain_objects for actual
|
65
|
+
xml_2 = return_valid(xml_2, self._domain_objects)
|
66
|
+
self._heater_id = check_heater_central(xml_2)
|
67
|
+
|
68
|
+
# Info for On-Off device
|
69
|
+
if self._on_off_device:
|
70
|
+
appl.name = "OnOff" # pragma: no cover
|
71
|
+
appl.vendor_name = None # pragma: no cover
|
72
|
+
appl.model = "Unknown" # pragma: no cover
|
73
|
+
return appl # pragma: no cover
|
74
|
+
|
75
|
+
# Info for OpenTherm device
|
76
|
+
appl.name = "OpenTherm"
|
77
|
+
locator_1 = "./logs/point_log[type='flame_state']/boiler_state"
|
78
|
+
locator_2 = "./services/boiler_state"
|
79
|
+
mod_type = "boiler_state"
|
80
|
+
# xml_1: appliance
|
81
|
+
# xml_3: self._modules for legacy, self._domain_objects for actual
|
82
|
+
xml_3 = return_valid(xml_3, self._domain_objects)
|
83
|
+
module_data = self._get_module_data(xml_1, locator_1, mod_type, xml_3)
|
84
|
+
if not module_data["contents"]:
|
85
|
+
module_data = self._get_module_data(xml_1, locator_2, mod_type, xml_3)
|
86
|
+
appl.vendor_name = module_data["vendor_name"]
|
87
|
+
appl.hardware = module_data["hardware_version"]
|
88
|
+
appl.model = module_data["vendor_model"]
|
89
|
+
if appl.model is None:
|
90
|
+
appl.model = (
|
91
|
+
"Generic heater/cooler"
|
92
|
+
if self._cooling_present
|
93
|
+
else "Generic heater"
|
94
|
+
)
|
95
|
+
|
96
|
+
return appl
|
97
|
+
|
98
|
+
def _appl_thermostat_info(self, appl: Munch, xml_1: etree, xml_2: etree = None) -> Munch:
|
99
|
+
"""Helper-function for _appliance_info_finder()."""
|
100
|
+
locator = "./logs/point_log[type='thermostat']/thermostat"
|
101
|
+
mod_type = "thermostat"
|
102
|
+
xml_2 = return_valid(xml_2, self._domain_objects)
|
103
|
+
module_data = self._get_module_data(xml_1, locator, mod_type, xml_2)
|
104
|
+
appl.vendor_name = module_data["vendor_name"]
|
105
|
+
appl.model = check_model(module_data["vendor_model"], appl.vendor_name)
|
106
|
+
appl.hardware = module_data["hardware_version"]
|
107
|
+
appl.firmware = module_data["firmware_version"]
|
108
|
+
appl.zigbee_mac = module_data["zigbee_mac_address"]
|
109
|
+
|
110
|
+
return appl
|
111
|
+
|
112
|
+
def _collect_power_values(self, data: DeviceData, loc: Munch, tariff: str, legacy: bool = False) -> None:
|
113
|
+
"""Something."""
|
114
|
+
for loc.peak_select in ("nl_peak", "nl_offpeak"):
|
115
|
+
loc.locator = (
|
116
|
+
f'./{loc.log_type}[type="{loc.measurement}"]/period/'
|
117
|
+
f'measurement[@{tariff}="{loc.peak_select}"]'
|
118
|
+
)
|
119
|
+
if legacy:
|
120
|
+
loc.locator = (
|
121
|
+
f"./{loc.meas_list[0]}_{loc.log_type}/measurement"
|
122
|
+
f'[@directionality="{loc.meas_list[1]}"][@{tariff}="{loc.peak_select}"]'
|
123
|
+
)
|
124
|
+
|
125
|
+
loc = self._power_data_peak_value(loc, legacy)
|
126
|
+
if not loc.found:
|
127
|
+
continue
|
128
|
+
|
129
|
+
data = self._power_data_energy_diff(
|
130
|
+
loc.measurement, loc.net_string, loc.f_val, data
|
131
|
+
)
|
132
|
+
key = cast(SensorType, loc.key_string)
|
133
|
+
data["sensors"][key] = loc.f_val
|
134
|
+
|
135
|
+
def _power_data_peak_value(self, loc: Munch, legacy: bool) -> Munch:
|
136
|
+
"""Helper-function for _power_data_from_location() and _power_data_from_modules()."""
|
137
|
+
loc.found = True
|
138
|
+
if loc.logs.find(loc.locator) is None:
|
139
|
+
loc = check_alternative_location(loc, legacy)
|
140
|
+
if not loc.found:
|
141
|
+
return loc
|
142
|
+
|
143
|
+
if (peak := loc.peak_select.split("_")[1]) == "offpeak":
|
144
|
+
peak = "off_peak"
|
145
|
+
log_found = loc.log_type.split("_")[0]
|
146
|
+
loc.key_string = f"{loc.measurement}_{peak}_{log_found}"
|
147
|
+
if "gas" in loc.measurement or loc.log_type == "point_meter":
|
148
|
+
loc.key_string = f"{loc.measurement}_{log_found}"
|
149
|
+
# Only for P1 Actual -------------------#
|
150
|
+
if "phase" in loc.measurement:
|
151
|
+
loc.key_string = f"{loc.measurement}"
|
152
|
+
# --------------------------------------#
|
153
|
+
loc.net_string = f"net_electricity_{log_found}"
|
154
|
+
val = loc.logs.find(loc.locator).text
|
155
|
+
loc.f_val = power_data_local_format(loc.attrs, loc.key_string, val)
|
156
|
+
|
157
|
+
return loc
|
158
|
+
|
159
|
+
def _power_data_energy_diff(
|
160
|
+
self,
|
161
|
+
measurement: str,
|
162
|
+
net_string: SensorType,
|
163
|
+
f_val: float | int,
|
164
|
+
direct_data: DeviceData,
|
165
|
+
) -> DeviceData:
|
166
|
+
"""Calculate differential energy."""
|
167
|
+
if (
|
168
|
+
"electricity" in measurement
|
169
|
+
and "phase" not in measurement
|
170
|
+
and "interval" not in net_string
|
171
|
+
):
|
172
|
+
diff = 1
|
173
|
+
if "produced" in measurement:
|
174
|
+
diff = -1
|
175
|
+
if net_string not in direct_data["sensors"]:
|
176
|
+
tmp_val: float | int = 0
|
177
|
+
else:
|
178
|
+
tmp_val = direct_data["sensors"][net_string]
|
179
|
+
|
180
|
+
if isinstance(f_val, int):
|
181
|
+
tmp_val += f_val * diff
|
182
|
+
else:
|
183
|
+
tmp_val += float(f_val * diff)
|
184
|
+
tmp_val = float(f"{round(tmp_val, 3):.3f}")
|
185
|
+
|
186
|
+
direct_data["sensors"][net_string] = tmp_val
|
187
|
+
|
188
|
+
return direct_data
|
189
|
+
|
190
|
+
def _create_gw_devices(self, appl: Munch) -> None:
|
191
|
+
"""Helper-function for creating/updating gw_devices."""
|
192
|
+
self.gw_devices[appl.dev_id] = {"dev_class": appl.pwclass}
|
193
|
+
self._count += 1
|
194
|
+
for key, value in {
|
195
|
+
"firmware": appl.firmware,
|
196
|
+
"hardware": appl.hardware,
|
197
|
+
"location": appl.location,
|
198
|
+
"mac_address": appl.mac,
|
199
|
+
"model": appl.model,
|
200
|
+
"name": appl.name,
|
201
|
+
"zigbee_mac_address": appl.zigbee_mac,
|
202
|
+
"vendor": appl.vendor_name,
|
203
|
+
}.items():
|
204
|
+
if value is not None or key == "location":
|
205
|
+
appl_key = cast(ApplianceType, key)
|
206
|
+
self.gw_devices[appl.dev_id][appl_key] = value
|
207
|
+
self._count += 1
|
208
|
+
|
209
|
+
def _device_data_switching_group(
|
210
|
+
self, device: DeviceData, data: DeviceData
|
211
|
+
) -> None:
|
212
|
+
"""Helper-function for _get_device_data().
|
213
|
+
|
214
|
+
Determine switching group device data.
|
215
|
+
"""
|
216
|
+
if device["dev_class"] in SWITCH_GROUP_TYPES:
|
217
|
+
counter = 0
|
218
|
+
for member in device["members"]:
|
219
|
+
if self.gw_devices[member]["switches"].get("relay"):
|
220
|
+
counter += 1
|
221
|
+
data["switches"]["relay"] = counter != 0
|
222
|
+
self._count += 1
|
223
|
+
|
224
|
+
def _get_group_switches(self) -> dict[str, DeviceData]:
|
225
|
+
"""Helper-function for smile.py: get_all_devices().
|
226
|
+
|
227
|
+
Collect switching- or pump-group info.
|
228
|
+
"""
|
229
|
+
switch_groups: dict[str, DeviceData] = {}
|
230
|
+
# P1 and Anna don't have switchgroups
|
231
|
+
if self.smile_type == "power" or self.smile(ANNA):
|
232
|
+
return switch_groups
|
233
|
+
|
234
|
+
for group in self._domain_objects.findall("./group"):
|
235
|
+
members: list[str] = []
|
236
|
+
group_id = group.attrib["id"]
|
237
|
+
group_name = group.find("name").text
|
238
|
+
group_type = group.find("type").text
|
239
|
+
group_appliances = group.findall("appliances/appliance")
|
240
|
+
for item in group_appliances:
|
241
|
+
# Check if members are not orphaned - stretch
|
242
|
+
if item.attrib["id"] in self.gw_devices:
|
243
|
+
members.append(item.attrib["id"])
|
244
|
+
|
245
|
+
if group_type in SWITCH_GROUP_TYPES and members:
|
246
|
+
switch_groups.update(
|
247
|
+
{
|
248
|
+
group_id: {
|
249
|
+
"dev_class": group_type,
|
250
|
+
"model": "Switchgroup",
|
251
|
+
"name": group_name,
|
252
|
+
"members": members,
|
253
|
+
},
|
254
|
+
},
|
255
|
+
)
|
256
|
+
self._count += 4
|
257
|
+
|
258
|
+
return switch_groups
|
259
|
+
|
260
|
+
def _get_lock_state(self, xml: etree, data: DeviceData, stretch_v2: bool = False) -> None:
|
261
|
+
"""Helper-function for _get_measurement_data().
|
262
|
+
|
263
|
+
Adam & Stretches: obtain the relay-switch lock state.
|
264
|
+
"""
|
265
|
+
actuator = "actuator_functionalities"
|
266
|
+
func_type = "relay_functionality"
|
267
|
+
if stretch_v2:
|
268
|
+
actuator = "actuators"
|
269
|
+
func_type = "relay"
|
270
|
+
if xml.find("type").text not in SPECIAL_PLUG_TYPES:
|
271
|
+
locator = f"./{actuator}/{func_type}/lock"
|
272
|
+
if (found := xml.find(locator)) is not None:
|
273
|
+
data["switches"]["lock"] = found.text == "true"
|
274
|
+
self._count += 1
|
275
|
+
|
276
|
+
def _get_module_data(
|
277
|
+
self,
|
278
|
+
xml_1: etree,
|
279
|
+
locator: str,
|
280
|
+
mod_type: str,
|
281
|
+
xml_2: etree = None,
|
282
|
+
legacy: bool = False,
|
283
|
+
) -> ModelData:
|
284
|
+
"""Helper-function for _energy_device_info_finder() and _appliance_info_finder().
|
285
|
+
|
286
|
+
Collect requested info from MODULES.
|
287
|
+
"""
|
288
|
+
model_data: ModelData = {
|
289
|
+
"contents": False,
|
290
|
+
"firmware_version": None,
|
291
|
+
"hardware_version": None,
|
292
|
+
"reachable": None,
|
293
|
+
"vendor_name": None,
|
294
|
+
"vendor_model": None,
|
295
|
+
"zigbee_mac_address": None,
|
296
|
+
}
|
297
|
+
# xml_1: appliance
|
298
|
+
if (appl_search := xml_1.find(locator)) is not None:
|
299
|
+
link_id = appl_search.attrib["id"]
|
300
|
+
loc = f".//services/{mod_type}[@id='{link_id}']...."
|
301
|
+
if legacy:
|
302
|
+
loc = f".//{mod_type}[@id='{link_id}']...."
|
303
|
+
# Not possible to walrus for some reason...
|
304
|
+
# xml_2: self._modules for legacy, self._domain_objects for actual
|
305
|
+
search = return_valid(xml_2, self._domain_objects)
|
306
|
+
module = search.find(loc)
|
307
|
+
if module is not None: # pylint: disable=consider-using-assignment-expr
|
308
|
+
model_data["contents"] = True
|
309
|
+
get_vendor_name(module, model_data)
|
310
|
+
model_data["vendor_model"] = module.find("vendor_model").text
|
311
|
+
model_data["hardware_version"] = module.find("hardware_version").text
|
312
|
+
model_data["firmware_version"] = module.find("firmware_version").text
|
313
|
+
self._get_zigbee_data(module, model_data, legacy)
|
314
|
+
|
315
|
+
return model_data
|
316
|
+
|
317
|
+
def _get_zigbee_data(self, module: etree, model_data: ModelData, legacy: bool) -> None:
|
318
|
+
"""Helper-function for _get_model_data()."""
|
319
|
+
if legacy:
|
320
|
+
# Stretches
|
321
|
+
if (router := module.find("./protocols/network_router")) is not None:
|
322
|
+
model_data["zigbee_mac_address"] = router.find("mac_address").text
|
323
|
+
# Also look for the Circle+/Stealth M+
|
324
|
+
if (coord := module.find("./protocols/network_coordinator")) is not None:
|
325
|
+
model_data["zigbee_mac_address"] = coord.find("mac_address").text
|
326
|
+
# Adam
|
327
|
+
elif (zb_node := module.find("./protocols/zig_bee_node")) is not None:
|
328
|
+
model_data["zigbee_mac_address"] = zb_node.find("mac_address").text
|
329
|
+
model_data["reachable"] = zb_node.find("reachable").text == "true"
|
@@ -121,21 +121,20 @@ P1_LEGACY_MEASUREMENTS: Final[dict[str, UOM]] = {
|
|
121
121
|
# radiator_valve: 'uncorrected_temperature', 'temperature_offset'
|
122
122
|
|
123
123
|
DEVICE_MEASUREMENTS: Final[dict[str, DATA | UOM]] = {
|
124
|
-
#
|
125
|
-
"
|
126
|
-
# HA Core thermostat
|
127
|
-
"thermostat": DATA("setpoint", TEMP_CELSIUS),
|
128
|
-
|
129
|
-
"illuminance": UOM(UNIT_LUMEN),
|
124
|
+
"humidity": UOM(PERCENTAGE), # Specific for a Jip
|
125
|
+
"illuminance": UOM(UNIT_LUMEN), # Specific for an Anna
|
126
|
+
"temperature": UOM(TEMP_CELSIUS), # HA Core thermostat current_temperature
|
127
|
+
"thermostat": DATA("setpoint", TEMP_CELSIUS), # HA Core thermostat setpoint
|
128
|
+
########################################################
|
130
129
|
# Specific for an Anna with heatpump extension installed
|
131
130
|
"cooling_activation_outdoor_temperature": UOM(TEMP_CELSIUS),
|
132
131
|
"cooling_deactivation_threshold": UOM(TEMP_CELSIUS),
|
133
|
-
|
132
|
+
##################################
|
133
|
+
# Specific for a Lisa or Tom/Floor
|
134
134
|
"battery": UOM(PERCENTAGE),
|
135
135
|
"temperature_difference": UOM(DEGREE),
|
136
136
|
"valve_position": UOM(PERCENTAGE),
|
137
|
-
|
138
|
-
"humidity": UOM(PERCENTAGE),
|
137
|
+
#####################
|
139
138
|
# Specific for a Plug
|
140
139
|
"electricity_consumed": UOM(POWER_WATT),
|
141
140
|
"electricity_produced": UOM(POWER_WATT),
|
@@ -144,39 +143,43 @@ DEVICE_MEASUREMENTS: Final[dict[str, DATA | UOM]] = {
|
|
144
143
|
|
145
144
|
# Heater Central related measurements
|
146
145
|
HEATER_CENTRAL_MEASUREMENTS: Final[dict[str, DATA | UOM]] = {
|
146
|
+
"boiler_state": DATA(
|
147
|
+
"flame_state", NONE
|
148
|
+
), # Legacy Anna: similar to flame-state on Anna/Adam
|
147
149
|
"boiler_temperature": DATA("water_temperature", TEMP_CELSIUS),
|
150
|
+
"central_heating_state": DATA(
|
151
|
+
"c_heating_state", NONE
|
152
|
+
), # For Elga (heatpump) use this instead of intended_central_heating_state
|
153
|
+
"central_heater_water_pressure": DATA("water_pressure", PRESSURE_BAR),
|
154
|
+
"compressor_state": UOM(NONE), # present with heatpump
|
155
|
+
"cooling_enabled": UOM(
|
156
|
+
NONE
|
157
|
+
), # Available with the Loria and Elga (newer Anna firmware) heatpumps
|
158
|
+
"cooling_state": UOM(NONE),
|
148
159
|
"domestic_hot_water_mode": DATA("select_dhw_mode", NONE),
|
149
160
|
"domestic_hot_water_setpoint": UOM(TEMP_CELSIUS),
|
150
161
|
"domestic_hot_water_state": DATA("dhw_state", NONE),
|
151
162
|
"domestic_hot_water_temperature": DATA("dhw_temperature", TEMP_CELSIUS),
|
152
163
|
"elga_status_code": UOM(NONE),
|
164
|
+
"intended_boiler_state": DATA(
|
165
|
+
"heating_state", NONE
|
166
|
+
), # Legacy Anna: shows when heating is active, we don't show dhw_state, cannot be determined reliably
|
167
|
+
"flame_state": UOM(
|
168
|
+
NONE
|
169
|
+
), # Also present when there is a single gas-heater
|
153
170
|
"intended_boiler_temperature": UOM(
|
154
171
|
TEMP_CELSIUS
|
155
172
|
), # Non-zero when heating, zero when dhw-heating
|
156
|
-
"central_heating_state": DATA(
|
157
|
-
"c_heating_state", NONE
|
158
|
-
), # For Elga (heatpump) use this instead of intended_central_heating_state
|
159
173
|
"intended_central_heating_state": DATA(
|
160
174
|
"heating_state", NONE
|
161
175
|
), # This key shows in general the heating-behavior better than c-h_state. except when connected to a heatpump
|
162
176
|
"modulation_level": UOM(PERCENTAGE),
|
163
177
|
"return_water_temperature": DATA("return_temperature", TEMP_CELSIUS),
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
"
|
168
|
-
#
|
169
|
-
"cooling_enabled": UOM(NONE),
|
170
|
-
# Next 2 keys are used to show the state of the gas-heater used next to the Elga heatpump - marcelveldt
|
171
|
-
"slave_boiler_state": UOM(NONE),
|
172
|
-
"flame_state": UOM(NONE), # Also present when there is a single gas-heater
|
173
|
-
"central_heater_water_pressure": DATA("water_pressure", PRESSURE_BAR),
|
174
|
-
# Legacy Anna: similar to flame-state on Anna/Adam
|
175
|
-
"boiler_state": DATA("flame_state", NONE),
|
176
|
-
# Legacy Anna: shows when heating is active, we don't show dhw_state, cannot be determined reliably
|
177
|
-
"intended_boiler_state": DATA("heating_state", NONE),
|
178
|
-
# Outdoor temperature from APPLIANCES - present for a heatpump
|
179
|
-
"outdoor_temperature": DATA("outdoor_air_temperature", TEMP_CELSIUS),
|
178
|
+
"outdoor_temperature": DATA(
|
179
|
+
"outdoor_air_temperature", TEMP_CELSIUS
|
180
|
+
), # Outdoor temperature from APPLIANCES - present for a heatpump
|
181
|
+
"slave_boiler_state": DATA("secondary_boiler_state", NONE),
|
182
|
+
"thermostat_supports_cooling": UOM(NONE), # present with heatpump
|
180
183
|
}
|
181
184
|
|
182
185
|
OBSOLETE_MEASUREMENTS: Final[tuple[str, ...]] = (
|
@@ -253,22 +256,22 @@ ApplianceType = Literal[
|
|
253
256
|
]
|
254
257
|
|
255
258
|
BinarySensorType = Literal[
|
256
|
-
"cooling_enabled",
|
257
259
|
"compressor_state",
|
260
|
+
"cooling_enabled",
|
258
261
|
"cooling_state",
|
259
262
|
"dhw_state",
|
260
263
|
"flame_state",
|
261
264
|
"heating_state",
|
262
265
|
"plugwise_notification",
|
263
|
-
"
|
266
|
+
"secondary_boiler_state",
|
264
267
|
]
|
265
268
|
BINARY_SENSORS: Final[tuple[str, ...]] = get_args(BinarySensorType)
|
266
269
|
|
267
270
|
LIMITS: Final[tuple[str, ...]] = (
|
271
|
+
"lower_bound",
|
268
272
|
"offset",
|
269
|
-
"setpoint",
|
270
273
|
"resolution",
|
271
|
-
"
|
274
|
+
"setpoint",
|
272
275
|
"upper_bound",
|
273
276
|
)
|
274
277
|
|
@@ -329,8 +332,8 @@ SENSORS: Final[tuple[str, ...]] = get_args(SensorType)
|
|
329
332
|
|
330
333
|
SPECIAL_PLUG_TYPES: Final[tuple[str, ...]] = (
|
331
334
|
"central_heating_pump",
|
332
|
-
"valve_actuator",
|
333
335
|
"heater_electric",
|
336
|
+
"valve_actuator",
|
334
337
|
)
|
335
338
|
|
336
339
|
SpecialType = Literal[
|
@@ -350,14 +353,14 @@ SwitchType = Literal[
|
|
350
353
|
]
|
351
354
|
SWITCHES: Final[tuple[str, ...]] = get_args(SwitchType)
|
352
355
|
|
353
|
-
SWITCH_GROUP_TYPES: Final[tuple[str, ...]] = ("
|
356
|
+
SWITCH_GROUP_TYPES: Final[tuple[str, ...]] = ("report", "switching")
|
354
357
|
|
355
358
|
THERMOSTAT_CLASSES: Final[tuple[str, ...]] = (
|
356
359
|
"thermostat",
|
360
|
+
"thermostatic_radiator_valve",
|
357
361
|
"thermo_sensor",
|
358
362
|
"zone_thermometer",
|
359
363
|
"zone_thermostat",
|
360
|
-
"thermostatic_radiator_valve",
|
361
364
|
)
|
362
365
|
|
363
366
|
ToggleNameType = Literal[
|
@@ -392,25 +395,25 @@ class ModelData(TypedDict):
|
|
392
395
|
"""The ModelData class."""
|
393
396
|
|
394
397
|
contents: bool
|
395
|
-
vendor_name: str | None
|
396
|
-
vendor_model: str | None
|
397
|
-
hardware_version: str | None
|
398
398
|
firmware_version: str | None
|
399
|
-
|
399
|
+
hardware_version: str | None
|
400
400
|
reachable: bool | None
|
401
|
+
vendor_model: str | None
|
402
|
+
vendor_name: str | None
|
403
|
+
zigbee_mac_address: str | None
|
401
404
|
|
402
405
|
|
403
406
|
class SmileBinarySensors(TypedDict, total=False):
|
404
407
|
"""Smile Binary Sensors class."""
|
405
408
|
|
406
|
-
cooling_enabled: bool
|
407
409
|
compressor_state: bool
|
410
|
+
cooling_enabled: bool
|
408
411
|
cooling_state: bool
|
409
412
|
dhw_state: bool
|
410
413
|
flame_state: bool
|
411
414
|
heating_state: bool
|
412
415
|
plugwise_notification: bool
|
413
|
-
|
416
|
+
secondary_boiler_state: bool
|
414
417
|
|
415
418
|
|
416
419
|
class SmileSensors(TypedDict, total=False):
|
@@ -482,9 +485,9 @@ class ThermoLoc(TypedDict, total=False):
|
|
482
485
|
"""Thermo Location class."""
|
483
486
|
|
484
487
|
name: str
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
+
primary: str | None
|
489
|
+
primary_prio: int
|
490
|
+
secondary: set[str]
|
488
491
|
|
489
492
|
|
490
493
|
class ActuatorData(TypedDict, total=False):
|