plugwise 1.7.6__tar.gz → 1.7.7__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-1.7.6 → plugwise-1.7.7}/PKG-INFO +2 -1
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/__init__.py +43 -51
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/common.py +15 -5
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/constants.py +1 -1
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/data.py +12 -12
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/helper.py +19 -36
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/legacy/helper.py +15 -30
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/legacy/smile.py +3 -18
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/smile.py +27 -34
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise.egg-info/PKG-INFO +2 -1
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise.egg-info/requires.txt +1 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/pyproject.toml +2 -1
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_adam.py +16 -16
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_anna.py +31 -31
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_init.py +75 -136
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_legacy_anna.py +4 -4
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_legacy_p1.py +5 -5
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_legacy_stretch.py +7 -7
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_p1.py +5 -5
- {plugwise-1.7.6 → plugwise-1.7.7}/LICENSE +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/README.md +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/exceptions.py +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/legacy/data.py +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/py.typed +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/smilecomm.py +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise/util.py +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise.egg-info/SOURCES.txt +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise.egg-info/dependency_links.txt +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/plugwise.egg-info/top_level.txt +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/setup.cfg +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_generic.py +0 -0
- {plugwise-1.7.6 → plugwise-1.7.7}/tests/test_legacy_generic.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: plugwise
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.7
|
4
4
|
Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
|
5
5
|
Author: Plugwise device owners
|
6
6
|
Maintainer: bouwew, CoMPaTech
|
@@ -16,6 +16,7 @@ Classifier: Topic :: Home Automation
|
|
16
16
|
Requires-Python: >=3.13
|
17
17
|
Description-Content-Type: text/markdown
|
18
18
|
License-File: LICENSE
|
19
|
+
Requires-Dist: aiofiles
|
19
20
|
Requires-Dist: aiohttp
|
20
21
|
Requires-Dist: defusedxml
|
21
22
|
Requires-Dist: munch
|
@@ -5,6 +5,8 @@ Plugwise backend module for Home Assistant Core.
|
|
5
5
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
|
+
from typing import cast
|
9
|
+
|
8
10
|
from plugwise.constants import (
|
9
11
|
DEFAULT_LEGACY_TIMEOUT,
|
10
12
|
DEFAULT_PORT,
|
@@ -36,6 +38,7 @@ from plugwise.smilecomm import SmileComm
|
|
36
38
|
|
37
39
|
import aiohttp
|
38
40
|
from defusedxml import ElementTree as etree
|
41
|
+
from munch import Munch
|
39
42
|
from packaging.version import Version, parse
|
40
43
|
|
41
44
|
|
@@ -72,16 +75,17 @@ class Smile(SmileComm):
|
|
72
75
|
self._smile_api: SmileAPI | SmileLegacyAPI
|
73
76
|
self._stretch_v2 = False
|
74
77
|
self._target_smile: str = NONE
|
75
|
-
self.
|
76
|
-
self.
|
77
|
-
self.
|
78
|
-
self.
|
79
|
-
self.
|
80
|
-
self.
|
81
|
-
self.
|
82
|
-
self.
|
83
|
-
self.
|
84
|
-
self.
|
78
|
+
self.smile: Munch = Munch()
|
79
|
+
self.smile.hostname = NONE
|
80
|
+
self.smile.hw_version = None
|
81
|
+
self.smile.legacy = False
|
82
|
+
self.smile.mac_address = None
|
83
|
+
self.smile.model = NONE
|
84
|
+
self.smile.model_id = None
|
85
|
+
self.smile.name = NONE
|
86
|
+
self.smile.type = NONE
|
87
|
+
self.smile.version = Version("0.0.0")
|
88
|
+
self.smile.zigbee_mac_address = None
|
85
89
|
|
86
90
|
@property
|
87
91
|
def cooling_present(self) -> bool:
|
@@ -109,7 +113,7 @@ class Smile(SmileComm):
|
|
109
113
|
|
110
114
|
All non-legacy devices support gateway-rebooting.
|
111
115
|
"""
|
112
|
-
return not self.
|
116
|
+
return not self.smile.legacy
|
113
117
|
|
114
118
|
async def connect(self) -> Version:
|
115
119
|
"""Connect to the Plugwise Gateway and determine its name, type, version, and other data."""
|
@@ -158,16 +162,9 @@ class Smile(SmileComm):
|
|
158
162
|
self._opentherm_device,
|
159
163
|
self._request,
|
160
164
|
self._schedule_old_states,
|
161
|
-
self.
|
162
|
-
self.smile_hw_version,
|
163
|
-
self.smile_mac_address,
|
164
|
-
self.smile_model,
|
165
|
-
self.smile_model_id,
|
166
|
-
self.smile_name,
|
167
|
-
self.smile_type,
|
168
|
-
self.smile_version,
|
165
|
+
self.smile,
|
169
166
|
)
|
170
|
-
if not self.
|
167
|
+
if not self.smile.legacy
|
171
168
|
else SmileLegacyAPI(
|
172
169
|
self._is_thermostat,
|
173
170
|
self._loc_data,
|
@@ -176,21 +173,14 @@ class Smile(SmileComm):
|
|
176
173
|
self._request,
|
177
174
|
self._stretch_v2,
|
178
175
|
self._target_smile,
|
179
|
-
self.
|
180
|
-
self.smile_hw_version,
|
181
|
-
self.smile_mac_address,
|
182
|
-
self.smile_model,
|
183
|
-
self.smile_name,
|
184
|
-
self.smile_type,
|
185
|
-
self.smile_version,
|
186
|
-
self.smile_zigbee_mac_address,
|
176
|
+
self.smile,
|
187
177
|
)
|
188
178
|
)
|
189
179
|
|
190
180
|
# Update all endpoints on first connect
|
191
181
|
await self._smile_api.full_xml_update()
|
192
182
|
|
193
|
-
return self.
|
183
|
+
return cast(Version, self.smile.version)
|
194
184
|
|
195
185
|
async def _smile_detect(
|
196
186
|
self, result: etree.Element, dsmrmain: etree.Element
|
@@ -203,15 +193,17 @@ class Smile(SmileComm):
|
|
203
193
|
if (gateway := result.find("./gateway")) is not None:
|
204
194
|
if (v_model := gateway.find("vendor_model")) is not None:
|
205
195
|
model = v_model.text
|
206
|
-
self.
|
207
|
-
self.
|
208
|
-
self.
|
209
|
-
self.
|
210
|
-
self.
|
196
|
+
self.smile.version = parse(gateway.find("firmware_version").text)
|
197
|
+
self.smile.hw_version = gateway.find("hardware_version").text
|
198
|
+
self.smile.hostname = gateway.find("hostname").text
|
199
|
+
self.smile.mac_address = gateway.find("mac_address").text
|
200
|
+
self.smile.model_id = gateway.find("vendor_model").text
|
211
201
|
else:
|
212
202
|
model = await self._smile_detect_legacy(result, dsmrmain, model)
|
213
203
|
|
214
|
-
if model == "Unknown" or self.
|
204
|
+
if model == "Unknown" or self.smile.version == Version(
|
205
|
+
"0.0.0"
|
206
|
+
): # pragma: no cover
|
215
207
|
# Corner case check
|
216
208
|
LOGGER.error(
|
217
209
|
"Unable to find model or version information, please create"
|
@@ -219,7 +211,7 @@ class Smile(SmileComm):
|
|
219
211
|
)
|
220
212
|
raise UnsupportedDeviceError
|
221
213
|
|
222
|
-
version_major = str(self.
|
214
|
+
version_major = str(self.smile.version.major)
|
223
215
|
self._target_smile = f"{model}_v{version_major}"
|
224
216
|
LOGGER.debug("Plugwise identified as %s", self._target_smile)
|
225
217
|
if self._target_smile not in SMILES:
|
@@ -230,7 +222,7 @@ class Smile(SmileComm):
|
|
230
222
|
)
|
231
223
|
raise UnsupportedDeviceError
|
232
224
|
|
233
|
-
if not self.
|
225
|
+
if not self.smile.legacy:
|
234
226
|
self._timeout = DEFAULT_TIMEOUT
|
235
227
|
|
236
228
|
if self._target_smile in ("smile_open_therm_v2", "smile_thermo_v3"):
|
@@ -240,14 +232,14 @@ class Smile(SmileComm):
|
|
240
232
|
) # pragma: no cover
|
241
233
|
raise UnsupportedDeviceError # pragma: no cover
|
242
234
|
|
243
|
-
self.
|
244
|
-
self.
|
245
|
-
self.
|
235
|
+
self.smile.model = "Gateway"
|
236
|
+
self.smile.name = SMILES[self._target_smile].smile_name
|
237
|
+
self.smile.type = SMILES[self._target_smile].smile_type
|
246
238
|
|
247
|
-
if self.
|
239
|
+
if self.smile.type == "stretch":
|
248
240
|
self._stretch_v2 = int(version_major) == 2
|
249
241
|
|
250
|
-
if self.
|
242
|
+
if self.smile.type == "thermostat":
|
251
243
|
self._is_thermostat = True
|
252
244
|
# For Adam, Anna, determine the system capabilities:
|
253
245
|
# Find the connected heating/cooling device (heater_central),
|
@@ -275,13 +267,13 @@ class Smile(SmileComm):
|
|
275
267
|
return_model = model
|
276
268
|
# Stretch: find the MAC of the zigbee master_controller (= Stick)
|
277
269
|
if (network := result.find("./module/protocols/master_controller")) is not None:
|
278
|
-
self.
|
270
|
+
self.smile.zigbee_mac_address = network.find("mac_address").text
|
279
271
|
# Find the active MAC in case there is an orphaned Stick
|
280
272
|
if zb_networks := result.findall("./network"):
|
281
273
|
for zb_network in zb_networks:
|
282
274
|
if zb_network.find("./nodes/network_router") is not None:
|
283
275
|
network = zb_network.find("./master_controller")
|
284
|
-
self.
|
276
|
+
self.smile.zigbee_mac_address = network.find("mac_address").text
|
285
277
|
|
286
278
|
# Legacy Anna or Stretch:
|
287
279
|
if (
|
@@ -289,22 +281,22 @@ class Smile(SmileComm):
|
|
289
281
|
or network is not None
|
290
282
|
):
|
291
283
|
system = await self._request(SYSTEM)
|
292
|
-
self.
|
284
|
+
self.smile.version = parse(system.find("./gateway/firmware").text)
|
293
285
|
return_model = str(system.find("./gateway/product").text)
|
294
|
-
self.
|
286
|
+
self.smile.hostname = system.find("./gateway/hostname").text
|
295
287
|
# If wlan0 contains data it's active, eth0 should be checked last as is preferred
|
296
288
|
for network in ("wlan0", "eth0"):
|
297
289
|
locator = f"./{network}/mac"
|
298
290
|
if (net_locator := system.find(locator)) is not None:
|
299
|
-
self.
|
291
|
+
self.smile.mac_address = net_locator.text
|
300
292
|
|
301
293
|
# P1 legacy:
|
302
294
|
elif dsmrmain is not None:
|
303
295
|
status = await self._request(STATUS)
|
304
|
-
self.
|
296
|
+
self.smile.version = parse(status.find("./system/version").text)
|
305
297
|
return_model = str(status.find("./system/product").text)
|
306
|
-
self.
|
307
|
-
self.
|
298
|
+
self.smile.hostname = status.find("./network/hostname").text
|
299
|
+
self.smile.mac_address = status.find("./network/mac_address").text
|
308
300
|
else: # pragma: no cover
|
309
301
|
# No cornercase, just end of the line
|
310
302
|
LOGGER.error(
|
@@ -313,7 +305,7 @@ class Smile(SmileComm):
|
|
313
305
|
)
|
314
306
|
raise ResponseError
|
315
307
|
|
316
|
-
self.
|
308
|
+
self.smile.legacy = True
|
317
309
|
return return_model
|
318
310
|
|
319
311
|
async def async_update(self) -> dict[str, GwEntityData]:
|
@@ -10,6 +10,7 @@ from typing import cast
|
|
10
10
|
from plugwise.constants import (
|
11
11
|
ANNA,
|
12
12
|
NONE,
|
13
|
+
PRIORITY_DEVICE_CLASSES,
|
13
14
|
SPECIAL_PLUG_TYPES,
|
14
15
|
SWITCH_GROUP_TYPES,
|
15
16
|
ApplianceType,
|
@@ -55,17 +56,16 @@ class SmileCommon:
|
|
55
56
|
self._heater_id: str = NONE
|
56
57
|
self._on_off_device: bool
|
57
58
|
self.gw_entities: dict[str, GwEntityData] = {}
|
58
|
-
self.
|
59
|
-
self.smile_type: str
|
59
|
+
self.smile: Munch
|
60
60
|
|
61
61
|
@property
|
62
62
|
def heater_id(self) -> str:
|
63
63
|
"""Return the heater-id."""
|
64
64
|
return self._heater_id
|
65
65
|
|
66
|
-
def
|
66
|
+
def check_name(self, name: str) -> bool:
|
67
67
|
"""Helper-function checking the smile-name."""
|
68
|
-
return self.
|
68
|
+
return bool(self.smile.name == name)
|
69
69
|
|
70
70
|
def _appl_heater_central_info(
|
71
71
|
self,
|
@@ -153,6 +153,16 @@ class SmileCommon:
|
|
153
153
|
self.gw_entities[appl.entity_id][appl_key] = value
|
154
154
|
self._count += 1
|
155
155
|
|
156
|
+
def _reorder_devices(self) -> None:
|
157
|
+
"""Place the gateway and optional heater_central devices as 1st and 2nd."""
|
158
|
+
reordered = {}
|
159
|
+
for dev_class in PRIORITY_DEVICE_CLASSES:
|
160
|
+
for entity_id, entity in dict(self.gw_entities).items():
|
161
|
+
if entity["dev_class"] == dev_class:
|
162
|
+
reordered[entity_id] = self.gw_entities.pop(entity_id)
|
163
|
+
break
|
164
|
+
self.gw_entities = {**reordered, **self.gw_entities}
|
165
|
+
|
156
166
|
def _entity_switching_group(self, entity: GwEntityData, data: GwEntityData) -> None:
|
157
167
|
"""Helper-function for _get_device_zone_data().
|
158
168
|
|
@@ -173,7 +183,7 @@ class SmileCommon:
|
|
173
183
|
"""
|
174
184
|
switch_groups: dict[str, GwEntityData] = {}
|
175
185
|
# P1 and Anna don't have switchgroups
|
176
|
-
if self.
|
186
|
+
if self.smile.type == "power" or self.check_name(ANNA):
|
177
187
|
return switch_groups
|
178
188
|
|
179
189
|
for group in self._domain_objects.findall("./group"):
|
@@ -86,7 +86,7 @@ MIN_SETPOINT: Final[float] = 4.0
|
|
86
86
|
MODULE_LOCATOR: Final = "./logs/point_log/*[@id]"
|
87
87
|
NONE: Final = "None"
|
88
88
|
OFF: Final = "off"
|
89
|
-
PRIORITY_DEVICE_CLASSES = ("
|
89
|
+
PRIORITY_DEVICE_CLASSES = ("gateway", "heater_central")
|
90
90
|
|
91
91
|
# XML data paths
|
92
92
|
APPLIANCES: Final = "/core/appliances"
|
@@ -35,7 +35,7 @@ class SmileData(SmileHelper):
|
|
35
35
|
Collect data for each entity and add to self.gw_entities.
|
36
36
|
"""
|
37
37
|
self._update_gw_entities()
|
38
|
-
if self.
|
38
|
+
if self.check_name(ADAM):
|
39
39
|
self._update_zones()
|
40
40
|
self.gw_entities.update(self._zones)
|
41
41
|
|
@@ -86,7 +86,7 @@ class SmileData(SmileHelper):
|
|
86
86
|
mac_pattern = re.compile(r"(?:[0-9A-F]{2}){8}")
|
87
87
|
matches = ["Battery", "below"]
|
88
88
|
if self._notifications:
|
89
|
-
for msg_id, notification in
|
89
|
+
for msg_id, notification in self._notifications.copy().items():
|
90
90
|
mac_address: str | None = None
|
91
91
|
message: str | None = notification.get("message")
|
92
92
|
warning: str | None = notification.get("warning")
|
@@ -111,7 +111,7 @@ class SmileData(SmileHelper):
|
|
111
111
|
"""Helper-function adding or updating the Plugwise notifications."""
|
112
112
|
if (
|
113
113
|
entity_id == self._gateway_id
|
114
|
-
and (self._is_thermostat or self.
|
114
|
+
and (self._is_thermostat or self.smile.type == "power")
|
115
115
|
) or (
|
116
116
|
"binary_sensors" in entity
|
117
117
|
and "plugwise_notification" in entity["binary_sensors"]
|
@@ -124,7 +124,7 @@ class SmileData(SmileHelper):
|
|
124
124
|
"""Helper-function for adding/updating various cooling-related values."""
|
125
125
|
# For Anna and heating + cooling, replace setpoint with setpoint_high/_low
|
126
126
|
if (
|
127
|
-
self.
|
127
|
+
self.check_name(ANNA)
|
128
128
|
and self._cooling_present
|
129
129
|
and entity["dev_class"] == "thermostat"
|
130
130
|
):
|
@@ -194,11 +194,11 @@ class SmileData(SmileHelper):
|
|
194
194
|
# Switching groups data
|
195
195
|
self._entity_switching_group(entity, data)
|
196
196
|
# Adam data
|
197
|
-
if self.
|
197
|
+
if self.check_name(ADAM):
|
198
198
|
self._get_adam_data(entity, data)
|
199
199
|
|
200
200
|
# Thermostat data for Anna (presets, temperatures etc)
|
201
|
-
if self.
|
201
|
+
if self.check_name(ANNA) and entity["dev_class"] == "thermostat":
|
202
202
|
self._climate_data(entity_id, entity, data)
|
203
203
|
self._get_anna_control_state(data)
|
204
204
|
|
@@ -232,12 +232,12 @@ class SmileData(SmileHelper):
|
|
232
232
|
if self._on_off_device and isinstance(self._heating_valves(), int):
|
233
233
|
data["binary_sensors"]["heating_state"] = self._heating_valves() != 0
|
234
234
|
# Add cooling_enabled binary_sensor
|
235
|
-
if
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
235
|
+
if (
|
236
|
+
"binary_sensors" in data
|
237
|
+
and "cooling_enabled" not in data["binary_sensors"]
|
238
|
+
and self._cooling_present
|
239
|
+
):
|
240
|
+
data["binary_sensors"]["cooling_enabled"] = self._cooling_enabled
|
241
241
|
|
242
242
|
# Show the allowed regulation_modes and gateway_modes
|
243
243
|
if entity["dev_class"] == "gateway":
|
@@ -28,7 +28,6 @@ from plugwise.constants import (
|
|
28
28
|
NONE,
|
29
29
|
OFF,
|
30
30
|
P1_MEASUREMENTS,
|
31
|
-
PRIORITY_DEVICE_CLASSES,
|
32
31
|
TEMP_CELSIUS,
|
33
32
|
THERMOSTAT_CLASSES,
|
34
33
|
TOGGLES,
|
@@ -85,11 +84,7 @@ class SmileHelper(SmileCommon):
|
|
85
84
|
self._gateway_id: str = NONE
|
86
85
|
self._zones: dict[str, GwEntityData]
|
87
86
|
self.gw_entities: dict[str, GwEntityData]
|
88
|
-
self.
|
89
|
-
self.smile_mac_address: str | None
|
90
|
-
self.smile_model: str
|
91
|
-
self.smile_model_id: str | None
|
92
|
-
self.smile_version: version.Version
|
87
|
+
self.smile: Munch = Munch()
|
93
88
|
|
94
89
|
@property
|
95
90
|
def gateway_id(self) -> str:
|
@@ -160,11 +155,11 @@ class SmileHelper(SmileCommon):
|
|
160
155
|
|
161
156
|
self._create_gw_entities(appl)
|
162
157
|
|
163
|
-
if self.
|
158
|
+
if self.smile.type == "power":
|
164
159
|
self._get_p1_smartmeter_info()
|
165
160
|
|
166
161
|
# Sort the gw_entities
|
167
|
-
self.
|
162
|
+
self._reorder_devices()
|
168
163
|
|
169
164
|
def _get_p1_smartmeter_info(self) -> None:
|
170
165
|
"""For P1 collect the connected SmartMeter info from the Home/building location.
|
@@ -197,18 +192,6 @@ class SmileHelper(SmileCommon):
|
|
197
192
|
|
198
193
|
self._create_gw_entities(appl)
|
199
194
|
|
200
|
-
def _sort_gw_entities(self) -> None:
|
201
|
-
"""Place the gateway and optional heater_central entities as 1st and 2nd."""
|
202
|
-
for dev_class in PRIORITY_DEVICE_CLASSES:
|
203
|
-
for entity_id, entity in dict(self.gw_entities).items():
|
204
|
-
if entity["dev_class"] == dev_class:
|
205
|
-
priority_entity = entity
|
206
|
-
self.gw_entities.pop(entity_id)
|
207
|
-
other_entities = self.gw_entities
|
208
|
-
priority_entities = {entity_id: priority_entity}
|
209
|
-
self.gw_entities = {**priority_entities, **other_entities}
|
210
|
-
break
|
211
|
-
|
212
195
|
def _all_locations(self) -> None:
|
213
196
|
"""Collect all locations."""
|
214
197
|
loc = Munch()
|
@@ -268,16 +251,16 @@ class SmileHelper(SmileCommon):
|
|
268
251
|
def _appl_gateway_info(self, appl: Munch, appliance: etree.Element) -> Munch:
|
269
252
|
"""Helper-function for _appliance_info_finder()."""
|
270
253
|
self._gateway_id = appliance.attrib["id"]
|
271
|
-
appl.firmware = str(self.
|
272
|
-
appl.hardware = self.
|
273
|
-
appl.mac = self.
|
274
|
-
appl.model = self.
|
275
|
-
appl.model_id = self.
|
276
|
-
appl.name = self.
|
254
|
+
appl.firmware = str(self.smile.version)
|
255
|
+
appl.hardware = self.smile.hw_version
|
256
|
+
appl.mac = self.smile.mac_address
|
257
|
+
appl.model = self.smile.model
|
258
|
+
appl.model_id = self.smile.model_id
|
259
|
+
appl.name = self.smile.name
|
277
260
|
appl.vendor_name = "Plugwise"
|
278
261
|
|
279
262
|
# Adam: collect the ZigBee MAC address of the Smile
|
280
|
-
if self.
|
263
|
+
if self.check_name(ADAM):
|
281
264
|
if (
|
282
265
|
found := self._domain_objects.find(".//protocols/zig_bee_coordinator")
|
283
266
|
) is not None:
|
@@ -346,7 +329,7 @@ class SmileHelper(SmileCommon):
|
|
346
329
|
# Get P1 smartmeter data from LOCATIONS
|
347
330
|
entity = self.gw_entities[entity_id]
|
348
331
|
# !! DON'T CHANGE below two if-lines, will break stuff !!
|
349
|
-
if self.
|
332
|
+
if self.smile.type == "power":
|
350
333
|
if entity["dev_class"] == "smartmeter":
|
351
334
|
data.update(self._power_data_from_location())
|
352
335
|
|
@@ -383,7 +366,7 @@ class SmileHelper(SmileCommon):
|
|
383
366
|
data.pop("c_heating_state")
|
384
367
|
self._count -= 1
|
385
368
|
|
386
|
-
if self._is_thermostat and self.
|
369
|
+
if self._is_thermostat and self.check_name(ANNA):
|
387
370
|
self._update_anna_cooling(entity_id, data)
|
388
371
|
|
389
372
|
self._cleanup_data(data)
|
@@ -484,7 +467,7 @@ class SmileHelper(SmileCommon):
|
|
484
467
|
item == "thermostat"
|
485
468
|
and (
|
486
469
|
entity["dev_class"] != "climate"
|
487
|
-
if self.
|
470
|
+
if self.check_name(ADAM)
|
488
471
|
else entity["dev_class"] != "thermostat"
|
489
472
|
)
|
490
473
|
):
|
@@ -539,7 +522,7 @@ class SmileHelper(SmileCommon):
|
|
539
522
|
|
540
523
|
Collect the requested gateway mode.
|
541
524
|
"""
|
542
|
-
if not (self.
|
525
|
+
if not (self.check_name(ADAM) and entity_id == self._gateway_id):
|
543
526
|
return None
|
544
527
|
|
545
528
|
if (search := search_actuator_functionalities(appliance, key)) is not None:
|
@@ -605,10 +588,10 @@ class SmileHelper(SmileCommon):
|
|
605
588
|
|
606
589
|
Solution for Core issue #81839.
|
607
590
|
"""
|
608
|
-
if self.
|
591
|
+
if self.check_name(ANNA):
|
609
592
|
data["binary_sensors"]["heating_state"] = data["c_heating_state"]
|
610
593
|
|
611
|
-
if self.
|
594
|
+
if self.check_name(ADAM):
|
612
595
|
# First count when not present, then create and init to False.
|
613
596
|
# When present init to False
|
614
597
|
if "heating_state" not in data["binary_sensors"]:
|
@@ -723,7 +706,7 @@ class SmileHelper(SmileCommon):
|
|
723
706
|
for entity_id, entity in self.gw_entities.items():
|
724
707
|
self._rank_thermostat(thermo_matching, loc_id, entity_id, entity)
|
725
708
|
|
726
|
-
for loc_id, loc_data in
|
709
|
+
for loc_id, loc_data in self._thermo_locs.items():
|
727
710
|
if loc_data["primary_prio"] != 0:
|
728
711
|
self._zones[loc_id] = {
|
729
712
|
"dev_class": "climate",
|
@@ -799,8 +782,8 @@ class SmileHelper(SmileCommon):
|
|
799
782
|
|
800
783
|
# Handle missing control_state in regulation_mode off for firmware >= 3.2.0 (issue #776)
|
801
784
|
# In newer firmware versions, default to "off" when control_state is not present
|
802
|
-
if self.
|
803
|
-
if self.
|
785
|
+
if self.smile.version != version.Version("0.0.0"):
|
786
|
+
if self.smile.version >= version.parse("3.2.0"):
|
804
787
|
return "off"
|
805
788
|
|
806
789
|
# Older Adam firmware does not have the control_state xml-key
|
@@ -23,7 +23,6 @@ from plugwise.constants import (
|
|
23
23
|
NONE,
|
24
24
|
OFF,
|
25
25
|
P1_LEGACY_MEASUREMENTS,
|
26
|
-
PRIORITY_DEVICE_CLASSES,
|
27
26
|
TEMP_CELSIUS,
|
28
27
|
THERMOSTAT_CLASSES,
|
29
28
|
UOM,
|
@@ -47,7 +46,6 @@ from plugwise.util import (
|
|
47
46
|
# This way of importing aiohttp is because of patch/mocking in testing (aiohttp timeouts)
|
48
47
|
from defusedxml import ElementTree as etree
|
49
48
|
from munch import Munch
|
50
|
-
from packaging.version import Version
|
51
49
|
|
52
50
|
|
53
51
|
def etree_to_dict(element: etree.Element) -> dict[str, str]:
|
@@ -73,10 +71,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
73
71
|
self._modules: etree.Element
|
74
72
|
self._stretch_v2: bool
|
75
73
|
self.gw_entities: dict[str, GwEntityData] = {}
|
76
|
-
self.
|
77
|
-
self.smile_model: str
|
78
|
-
self.smile_version: Version
|
79
|
-
self.smile_zigbee_mac_address: str | None
|
74
|
+
self.smile: Munch = Munch()
|
80
75
|
|
81
76
|
@property
|
82
77
|
def gateway_id(self) -> str:
|
@@ -95,7 +90,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
95
90
|
|
96
91
|
self._create_legacy_gateway()
|
97
92
|
# For legacy P1 collect the connected SmartMeter info
|
98
|
-
if self.
|
93
|
+
if self.smile.type == "power":
|
99
94
|
appl = Munch()
|
100
95
|
self._p1_smartmeter_info_finder(appl)
|
101
96
|
# Legacy P1 has no more devices
|
@@ -140,17 +135,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
140
135
|
continue # pragma: no cover
|
141
136
|
|
142
137
|
self._create_gw_entities(appl)
|
143
|
-
|
144
|
-
# Place the gateway and optional heater_central devices as 1st and 2nd
|
145
|
-
for dev_class in PRIORITY_DEVICE_CLASSES:
|
146
|
-
for entity_id, entity in dict(self.gw_entities).items():
|
147
|
-
if entity["dev_class"] == dev_class:
|
148
|
-
tmp_entity = entity
|
149
|
-
self.gw_entities.pop(entity_id)
|
150
|
-
cleared_dict = self.gw_entities
|
151
|
-
add_to_front = {entity_id: tmp_entity}
|
152
|
-
self.gw_entities = {**add_to_front, **cleared_dict}
|
153
|
-
break
|
138
|
+
self._reorder_devices()
|
154
139
|
|
155
140
|
def _all_locations(self) -> None:
|
156
141
|
"""Collect all locations."""
|
@@ -167,13 +152,13 @@ class SmileLegacyHelper(SmileCommon):
|
|
167
152
|
loc.loc_id = location.attrib["id"]
|
168
153
|
# Filter the valid single location for P1 legacy: services not empty
|
169
154
|
locator = "./services"
|
170
|
-
if self.
|
155
|
+
if self.smile.type == "power" and len(location.find(locator)) == 0:
|
171
156
|
continue
|
172
157
|
|
173
158
|
if loc.name == "Home":
|
174
159
|
self._home_loc_id = loc.loc_id
|
175
160
|
# Replace location-name for P1 legacy, can contain privacy-related info
|
176
|
-
if self.
|
161
|
+
if self.smile.type == "power":
|
177
162
|
loc.name = "Home"
|
178
163
|
self._home_loc_id = loc.loc_id
|
179
164
|
|
@@ -185,18 +170,18 @@ class SmileLegacyHelper(SmileCommon):
|
|
185
170
|
Use the home_location or FAKE_APPL as entity id.
|
186
171
|
"""
|
187
172
|
self._gateway_id = self._home_loc_id
|
188
|
-
if self.
|
173
|
+
if self.smile.type == "power":
|
189
174
|
self._gateway_id = FAKE_APPL
|
190
175
|
|
191
176
|
self.gw_entities[self._gateway_id] = {"dev_class": "gateway"}
|
192
177
|
self._count += 1
|
193
178
|
for key, value in {
|
194
|
-
"firmware": str(self.
|
179
|
+
"firmware": str(self.smile.version),
|
195
180
|
"location": self._home_loc_id,
|
196
|
-
"mac_address": self.
|
197
|
-
"model": self.
|
198
|
-
"name": self.
|
199
|
-
"zigbee_mac_address": self.
|
181
|
+
"mac_address": self.smile.mac_address,
|
182
|
+
"model": self.smile.model,
|
183
|
+
"name": self.smile.name,
|
184
|
+
"zigbee_mac_address": self.smile.zigbee_mac_address,
|
200
185
|
"vendor": "Plugwise",
|
201
186
|
}.items():
|
202
187
|
if value is not None:
|
@@ -224,14 +209,14 @@ class SmileLegacyHelper(SmileCommon):
|
|
224
209
|
|
225
210
|
Collect energy entity info (Smartmeter, Circle, Stealth, etc.): firmware, model and vendor name.
|
226
211
|
"""
|
227
|
-
if self.
|
212
|
+
if self.smile.type in ("power", "stretch"):
|
228
213
|
locator = "./services/electricity_point_meter"
|
229
214
|
module_data = self._get_module_data(
|
230
215
|
appliance, locator, self._modules, legacy=True
|
231
216
|
)
|
232
217
|
appl.zigbee_mac = module_data["zigbee_mac_address"]
|
233
218
|
# Filter appliance without zigbee_mac, it's an orphaned device
|
234
|
-
if appl.zigbee_mac is None and self.
|
219
|
+
if appl.zigbee_mac is None and self.smile.type != "power":
|
235
220
|
return None
|
236
221
|
|
237
222
|
appl.hardware = module_data["hardware_version"]
|
@@ -253,7 +238,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
253
238
|
appl.entity_id = loc_id
|
254
239
|
appl.location = loc_id
|
255
240
|
appl.mac = None
|
256
|
-
appl.model = self.
|
241
|
+
appl.model = self.smile.model
|
257
242
|
appl.model_id = None
|
258
243
|
appl.name = "P1"
|
259
244
|
appl.pwclass = "smartmeter"
|
@@ -272,7 +257,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
272
257
|
# Get P1 smartmeter data from MODULES
|
273
258
|
entity = self.gw_entities[entity_id]
|
274
259
|
# !! DON'T CHANGE below two if-lines, will break stuff !!
|
275
|
-
if self.
|
260
|
+
if self.smile.type == "power":
|
276
261
|
if entity["dev_class"] == "smartmeter":
|
277
262
|
data.update(self._power_data_from_modules())
|
278
263
|
|