plugwise 1.6.3__py3-none-any.whl → 1.7.0__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 +134 -100
- plugwise/common.py +32 -123
- plugwise/constants.py +6 -16
- plugwise/data.py +60 -37
- plugwise/helper.py +198 -333
- plugwise/legacy/data.py +20 -12
- plugwise/legacy/helper.py +31 -63
- plugwise/legacy/smile.py +64 -60
- plugwise/smile.py +50 -54
- plugwise/smilecomm.py +148 -0
- plugwise/util.py +117 -28
- {plugwise-1.6.3.dist-info → plugwise-1.7.0.dist-info}/METADATA +2 -2
- plugwise-1.7.0.dist-info/RECORD +18 -0
- {plugwise-1.6.3.dist-info → plugwise-1.7.0.dist-info}/WHEEL +1 -1
- plugwise-1.6.3.dist-info/RECORD +0 -17
- {plugwise-1.6.3.dist-info → plugwise-1.7.0.dist-info}/LICENSE +0 -0
- {plugwise-1.6.3.dist-info → plugwise-1.7.0.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 (
|
@@ -16,7 +17,8 @@ from plugwise.constants import (
|
|
16
17
|
SMILES,
|
17
18
|
STATUS,
|
18
19
|
SYSTEM,
|
19
|
-
|
20
|
+
GwEntityData,
|
21
|
+
SmileProps,
|
20
22
|
ThermoLoc,
|
21
23
|
)
|
22
24
|
from plugwise.exceptions import (
|
@@ -27,9 +29,9 @@ from plugwise.exceptions import (
|
|
27
29
|
ResponseError,
|
28
30
|
UnsupportedDeviceError,
|
29
31
|
)
|
30
|
-
from plugwise.helper import SmileComm
|
31
32
|
from plugwise.legacy.smile import SmileLegacyAPI
|
32
33
|
from plugwise.smile import SmileAPI
|
34
|
+
from plugwise.smilecomm import SmileComm
|
33
35
|
|
34
36
|
import aiohttp
|
35
37
|
from defusedxml import ElementTree as etree
|
@@ -37,9 +39,7 @@ from packaging.version import Version, parse
|
|
37
39
|
|
38
40
|
|
39
41
|
class Smile(SmileComm):
|
40
|
-
"""The Plugwise
|
41
|
-
|
42
|
-
# pylint: disable=too-many-instance-attributes, too-many-public-methods
|
42
|
+
"""The main Plugwise Smile API class."""
|
43
43
|
|
44
44
|
def __init__(
|
45
45
|
self,
|
@@ -50,20 +50,15 @@ class Smile(SmileComm):
|
|
50
50
|
username: str = DEFAULT_USERNAME,
|
51
51
|
) -> None:
|
52
52
|
"""Set the constructor for this class."""
|
53
|
-
self._host = host
|
54
|
-
self._password = password
|
55
|
-
self._port = port
|
56
53
|
self._timeout = DEFAULT_LEGACY_TIMEOUT
|
57
|
-
self._username = username
|
58
|
-
self._websession = websession
|
59
54
|
super().__init__(
|
60
|
-
|
61
|
-
|
62
|
-
|
55
|
+
host,
|
56
|
+
password,
|
57
|
+
port,
|
63
58
|
self._timeout,
|
64
|
-
|
65
|
-
|
66
|
-
|
59
|
+
username,
|
60
|
+
websession,
|
61
|
+
)
|
67
62
|
|
68
63
|
self._cooling_present = False
|
69
64
|
self._elga = False
|
@@ -74,10 +69,9 @@ class Smile(SmileComm):
|
|
74
69
|
self._opentherm_device = False
|
75
70
|
self._schedule_old_states: dict[str, dict[str, str]] = {}
|
76
71
|
self._smile_api: SmileAPI | SmileLegacyAPI
|
72
|
+
self._smile_props: SmileProps = {}
|
77
73
|
self._stretch_v2 = False
|
78
74
|
self._target_smile: str = NONE
|
79
|
-
self.gateway_id: str = NONE
|
80
|
-
self.smile_fw_version: Version | None = None
|
81
75
|
self.smile_hostname: str = NONE
|
82
76
|
self.smile_hw_version: str | None = None
|
83
77
|
self.smile_legacy = False
|
@@ -89,8 +83,40 @@ class Smile(SmileComm):
|
|
89
83
|
self.smile_version: Version | None = None
|
90
84
|
self.smile_zigbee_mac_address: str | None = None
|
91
85
|
|
86
|
+
@property
|
87
|
+
def cooling_present(self) -> bool:
|
88
|
+
"""Return the cooling capability."""
|
89
|
+
if "cooling_present" in self._smile_props:
|
90
|
+
return self._smile_props["cooling_present"]
|
91
|
+
return False
|
92
|
+
|
93
|
+
@property
|
94
|
+
def gateway_id(self) -> str:
|
95
|
+
"""Return the gateway-id."""
|
96
|
+
return self._smile_props["gateway_id"]
|
97
|
+
|
98
|
+
@property
|
99
|
+
def heater_id(self) -> str:
|
100
|
+
"""Return the heater-id."""
|
101
|
+
if "heater_id" in self._smile_props:
|
102
|
+
return self._smile_props["heater_id"]
|
103
|
+
return NONE
|
104
|
+
|
105
|
+
@property
|
106
|
+
def item_count(self) -> int:
|
107
|
+
"""Return the item-count."""
|
108
|
+
return self._smile_props["item_count"]
|
109
|
+
|
110
|
+
@property
|
111
|
+
def reboot(self) -> bool:
|
112
|
+
"""Return the reboot capability.
|
113
|
+
|
114
|
+
All non-legacy devices support gateway-rebooting.
|
115
|
+
"""
|
116
|
+
return not self.smile_legacy
|
117
|
+
|
92
118
|
async def connect(self) -> Version | None:
|
93
|
-
"""Connect to Plugwise
|
119
|
+
"""Connect to the Plugwise Gateway and determine its name, type, version, and other data."""
|
94
120
|
result = await self._request(DOMAIN_OBJECTS)
|
95
121
|
# Work-around for Stretch fw 2.7.18
|
96
122
|
if not (vendor_names := result.findall("./module/vendor_name")):
|
@@ -125,52 +151,46 @@ class Smile(SmileComm):
|
|
125
151
|
# Determine smile specifics
|
126
152
|
await self._smile_detect(result, dsmrmain)
|
127
153
|
|
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
|
-
self.
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
self.smile_model,
|
169
|
-
self.smile_name,
|
170
|
-
self.smile_type,
|
171
|
-
self.smile_zigbee_mac_address,
|
172
|
-
self._port,
|
173
|
-
self._username,
|
154
|
+
self._smile_api = (
|
155
|
+
SmileAPI(
|
156
|
+
self._cooling_present,
|
157
|
+
self._elga,
|
158
|
+
self._is_thermostat,
|
159
|
+
self._last_active,
|
160
|
+
self._loc_data,
|
161
|
+
self._on_off_device,
|
162
|
+
self._opentherm_device,
|
163
|
+
self._request,
|
164
|
+
self._schedule_old_states,
|
165
|
+
self._smile_props,
|
166
|
+
self.smile_hostname,
|
167
|
+
self.smile_hw_version,
|
168
|
+
self.smile_mac_address,
|
169
|
+
self.smile_model,
|
170
|
+
self.smile_model_id,
|
171
|
+
self.smile_name,
|
172
|
+
self.smile_type,
|
173
|
+
self.smile_version,
|
174
|
+
)
|
175
|
+
if not self.smile_legacy
|
176
|
+
else SmileLegacyAPI(
|
177
|
+
self._is_thermostat,
|
178
|
+
self._loc_data,
|
179
|
+
self._on_off_device,
|
180
|
+
self._opentherm_device,
|
181
|
+
self._request,
|
182
|
+
self._smile_props,
|
183
|
+
self._stretch_v2,
|
184
|
+
self._target_smile,
|
185
|
+
self.smile_hostname,
|
186
|
+
self.smile_hw_version,
|
187
|
+
self.smile_mac_address,
|
188
|
+
self.smile_model,
|
189
|
+
self.smile_name,
|
190
|
+
self.smile_type,
|
191
|
+
self.smile_version,
|
192
|
+
self.smile_zigbee_mac_address,
|
193
|
+
)
|
174
194
|
)
|
175
195
|
|
176
196
|
# Update all endpoints on first connect
|
@@ -181,13 +201,13 @@ class Smile(SmileComm):
|
|
181
201
|
async def _smile_detect(self, result: etree, dsmrmain: etree) -> None:
|
182
202
|
"""Helper-function for connect().
|
183
203
|
|
184
|
-
Detect which type of
|
204
|
+
Detect which type of Plugwise Gateway is being connected.
|
185
205
|
"""
|
186
206
|
model: str = "Unknown"
|
187
207
|
if (gateway := result.find("./gateway")) is not None:
|
188
208
|
if (v_model := gateway.find("vendor_model")) is not None:
|
189
209
|
model = v_model.text
|
190
|
-
self.
|
210
|
+
self.smile_version = parse(gateway.find("firmware_version").text)
|
191
211
|
self.smile_hw_version = gateway.find("hardware_version").text
|
192
212
|
self.smile_hostname = gateway.find("hostname").text
|
193
213
|
self.smile_mac_address = gateway.find("mac_address").text
|
@@ -195,7 +215,7 @@ class Smile(SmileComm):
|
|
195
215
|
else:
|
196
216
|
model = await self._smile_detect_legacy(result, dsmrmain, model)
|
197
217
|
|
198
|
-
if model == "Unknown" or self.
|
218
|
+
if model == "Unknown" or self.smile_version is None: # pragma: no cover
|
199
219
|
# Corner case check
|
200
220
|
LOGGER.error(
|
201
221
|
"Unable to find model or version information, please create"
|
@@ -203,7 +223,7 @@ class Smile(SmileComm):
|
|
203
223
|
)
|
204
224
|
raise UnsupportedDeviceError
|
205
225
|
|
206
|
-
version_major= str(self.
|
226
|
+
version_major = str(self.smile_version.major)
|
207
227
|
self._target_smile = f"{model}_v{version_major}"
|
208
228
|
LOGGER.debug("Plugwise identified as %s", self._target_smile)
|
209
229
|
if self._target_smile not in SMILES:
|
@@ -227,7 +247,6 @@ class Smile(SmileComm):
|
|
227
247
|
self.smile_model = "Gateway"
|
228
248
|
self.smile_name = SMILES[self._target_smile].smile_name
|
229
249
|
self.smile_type = SMILES[self._target_smile].smile_type
|
230
|
-
self.smile_version = self.smile_fw_version
|
231
250
|
|
232
251
|
if self.smile_type == "stretch":
|
233
252
|
self._stretch_v2 = int(version_major) == 2
|
@@ -255,7 +274,10 @@ class Smile(SmileComm):
|
|
255
274
|
async def _smile_detect_legacy(
|
256
275
|
self, result: etree, dsmrmain: etree, model: str
|
257
276
|
) -> str:
|
258
|
-
"""Helper-function for _smile_detect().
|
277
|
+
"""Helper-function for _smile_detect().
|
278
|
+
|
279
|
+
Detect which type of legacy Plugwise Gateway is being connected.
|
280
|
+
"""
|
259
281
|
return_model = model
|
260
282
|
# Stretch: find the MAC of the zigbee master_controller (= Stick)
|
261
283
|
if (network := result.find("./module/protocols/master_controller")) is not None:
|
@@ -273,7 +295,7 @@ class Smile(SmileComm):
|
|
273
295
|
or network is not None
|
274
296
|
):
|
275
297
|
system = await self._request(SYSTEM)
|
276
|
-
self.
|
298
|
+
self.smile_version = parse(system.find("./gateway/firmware").text)
|
277
299
|
return_model = system.find("./gateway/product").text
|
278
300
|
self.smile_hostname = system.find("./gateway/hostname").text
|
279
301
|
# If wlan0 contains data it's active, so eth0 should be checked last
|
@@ -284,7 +306,7 @@ class Smile(SmileComm):
|
|
284
306
|
# P1 legacy:
|
285
307
|
elif dsmrmain is not None:
|
286
308
|
status = await self._request(STATUS)
|
287
|
-
self.
|
309
|
+
self.smile_version = parse(status.find("./system/version").text)
|
288
310
|
return_model = status.find("./system/product").text
|
289
311
|
self.smile_hostname = status.find("./network/hostname").text
|
290
312
|
self.smile_mac_address = status.find("./network/mac_address").text
|
@@ -299,28 +321,19 @@ class Smile(SmileComm):
|
|
299
321
|
self.smile_legacy = True
|
300
322
|
return return_model
|
301
323
|
|
302
|
-
async def
|
303
|
-
"""
|
304
|
-
|
305
|
-
|
306
|
-
def get_all_gateway_entities(self) -> None:
|
307
|
-
"""Helper-function used for testing."""
|
308
|
-
self._smile_api.get_all_gateway_entities()
|
309
|
-
|
310
|
-
async def async_update(self) -> PlugwiseData:
|
311
|
-
"""Update the various entities and their states."""
|
312
|
-
data = PlugwiseData(devices={}, gateway={})
|
324
|
+
async def async_update(self) -> dict[str, GwEntityData]:
|
325
|
+
"""Update the Plughwise Gateway entities and their data and states."""
|
326
|
+
data: dict[str, GwEntityData] = {}
|
313
327
|
try:
|
314
328
|
data = await self._smile_api.async_update()
|
315
|
-
self.gateway_id = data.gateway["gateway_id"]
|
316
329
|
except (DataMissingError, KeyError) as err:
|
317
330
|
raise PlugwiseError("No Plugwise data received") from err
|
318
331
|
|
319
332
|
return data
|
320
333
|
|
321
|
-
########################################################################################################
|
322
|
-
### API Set and HA Service-related Functions ###
|
323
|
-
########################################################################################################
|
334
|
+
########################################################################################################
|
335
|
+
### API Set and HA Service-related Functions ###
|
336
|
+
########################################################################################################
|
324
337
|
|
325
338
|
async def set_select(
|
326
339
|
self,
|
@@ -333,7 +346,9 @@ class Smile(SmileComm):
|
|
333
346
|
try:
|
334
347
|
await self._smile_api.set_select(key, loc_id, option, state)
|
335
348
|
except ConnectionFailedError as exc:
|
336
|
-
raise ConnectionFailedError(
|
349
|
+
raise ConnectionFailedError(
|
350
|
+
f"Failed to set select option '{option}': {str(exc)}"
|
351
|
+
) from exc
|
337
352
|
|
338
353
|
async def set_schedule_state(
|
339
354
|
self,
|
@@ -345,8 +360,9 @@ class Smile(SmileComm):
|
|
345
360
|
try:
|
346
361
|
await self._smile_api.set_schedule_state(loc_id, state, name)
|
347
362
|
except ConnectionFailedError as exc: # pragma no cover
|
348
|
-
raise ConnectionFailedError(
|
349
|
-
|
363
|
+
raise ConnectionFailedError(
|
364
|
+
f"Failed to set schedule state: {str(exc)}"
|
365
|
+
) from exc # pragma no cover
|
350
366
|
|
351
367
|
async def set_preset(self, loc_id: str, preset: str) -> None:
|
352
368
|
"""Set the given Preset on the relevant Thermostat."""
|
@@ -360,7 +376,9 @@ class Smile(SmileComm):
|
|
360
376
|
try:
|
361
377
|
await self._smile_api.set_temperature(loc_id, items)
|
362
378
|
except ConnectionFailedError as exc:
|
363
|
-
raise ConnectionFailedError(
|
379
|
+
raise ConnectionFailedError(
|
380
|
+
f"Failed to set temperature: {str(exc)}"
|
381
|
+
) from exc
|
364
382
|
|
365
383
|
async def set_number(
|
366
384
|
self,
|
@@ -372,14 +390,18 @@ class Smile(SmileComm):
|
|
372
390
|
try:
|
373
391
|
await self._smile_api.set_number(dev_id, key, temperature)
|
374
392
|
except ConnectionFailedError as exc:
|
375
|
-
raise ConnectionFailedError(
|
393
|
+
raise ConnectionFailedError(
|
394
|
+
f"Failed to set number '{key}': {str(exc)}"
|
395
|
+
) from exc
|
376
396
|
|
377
397
|
async def set_temperature_offset(self, dev_id: str, offset: float) -> None:
|
378
398
|
"""Set the Temperature offset for thermostats that support this feature."""
|
379
399
|
try: # pragma no cover
|
380
400
|
await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
|
381
401
|
except ConnectionFailedError as exc: # pragma no cover
|
382
|
-
raise ConnectionFailedError(
|
402
|
+
raise ConnectionFailedError(
|
403
|
+
f"Failed to set temperature offset: {str(exc)}"
|
404
|
+
) from exc # pragma no cover
|
383
405
|
|
384
406
|
async def set_switch_state(
|
385
407
|
self, appl_id: str, members: list[str] | None, model: str, state: str
|
@@ -388,39 +410,51 @@ class Smile(SmileComm):
|
|
388
410
|
try:
|
389
411
|
await self._smile_api.set_switch_state(appl_id, members, model, state)
|
390
412
|
except ConnectionFailedError as exc:
|
391
|
-
raise ConnectionFailedError(
|
413
|
+
raise ConnectionFailedError(
|
414
|
+
f"Failed to set switch state: {str(exc)}"
|
415
|
+
) from exc
|
392
416
|
|
393
417
|
async def set_gateway_mode(self, mode: str) -> None:
|
394
418
|
"""Set the gateway mode."""
|
395
419
|
try: # pragma no cover
|
396
420
|
await self._smile_api.set_gateway_mode(mode) # pragma: no cover
|
397
421
|
except ConnectionFailedError as exc: # pragma no cover
|
398
|
-
raise ConnectionFailedError(
|
422
|
+
raise ConnectionFailedError(
|
423
|
+
f"Failed to set gateway mode: {str(exc)}"
|
424
|
+
) from exc # pragma no cover
|
399
425
|
|
400
426
|
async def set_regulation_mode(self, mode: str) -> None:
|
401
427
|
"""Set the heating regulation mode."""
|
402
428
|
try: # pragma no cover
|
403
429
|
await self._smile_api.set_regulation_mode(mode) # pragma: no cover
|
404
430
|
except ConnectionFailedError as exc: # pragma no cover
|
405
|
-
raise ConnectionFailedError(
|
431
|
+
raise ConnectionFailedError(
|
432
|
+
f"Failed to set regulation mode: {str(exc)}"
|
433
|
+
) from exc # pragma no cover
|
406
434
|
|
407
435
|
async def set_dhw_mode(self, mode: str) -> None:
|
408
436
|
"""Set the domestic hot water heating regulation mode."""
|
409
437
|
try: # pragma no cover
|
410
438
|
await self._smile_api.set_dhw_mode(mode) # pragma: no cover
|
411
439
|
except ConnectionFailedError as exc: # pragma no cover
|
412
|
-
raise ConnectionFailedError(
|
440
|
+
raise ConnectionFailedError(
|
441
|
+
f"Failed to set dhw mode: {str(exc)}"
|
442
|
+
) from exc # pragma no cover
|
413
443
|
|
414
444
|
async def delete_notification(self) -> None:
|
415
445
|
"""Delete the active Plugwise Notification."""
|
416
446
|
try:
|
417
447
|
await self._smile_api.delete_notification()
|
418
448
|
except ConnectionFailedError as exc:
|
419
|
-
raise ConnectionFailedError(
|
449
|
+
raise ConnectionFailedError(
|
450
|
+
f"Failed to delete notification: {str(exc)}"
|
451
|
+
) from exc
|
420
452
|
|
421
453
|
async def reboot_gateway(self) -> None:
|
422
454
|
"""Reboot the Plugwise Gateway."""
|
423
455
|
try:
|
424
456
|
await self._smile_api.reboot_gateway()
|
425
457
|
except ConnectionFailedError as exc:
|
426
|
-
raise ConnectionFailedError(
|
458
|
+
raise ConnectionFailedError(
|
459
|
+
f"Failed to reboot gateway: {str(exc)}"
|
460
|
+
) from exc
|
plugwise/common.py
CHANGED
@@ -2,25 +2,24 @@
|
|
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
|
8
9
|
|
9
10
|
from plugwise.constants import (
|
10
11
|
ANNA,
|
12
|
+
NONE,
|
11
13
|
SPECIAL_PLUG_TYPES,
|
12
14
|
SWITCH_GROUP_TYPES,
|
13
15
|
ApplianceType,
|
14
16
|
GwEntityData,
|
15
17
|
ModuleData,
|
16
|
-
SensorType,
|
17
18
|
)
|
18
19
|
from plugwise.util import (
|
19
|
-
check_alternative_location,
|
20
20
|
check_heater_central,
|
21
21
|
check_model,
|
22
22
|
get_vendor_name,
|
23
|
-
power_data_local_format,
|
24
23
|
return_valid,
|
25
24
|
)
|
26
25
|
|
@@ -28,19 +27,32 @@ from defusedxml import ElementTree as etree
|
|
28
27
|
from munch import Munch
|
29
28
|
|
30
29
|
|
30
|
+
def get_zigbee_data(module: etree, module_data: ModuleData, legacy: bool) -> None:
|
31
|
+
"""Helper-function for _get_module_data()."""
|
32
|
+
if legacy:
|
33
|
+
# Stretches
|
34
|
+
if (router := module.find("./protocols/network_router")) is not None:
|
35
|
+
module_data["zigbee_mac_address"] = router.find("mac_address").text
|
36
|
+
# Also look for the Circle+/Stealth M+
|
37
|
+
if (coord := module.find("./protocols/network_coordinator")) is not None:
|
38
|
+
module_data["zigbee_mac_address"] = coord.find("mac_address").text
|
39
|
+
# Adam
|
40
|
+
elif (zb_node := module.find("./protocols/zig_bee_node")) is not None:
|
41
|
+
module_data["zigbee_mac_address"] = zb_node.find("mac_address").text
|
42
|
+
module_data["reachable"] = zb_node.find("reachable").text == "true"
|
43
|
+
|
44
|
+
|
31
45
|
class SmileCommon:
|
32
46
|
"""The SmileCommon class."""
|
33
47
|
|
34
48
|
def __init__(self) -> None:
|
35
49
|
"""Init."""
|
36
|
-
self.
|
50
|
+
self._cooling_present: bool
|
37
51
|
self._count: int
|
38
52
|
self._domain_objects: etree
|
39
|
-
self.
|
40
|
-
self._heater_id: str
|
53
|
+
self._heater_id: str = NONE
|
41
54
|
self._on_off_device: bool
|
42
|
-
self.
|
43
|
-
self.gw_entities: dict[str, GwEntityData]
|
55
|
+
self.gw_entities: dict[str, GwEntityData] = {}
|
44
56
|
self.smile_name: str
|
45
57
|
self.smile_type: str
|
46
58
|
|
@@ -83,21 +95,23 @@ class SmileCommon:
|
|
83
95
|
appl.hardware = module_data["hardware_version"]
|
84
96
|
appl.model_id = module_data["vendor_model"] if not legacy else None
|
85
97
|
appl.model = (
|
86
|
-
"Generic heater/cooler"
|
87
|
-
if self._cooling_present
|
88
|
-
else "Generic heater"
|
98
|
+
"Generic heater/cooler" if self._cooling_present else "Generic heater"
|
89
99
|
)
|
90
100
|
|
91
101
|
return appl
|
92
102
|
|
93
|
-
def _appl_thermostat_info(
|
103
|
+
def _appl_thermostat_info(
|
104
|
+
self, appl: Munch, xml_1: etree, xml_2: etree = None
|
105
|
+
) -> Munch:
|
94
106
|
"""Helper-function for _appliance_info_finder()."""
|
95
107
|
locator = "./logs/point_log[type='thermostat']/thermostat"
|
96
108
|
xml_2 = return_valid(xml_2, self._domain_objects)
|
97
109
|
module_data = self._get_module_data(xml_1, locator, xml_2)
|
98
110
|
appl.vendor_name = module_data["vendor_name"]
|
99
111
|
appl.model = module_data["vendor_model"]
|
100
|
-
if
|
112
|
+
if (
|
113
|
+
appl.model != "ThermoTouch"
|
114
|
+
): # model_id for Anna not present as stand-alone device
|
101
115
|
appl.model_id = appl.model
|
102
116
|
appl.model = check_model(appl.model, appl.vendor_name)
|
103
117
|
|
@@ -108,97 +122,6 @@ class SmileCommon:
|
|
108
122
|
|
109
123
|
return appl
|
110
124
|
|
111
|
-
def _collect_power_values(self, data: GwEntityData, loc: Munch, tariff: str, legacy: bool = False) -> None:
|
112
|
-
"""Something."""
|
113
|
-
for loc.peak_select in ("nl_peak", "nl_offpeak"):
|
114
|
-
loc.locator = (
|
115
|
-
f'./{loc.log_type}[type="{loc.measurement}"]/period/'
|
116
|
-
f'measurement[@{tariff}="{loc.peak_select}"]'
|
117
|
-
)
|
118
|
-
if legacy:
|
119
|
-
loc.locator = (
|
120
|
-
f"./{loc.meas_list[0]}_{loc.log_type}/measurement"
|
121
|
-
f'[@directionality="{loc.meas_list[1]}"][@{tariff}="{loc.peak_select}"]'
|
122
|
-
)
|
123
|
-
|
124
|
-
loc = self._power_data_peak_value(loc, legacy)
|
125
|
-
if not loc.found:
|
126
|
-
continue
|
127
|
-
|
128
|
-
data = self._power_data_energy_diff(
|
129
|
-
loc.measurement, loc.net_string, loc.f_val, data
|
130
|
-
)
|
131
|
-
key = cast(SensorType, loc.key_string)
|
132
|
-
data["sensors"][key] = loc.f_val
|
133
|
-
|
134
|
-
def _count_data_items(self, data: GwEntityData) -> None:
|
135
|
-
"""When present, count the binary_sensors, sensors and switches dict-items, don't count the dicts.
|
136
|
-
|
137
|
-
Also, count the remaining single data items, the amount of dicts present have already been pre-subtracted in the previous step.
|
138
|
-
"""
|
139
|
-
if "binary_sensors" in data:
|
140
|
-
self._count += len(data["binary_sensors"]) - 1
|
141
|
-
if "sensors" in data:
|
142
|
-
self._count += len(data["sensors"]) - 1
|
143
|
-
if "switches" in data:
|
144
|
-
self._count += len(data["switches"]) - 1
|
145
|
-
self._count += len(data)
|
146
|
-
|
147
|
-
def _power_data_peak_value(self, loc: Munch, legacy: bool) -> Munch:
|
148
|
-
"""Helper-function for _power_data_from_location() and _power_data_from_modules()."""
|
149
|
-
loc.found = True
|
150
|
-
if loc.logs.find(loc.locator) is None:
|
151
|
-
loc = check_alternative_location(loc, legacy)
|
152
|
-
if not loc.found:
|
153
|
-
return loc
|
154
|
-
|
155
|
-
if (peak := loc.peak_select.split("_")[1]) == "offpeak":
|
156
|
-
peak = "off_peak"
|
157
|
-
log_found = loc.log_type.split("_")[0]
|
158
|
-
loc.key_string = f"{loc.measurement}_{peak}_{log_found}"
|
159
|
-
if "gas" in loc.measurement or loc.log_type == "point_meter":
|
160
|
-
loc.key_string = f"{loc.measurement}_{log_found}"
|
161
|
-
# Only for P1 Actual -------------------#
|
162
|
-
if "phase" in loc.measurement:
|
163
|
-
loc.key_string = f"{loc.measurement}"
|
164
|
-
# --------------------------------------#
|
165
|
-
loc.net_string = f"net_electricity_{log_found}"
|
166
|
-
val = loc.logs.find(loc.locator).text
|
167
|
-
loc.f_val = power_data_local_format(loc.attrs, loc.key_string, val)
|
168
|
-
|
169
|
-
return loc
|
170
|
-
|
171
|
-
def _power_data_energy_diff(
|
172
|
-
self,
|
173
|
-
measurement: str,
|
174
|
-
net_string: SensorType,
|
175
|
-
f_val: float | int,
|
176
|
-
data: GwEntityData,
|
177
|
-
) -> GwEntityData:
|
178
|
-
"""Calculate differential energy."""
|
179
|
-
if (
|
180
|
-
"electricity" in measurement
|
181
|
-
and "phase" not in measurement
|
182
|
-
and "interval" not in net_string
|
183
|
-
):
|
184
|
-
diff = 1
|
185
|
-
if "produced" in measurement:
|
186
|
-
diff = -1
|
187
|
-
if net_string not in data["sensors"]:
|
188
|
-
tmp_val: float | int = 0
|
189
|
-
else:
|
190
|
-
tmp_val = data["sensors"][net_string]
|
191
|
-
|
192
|
-
if isinstance(f_val, int):
|
193
|
-
tmp_val += f_val * diff
|
194
|
-
else:
|
195
|
-
tmp_val += float(f_val * diff)
|
196
|
-
tmp_val = float(f"{round(tmp_val, 3):.3f}")
|
197
|
-
|
198
|
-
data["sensors"][net_string] = tmp_val
|
199
|
-
|
200
|
-
return data
|
201
|
-
|
202
125
|
def _create_gw_entities(self, appl: Munch) -> None:
|
203
126
|
"""Helper-function for creating/updating gw_entities."""
|
204
127
|
self.gw_entities[appl.entity_id] = {"dev_class": appl.pwclass}
|
@@ -220,9 +143,7 @@ class SmileCommon:
|
|
220
143
|
self.gw_entities[appl.entity_id][appl_key] = value
|
221
144
|
self._count += 1
|
222
145
|
|
223
|
-
def _entity_switching_group(
|
224
|
-
self, entity: GwEntityData, data: GwEntityData
|
225
|
-
) -> None:
|
146
|
+
def _entity_switching_group(self, entity: GwEntityData, data: GwEntityData) -> None:
|
226
147
|
"""Helper-function for _get_device_zone_data().
|
227
148
|
|
228
149
|
Determine switching group device data.
|
@@ -268,7 +189,9 @@ class SmileCommon:
|
|
268
189
|
|
269
190
|
return switch_groups
|
270
191
|
|
271
|
-
def _get_lock_state(
|
192
|
+
def _get_lock_state(
|
193
|
+
self, xml: etree, data: GwEntityData, stretch_v2: bool = False
|
194
|
+
) -> None:
|
272
195
|
"""Helper-function for _get_measurement_data().
|
273
196
|
|
274
197
|
Adam & Stretches: obtain the relay-switch lock state.
|
@@ -319,20 +242,6 @@ class SmileCommon:
|
|
319
242
|
module_data["vendor_model"] = module.find("vendor_model").text
|
320
243
|
module_data["hardware_version"] = module.find("hardware_version").text
|
321
244
|
module_data["firmware_version"] = module.find("firmware_version").text
|
322
|
-
|
245
|
+
get_zigbee_data(module, module_data, legacy)
|
323
246
|
|
324
247
|
return module_data
|
325
|
-
|
326
|
-
def _get_zigbee_data(self, module: etree, module_data: ModuleData, legacy: bool) -> None:
|
327
|
-
"""Helper-function for _get_module_data()."""
|
328
|
-
if legacy:
|
329
|
-
# Stretches
|
330
|
-
if (router := module.find("./protocols/network_router")) is not None:
|
331
|
-
module_data["zigbee_mac_address"] = router.find("mac_address").text
|
332
|
-
# Also look for the Circle+/Stealth M+
|
333
|
-
if (coord := module.find("./protocols/network_coordinator")) is not None:
|
334
|
-
module_data["zigbee_mac_address"] = coord.find("mac_address").text
|
335
|
-
# Adam
|
336
|
-
elif (zb_node := module.find("./protocols/zig_bee_node")) is not None:
|
337
|
-
module_data["zigbee_mac_address"] = zb_node.find("mac_address").text
|
338
|
-
module_data["reachable"] = zb_node.find("reachable").text == "true"
|