plugwise 1.7.2__tar.gz → 1.7.3a1__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.2 → plugwise-1.7.3a1}/PKG-INFO +4 -3
- {plugwise-1.7.2 → plugwise-1.7.3a1}/README.md +3 -2
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/__init__.py +8 -7
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/common.py +11 -9
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/data.py +8 -9
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/helper.py +14 -13
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/legacy/helper.py +9 -7
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/legacy/smile.py +30 -27
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/smile.py +60 -32
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/smilecomm.py +4 -2
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/util.py +5 -5
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise.egg-info/PKG-INFO +4 -3
- {plugwise-1.7.2 → plugwise-1.7.3a1}/pyproject.toml +1 -1
- {plugwise-1.7.2 → plugwise-1.7.3a1}/LICENSE +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/constants.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/exceptions.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/legacy/data.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise/py.typed +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise.egg-info/SOURCES.txt +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise.egg-info/dependency_links.txt +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise.egg-info/requires.txt +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/plugwise.egg-info/top_level.txt +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/setup.cfg +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/setup.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_adam.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_anna.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_generic.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_init.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_legacy_anna.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_legacy_generic.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_legacy_p1.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_legacy_stretch.py +0 -0
- {plugwise-1.7.2 → plugwise-1.7.3a1}/tests/test_p1.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: plugwise
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.3a1
|
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
|
@@ -48,12 +48,13 @@ Requires-Dist: python-dateutil
|
|
48
48
|
|
49
49
|
# Plugwise python module
|
50
50
|
|
51
|
-
This module is the backend for the [`plugwise` component](https://github.com/home-assistant/core/tree/dev/homeassistant/components/plugwise) in Home Assistant Core (which we maintain as
|
51
|
+
This module is the backend for the [`plugwise` component](https://github.com/home-assistant/core/tree/dev/homeassistant/components/plugwise) in Home Assistant Core (which we maintain as code owners).
|
52
52
|
|
53
|
-
This module supports `Smile`s
|
53
|
+
This module supports Hubs such as `Adam`, `Smile`s for Anna and P1 and `Stretch`, i.e. the networked plugwise devices. For the USB (or Stick-standalone version) please refer to upcoming [`plugwise-usb` component](https://github.com/plugwise/plugwise_usb-beta).
|
54
54
|
|
55
55
|
Our main usage for this module is supporting [Home Assistant](https://www.home-assistant.io) / [home-assistant](http://github.com/home-assistant/core/)
|
56
56
|
|
57
|
+

|
57
58
|
[](https://github.com/plugwise)
|
58
59
|
[](https://coderabbit.ai)
|
59
60
|
[](https://github.com/plugwise/python-plugwise/issues/291)
|
@@ -1,11 +1,12 @@
|
|
1
1
|
# Plugwise python module
|
2
2
|
|
3
|
-
This module is the backend for the [`plugwise` component](https://github.com/home-assistant/core/tree/dev/homeassistant/components/plugwise) in Home Assistant Core (which we maintain as
|
3
|
+
This module is the backend for the [`plugwise` component](https://github.com/home-assistant/core/tree/dev/homeassistant/components/plugwise) in Home Assistant Core (which we maintain as code owners).
|
4
4
|
|
5
|
-
This module supports `Smile`s
|
5
|
+
This module supports Hubs such as `Adam`, `Smile`s for Anna and P1 and `Stretch`, i.e. the networked plugwise devices. For the USB (or Stick-standalone version) please refer to upcoming [`plugwise-usb` component](https://github.com/plugwise/plugwise_usb-beta).
|
6
6
|
|
7
7
|
Our main usage for this module is supporting [Home Assistant](https://www.home-assistant.io) / [home-assistant](http://github.com/home-assistant/core/)
|
8
8
|
|
9
|
+

|
9
10
|
[](https://github.com/plugwise)
|
10
11
|
[](https://coderabbit.ai)
|
11
12
|
[](https://github.com/plugwise/python-plugwise/issues/291)
|
@@ -198,7 +198,9 @@ class Smile(SmileComm):
|
|
198
198
|
|
199
199
|
return self.smile_version
|
200
200
|
|
201
|
-
async def _smile_detect(
|
201
|
+
async def _smile_detect(
|
202
|
+
self, result: etree.Element, dsmrmain: etree.Element
|
203
|
+
) -> None:
|
202
204
|
"""Helper-function for connect().
|
203
205
|
|
204
206
|
Detect which type of Plugwise Gateway is being connected.
|
@@ -256,10 +258,8 @@ class Smile(SmileComm):
|
|
256
258
|
# For Adam, Anna, determine the system capabilities:
|
257
259
|
# Find the connected heating/cooling device (heater_central),
|
258
260
|
# e.g. heat-pump or gas-fired heater
|
259
|
-
onoff_boiler
|
260
|
-
open_therm_boiler
|
261
|
-
"./module/protocols/open_therm_boiler"
|
262
|
-
)
|
261
|
+
onoff_boiler = result.find("./module/protocols/onoff_boiler")
|
262
|
+
open_therm_boiler = result.find("./module/protocols/open_therm_boiler")
|
263
263
|
self._on_off_device = onoff_boiler is not None
|
264
264
|
self._opentherm_device = open_therm_boiler is not None
|
265
265
|
|
@@ -272,7 +272,7 @@ class Smile(SmileComm):
|
|
272
272
|
self._elga = True
|
273
273
|
|
274
274
|
async def _smile_detect_legacy(
|
275
|
-
self, result: etree, dsmrmain: etree, model: str
|
275
|
+
self, result: etree.Element, dsmrmain: etree.Element, model: str
|
276
276
|
) -> str:
|
277
277
|
"""Helper-function for _smile_detect().
|
278
278
|
|
@@ -298,11 +298,12 @@ class Smile(SmileComm):
|
|
298
298
|
self.smile_version = parse(system.find("./gateway/firmware").text)
|
299
299
|
return_model = str(system.find("./gateway/product").text)
|
300
300
|
self.smile_hostname = system.find("./gateway/hostname").text
|
301
|
-
# If wlan0 contains data it's active,
|
301
|
+
# If wlan0 contains data it's active, eth0 should be checked last as is preferred
|
302
302
|
for network in ("wlan0", "eth0"):
|
303
303
|
locator = f"./{network}/mac"
|
304
304
|
if (net_locator := system.find(locator)) is not None:
|
305
305
|
self.smile_mac_address = net_locator.text
|
306
|
+
|
306
307
|
# P1 legacy:
|
307
308
|
elif dsmrmain is not None:
|
308
309
|
status = await self._request(STATUS)
|
@@ -27,7 +27,9 @@ from defusedxml import ElementTree as etree
|
|
27
27
|
from munch import Munch
|
28
28
|
|
29
29
|
|
30
|
-
def get_zigbee_data(
|
30
|
+
def get_zigbee_data(
|
31
|
+
module: etree.Element, module_data: ModuleData, legacy: bool
|
32
|
+
) -> None:
|
31
33
|
"""Helper-function for _get_module_data()."""
|
32
34
|
if legacy:
|
33
35
|
# Stretches
|
@@ -49,7 +51,7 @@ class SmileCommon:
|
|
49
51
|
"""Init."""
|
50
52
|
self._cooling_present: bool
|
51
53
|
self._count: int
|
52
|
-
self._domain_objects: etree
|
54
|
+
self._domain_objects: etree.Element
|
53
55
|
self._heater_id: str = NONE
|
54
56
|
self._on_off_device: bool
|
55
57
|
self.gw_entities: dict[str, GwEntityData] = {}
|
@@ -63,10 +65,10 @@ class SmileCommon:
|
|
63
65
|
def _appl_heater_central_info(
|
64
66
|
self,
|
65
67
|
appl: Munch,
|
66
|
-
xml_1: etree,
|
68
|
+
xml_1: etree.Element,
|
67
69
|
legacy: bool,
|
68
|
-
xml_2: etree = None,
|
69
|
-
xml_3: etree = None,
|
70
|
+
xml_2: etree.Element = None,
|
71
|
+
xml_3: etree.Element = None,
|
70
72
|
) -> Munch:
|
71
73
|
"""Helper-function for _appliance_info_finder()."""
|
72
74
|
# Find the valid heater_central
|
@@ -101,7 +103,7 @@ class SmileCommon:
|
|
101
103
|
return appl
|
102
104
|
|
103
105
|
def _appl_thermostat_info(
|
104
|
-
self, appl: Munch, xml_1: etree, xml_2: etree = None
|
106
|
+
self, appl: Munch, xml_1: etree.Element, xml_2: etree.Element = None
|
105
107
|
) -> Munch:
|
106
108
|
"""Helper-function for _appliance_info_finder()."""
|
107
109
|
locator = "./logs/point_log[type='thermostat']/thermostat"
|
@@ -190,7 +192,7 @@ class SmileCommon:
|
|
190
192
|
return switch_groups
|
191
193
|
|
192
194
|
def _get_lock_state(
|
193
|
-
self, xml: etree, data: GwEntityData, stretch_v2: bool = False
|
195
|
+
self, xml: etree.Element, data: GwEntityData, stretch_v2: bool = False
|
194
196
|
) -> None:
|
195
197
|
"""Helper-function for _get_measurement_data().
|
196
198
|
|
@@ -209,9 +211,9 @@ class SmileCommon:
|
|
209
211
|
|
210
212
|
def _get_module_data(
|
211
213
|
self,
|
212
|
-
xml_1: etree,
|
214
|
+
xml_1: etree.Element,
|
213
215
|
locator: str,
|
214
|
-
xml_2: etree = None,
|
216
|
+
xml_2: etree.Element = None,
|
215
217
|
legacy: bool = False,
|
216
218
|
) -> ModuleData:
|
217
219
|
"""Helper-function for _energy_device_info_finder() and _appliance_info_finder().
|
@@ -228,6 +228,7 @@ class SmileData(SmileHelper):
|
|
228
228
|
for msg in item.values():
|
229
229
|
if message in msg:
|
230
230
|
data["available"] = False
|
231
|
+
break
|
231
232
|
|
232
233
|
def _get_adam_data(self, entity: GwEntityData, data: GwEntityData) -> None:
|
233
234
|
"""Helper-function for _get_entity_data().
|
@@ -329,16 +330,14 @@ class SmileData(SmileHelper):
|
|
329
330
|
|
330
331
|
Also, replace NONE by OFF when none of the schedules are active.
|
331
332
|
"""
|
332
|
-
loc_schedule_states: dict[str, str] = {}
|
333
|
-
for schedule in schedules:
|
334
|
-
loc_schedule_states[schedule] = "off"
|
335
|
-
if schedule == selected and data["climate_mode"] == "auto":
|
336
|
-
loc_schedule_states[schedule] = "on"
|
337
|
-
self._schedule_old_states[location] = loc_schedule_states
|
338
|
-
|
339
333
|
all_off = True
|
340
|
-
|
341
|
-
|
334
|
+
self._schedule_old_states[location] = {}
|
335
|
+
for schedule in schedules:
|
336
|
+
active: bool = schedule == selected and data["climate_mode"] == "auto"
|
337
|
+
self._schedule_old_states[location][schedule] = "off"
|
338
|
+
if active:
|
339
|
+
self._schedule_old_states[location][schedule] = "on"
|
342
340
|
all_off = False
|
341
|
+
|
343
342
|
if all_off:
|
344
343
|
data["select_schedule"] = OFF
|
@@ -59,7 +59,9 @@ from munch import Munch
|
|
59
59
|
from packaging import version
|
60
60
|
|
61
61
|
|
62
|
-
def search_actuator_functionalities(
|
62
|
+
def search_actuator_functionalities(
|
63
|
+
appliance: etree.Element, actuator: str
|
64
|
+
) -> etree.Element | None:
|
63
65
|
"""Helper-function for finding the relevant actuator xml-structure."""
|
64
66
|
locator = f"./actuator_functionalities/{actuator}"
|
65
67
|
if (search := appliance.find(locator)) is not None:
|
@@ -195,6 +197,7 @@ class SmileHelper(SmileCommon):
|
|
195
197
|
other_entities = self.gw_entities
|
196
198
|
priority_entities = {entity_id: priority_entity}
|
197
199
|
self.gw_entities = {**priority_entities, **other_entities}
|
200
|
+
break
|
198
201
|
|
199
202
|
def _all_locations(self) -> None:
|
200
203
|
"""Collect all locations."""
|
@@ -212,7 +215,7 @@ class SmileHelper(SmileCommon):
|
|
212
215
|
f"./location[@id='{loc.loc_id}']"
|
213
216
|
)
|
214
217
|
|
215
|
-
def _appliance_info_finder(self, appl: Munch, appliance: etree) -> Munch:
|
218
|
+
def _appliance_info_finder(self, appl: Munch, appliance: etree.Element) -> Munch:
|
216
219
|
"""Collect info for all appliances found."""
|
217
220
|
match appl.pwclass:
|
218
221
|
case "gateway":
|
@@ -252,7 +255,7 @@ class SmileHelper(SmileCommon):
|
|
252
255
|
case _: # pragma: no cover
|
253
256
|
return appl
|
254
257
|
|
255
|
-
def _appl_gateway_info(self, appl: Munch, appliance: etree) -> Munch:
|
258
|
+
def _appl_gateway_info(self, appl: Munch, appliance: etree.Element) -> Munch:
|
256
259
|
"""Helper-function for _appliance_info_finder()."""
|
257
260
|
self._gateway_id = appliance.attrib["id"]
|
258
261
|
appl.firmware = str(self.smile_version)
|
@@ -285,7 +288,7 @@ class SmileHelper(SmileCommon):
|
|
285
288
|
return appl
|
286
289
|
|
287
290
|
def _get_appl_actuator_modes(
|
288
|
-
self, appliance: etree, actuator_type: str
|
291
|
+
self, appliance: etree.Element, actuator_type: str
|
289
292
|
) -> list[str]:
|
290
293
|
"""Get allowed modes for the given actuator type."""
|
291
294
|
mode_list: list[str] = []
|
@@ -397,7 +400,7 @@ class SmileHelper(SmileCommon):
|
|
397
400
|
|
398
401
|
def _appliance_measurements(
|
399
402
|
self,
|
400
|
-
appliance: etree,
|
403
|
+
appliance: etree.Element,
|
401
404
|
data: GwEntityData,
|
402
405
|
measurements: dict[str, DATA | UOM],
|
403
406
|
) -> None:
|
@@ -429,7 +432,7 @@ class SmileHelper(SmileCommon):
|
|
429
432
|
self._count = count_data_items(self._count, data)
|
430
433
|
|
431
434
|
def _get_toggle_state(
|
432
|
-
self, xml: etree, toggle: str, name: ToggleNameType, data: GwEntityData
|
435
|
+
self, xml: etree.Element, toggle: str, name: ToggleNameType, data: GwEntityData
|
433
436
|
) -> None:
|
434
437
|
"""Helper-function for _get_measurement_data().
|
435
438
|
|
@@ -458,7 +461,7 @@ class SmileHelper(SmileCommon):
|
|
458
461
|
)
|
459
462
|
|
460
463
|
def _get_actuator_functionalities(
|
461
|
-
self, xml: etree, entity: GwEntityData, data: GwEntityData
|
464
|
+
self, xml: etree.Element, entity: GwEntityData, data: GwEntityData
|
462
465
|
) -> None:
|
463
466
|
"""Get and process the actuator_functionalities details for an entity.
|
464
467
|
|
@@ -520,7 +523,7 @@ class SmileHelper(SmileCommon):
|
|
520
523
|
data[act_item] = temp_dict
|
521
524
|
|
522
525
|
def _get_actuator_mode(
|
523
|
-
self, appliance: etree, entity_id: str, key: str
|
526
|
+
self, appliance: etree.Element, entity_id: str, key: str
|
524
527
|
) -> str | None:
|
525
528
|
"""Helper-function for _get_regulation_mode and _get_gateway_mode.
|
526
529
|
|
@@ -535,7 +538,7 @@ class SmileHelper(SmileCommon):
|
|
535
538
|
return None
|
536
539
|
|
537
540
|
def _get_regulation_mode(
|
538
|
-
self, appliance: etree, entity_id: str, data: GwEntityData
|
541
|
+
self, appliance: etree.Element, entity_id: str, data: GwEntityData
|
539
542
|
) -> None:
|
540
543
|
"""Helper-function for _get_measurement_data().
|
541
544
|
|
@@ -551,7 +554,7 @@ class SmileHelper(SmileCommon):
|
|
551
554
|
self._cooling_enabled = mode == "cooling"
|
552
555
|
|
553
556
|
def _get_gateway_mode(
|
554
|
-
self, appliance: etree, entity_id: str, data: GwEntityData
|
557
|
+
self, appliance: etree.Element, entity_id: str, data: GwEntityData
|
555
558
|
) -> None:
|
556
559
|
"""Helper-function for _get_measurement_data().
|
557
560
|
|
@@ -837,9 +840,7 @@ class SmileHelper(SmileCommon):
|
|
837
840
|
return presets # pragma: no cover
|
838
841
|
|
839
842
|
for rule_id in rule_ids:
|
840
|
-
directives
|
841
|
-
f'rule[@id="{rule_id}"]/directives'
|
842
|
-
)
|
843
|
+
directives = self._domain_objects.find(f'rule[@id="{rule_id}"]/directives')
|
843
844
|
for directive in directives:
|
844
845
|
preset = directive.find("then").attrib
|
845
846
|
presets[directive.attrib["preset"]] = [
|
@@ -23,6 +23,7 @@ from plugwise.constants import (
|
|
23
23
|
NONE,
|
24
24
|
OFF,
|
25
25
|
P1_LEGACY_MEASUREMENTS,
|
26
|
+
PRIORITY_DEVICE_CLASSES,
|
26
27
|
TEMP_CELSIUS,
|
27
28
|
THERMOSTAT_CLASSES,
|
28
29
|
UOM,
|
@@ -49,7 +50,7 @@ from munch import Munch
|
|
49
50
|
from packaging.version import Version
|
50
51
|
|
51
52
|
|
52
|
-
def etree_to_dict(element: etree) -> dict[str, str]:
|
53
|
+
def etree_to_dict(element: etree.Element) -> dict[str, str]:
|
53
54
|
"""Helper-function translating xml Element to dict."""
|
54
55
|
node: dict[str, str] = {}
|
55
56
|
if element is not None:
|
@@ -63,11 +64,11 @@ class SmileLegacyHelper(SmileCommon):
|
|
63
64
|
|
64
65
|
def __init__(self) -> None:
|
65
66
|
"""Set the constructor for this class."""
|
66
|
-
self._appliances: etree
|
67
|
+
self._appliances: etree.Element
|
67
68
|
self._is_thermostat: bool
|
68
69
|
self._loc_data: dict[str, ThermoLoc]
|
69
|
-
self._locations: etree
|
70
|
-
self._modules: etree
|
70
|
+
self._locations: etree.Element
|
71
|
+
self._modules: etree.Element
|
71
72
|
self._stretch_v2: bool
|
72
73
|
self.gw_entities: dict[str, GwEntityData] = {}
|
73
74
|
self.smile_mac_address: str | None
|
@@ -130,7 +131,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
130
131
|
self._create_gw_entities(appl)
|
131
132
|
|
132
133
|
# Place the gateway and optional heater_central devices as 1st and 2nd
|
133
|
-
for dev_class in
|
134
|
+
for dev_class in PRIORITY_DEVICE_CLASSES:
|
134
135
|
for entity_id, entity in dict(self.gw_entities).items():
|
135
136
|
if entity["dev_class"] == dev_class:
|
136
137
|
tmp_entity = entity
|
@@ -138,6 +139,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
138
139
|
cleared_dict = self.gw_entities
|
139
140
|
add_to_front = {entity_id: tmp_entity}
|
140
141
|
self.gw_entities = {**add_to_front, **cleared_dict}
|
142
|
+
break
|
141
143
|
|
142
144
|
def _all_locations(self) -> None:
|
143
145
|
"""Collect all locations."""
|
@@ -316,7 +318,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
316
318
|
|
317
319
|
def _appliance_measurements(
|
318
320
|
self,
|
319
|
-
appliance: etree,
|
321
|
+
appliance: etree.Element,
|
320
322
|
data: GwEntityData,
|
321
323
|
measurements: dict[str, DATA | UOM],
|
322
324
|
) -> None:
|
@@ -345,7 +347,7 @@ class SmileLegacyHelper(SmileCommon):
|
|
345
347
|
self._count = count_data_items(self._count, data)
|
346
348
|
|
347
349
|
def _get_actuator_functionalities(
|
348
|
-
self, xml: etree, entity: GwEntityData, data: GwEntityData
|
350
|
+
self, xml: etree.Element, entity: GwEntityData, data: GwEntityData
|
349
351
|
) -> None:
|
350
352
|
"""Helper-function for _get_measurement_data()."""
|
351
353
|
for item in ACTIVE_ACTUATORS:
|
@@ -171,9 +171,8 @@ class SmileLegacyAPI(SmileLegacyData):
|
|
171
171
|
raise PlugwiseError("Plugwise: invalid preset.")
|
172
172
|
|
173
173
|
locator = f'rule/directives/when/then[@icon="{preset}"].../.../...'
|
174
|
-
|
175
|
-
data = f
|
176
|
-
|
174
|
+
rule_id = self._domain_objects.find(locator).attrib["id"]
|
175
|
+
data = f"<rules><rule id='{rule_id}'><active>true</active></rule></rules>"
|
177
176
|
await self.call_request(RULES, method="put", data=data)
|
178
177
|
|
179
178
|
async def set_regulation_mode(self, mode: str) -> None:
|
@@ -205,6 +204,7 @@ class SmileLegacyAPI(SmileLegacyData):
|
|
205
204
|
for rule in self._domain_objects.findall("rule"):
|
206
205
|
if rule.find("name").text == name:
|
207
206
|
schedule_rule_id = rule.attrib["id"]
|
207
|
+
break
|
208
208
|
|
209
209
|
if schedule_rule_id is None:
|
210
210
|
raise PlugwiseError(
|
@@ -216,16 +216,18 @@ class SmileLegacyAPI(SmileLegacyData):
|
|
216
216
|
new_state = "true"
|
217
217
|
|
218
218
|
locator = f'.//*[@id="{schedule_rule_id}"]/template'
|
219
|
-
|
220
|
-
template_id = rule.attrib["id"]
|
219
|
+
template_id = self._domain_objects.find(locator).attrib["id"]
|
221
220
|
|
222
|
-
uri = f"{RULES};id={schedule_rule_id}"
|
223
221
|
data = (
|
224
|
-
"<rules
|
225
|
-
f
|
226
|
-
f
|
222
|
+
"<rules>"
|
223
|
+
f"<rule id='{schedule_rule_id}'>"
|
224
|
+
f"<name><![CDATA[{name}]]></name>"
|
225
|
+
f"<template id='{template_id}' />"
|
226
|
+
f"<active>{new_state}</active>"
|
227
|
+
"</rule>"
|
228
|
+
"</rules>"
|
227
229
|
)
|
228
|
-
|
230
|
+
uri = f"{RULES};id={schedule_rule_id}"
|
229
231
|
await self.call_request(uri, method="put", data=data)
|
230
232
|
|
231
233
|
async def set_switch_state(
|
@@ -250,19 +252,20 @@ class SmileLegacyAPI(SmileLegacyData):
|
|
250
252
|
appliance = self._appliances.find(f'appliance[@id="{appl_id}"]')
|
251
253
|
appl_name = appliance.find("name").text
|
252
254
|
appl_type = appliance.find("type").text
|
253
|
-
data =
|
254
|
-
<appliances>
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
</appliances>
|
255
|
+
data = (
|
256
|
+
"<appliances>"
|
257
|
+
f"<appliance id='{appl_id}'>"
|
258
|
+
f"<name><![CDATA[{appl_name}]]></name>"
|
259
|
+
f"<description><![CDATA[]]></description>"
|
260
|
+
f"<type><![CDATA[{appl_type}]]></type>"
|
261
|
+
f"<{switch.actuator}>"
|
262
|
+
f"<{switch.func_type}>"
|
263
|
+
f"<lock>{state}</lock>"
|
264
|
+
f"</{switch.func_type}>"
|
265
|
+
f"</{switch.actuator}>"
|
266
|
+
"</appliance>"
|
267
|
+
"</appliances>"
|
268
|
+
)
|
266
269
|
await self.call_request(APPLIANCES, method="post", data=data)
|
267
270
|
return
|
268
271
|
|
@@ -308,12 +311,12 @@ class SmileLegacyAPI(SmileLegacyData):
|
|
308
311
|
) # pragma: no cover"
|
309
312
|
|
310
313
|
temperature = str(setpoint)
|
311
|
-
uri = self._thermostat_uri()
|
312
314
|
data = (
|
313
|
-
"<thermostat_functionality
|
314
|
-
f"{temperature}</setpoint
|
315
|
+
"<thermostat_functionality>"
|
316
|
+
f"<setpoint>{temperature}</setpoint>"
|
317
|
+
"</thermostat_functionality>"
|
315
318
|
)
|
316
|
-
|
319
|
+
uri = self._thermostat_uri()
|
317
320
|
await self.call_request(uri, method="put", data=data)
|
318
321
|
|
319
322
|
async def call_request(self, uri: str, **kwargs: Any) -> None:
|
@@ -174,8 +174,12 @@ class SmileAPI(SmileData):
|
|
174
174
|
if thermostat_id is None:
|
175
175
|
raise PlugwiseError(f"Plugwise: cannot change setpoint, {key} not found.")
|
176
176
|
|
177
|
+
data = (
|
178
|
+
"<thermostat_functionality>"
|
179
|
+
f"<setpoint>{temp}</setpoint>"
|
180
|
+
"</thermostat_functionality>"
|
181
|
+
)
|
177
182
|
uri = f"{APPLIANCES};id={self._heater_id}/thermostat;id={thermostat_id}"
|
178
|
-
data = f"<thermostat_functionality><setpoint>{temp}</setpoint></thermostat_functionality>"
|
179
183
|
await self.call_request(uri, method="put", data=data)
|
180
184
|
|
181
185
|
async def set_offset(self, dev_id: str, offset: float) -> None:
|
@@ -186,9 +190,8 @@ class SmileAPI(SmileData):
|
|
186
190
|
)
|
187
191
|
|
188
192
|
value = str(offset)
|
189
|
-
uri = f"{APPLIANCES};id={dev_id}/offset;type=temperature_offset"
|
190
193
|
data = f"<offset_functionality><offset>{value}</offset></offset_functionality>"
|
191
|
-
|
194
|
+
uri = f"{APPLIANCES};id={dev_id}/offset;type=temperature_offset"
|
192
195
|
await self.call_request(uri, method="put", data=data)
|
193
196
|
|
194
197
|
async def set_preset(self, loc_id: str, preset: str) -> None:
|
@@ -201,14 +204,16 @@ class SmileAPI(SmileData):
|
|
201
204
|
current_location = self._domain_objects.find(f'location[@id="{loc_id}"]')
|
202
205
|
location_name = current_location.find("name").text
|
203
206
|
location_type = current_location.find("type").text
|
204
|
-
|
205
|
-
uri = f"{LOCATIONS};id={loc_id}"
|
206
207
|
data = (
|
207
|
-
"<locations
|
208
|
-
f' id="{loc_id}"
|
209
|
-
f"
|
208
|
+
"<locations>"
|
209
|
+
f'<location id="{loc_id}">'
|
210
|
+
f"<name>{location_name}</name>"
|
211
|
+
f"<type>{location_type}</type>"
|
212
|
+
f"<preset>{preset}</preset>"
|
213
|
+
"</location>"
|
214
|
+
"</locations>"
|
210
215
|
)
|
211
|
-
|
216
|
+
uri = f"{LOCATIONS};id={loc_id}"
|
212
217
|
await self.call_request(uri, method="put", data=data)
|
213
218
|
|
214
219
|
async def set_select(
|
@@ -231,9 +236,12 @@ class SmileAPI(SmileData):
|
|
231
236
|
if mode not in self._dhw_allowed_modes:
|
232
237
|
raise PlugwiseError("Plugwise: invalid dhw mode.")
|
233
238
|
|
239
|
+
data = (
|
240
|
+
"<domestic_hot_water_mode_control_functionality>"
|
241
|
+
f"<mode>{mode}</mode>"
|
242
|
+
"</domestic_hot_water_mode_control_functionality>"
|
243
|
+
)
|
234
244
|
uri = f"{APPLIANCES};type=heater_central/domestic_hot_water_mode_control"
|
235
|
-
data = f"<domestic_hot_water_mode_control_functionality><mode>{mode}</mode></domestic_hot_water_mode_control_functionality>"
|
236
|
-
|
237
245
|
await self.call_request(uri, method="put", data=data)
|
238
246
|
|
239
247
|
async def set_gateway_mode(self, mode: str) -> None:
|
@@ -259,9 +267,13 @@ class SmileAPI(SmileData):
|
|
259
267
|
vacation_time = time_2 + "T23:00:00.000Z"
|
260
268
|
valid = f"<valid_from>{vacation_time}</valid_from><valid_to>{end_time}</valid_to>"
|
261
269
|
|
270
|
+
data = (
|
271
|
+
"<gateway_mode_control_functionality>"
|
272
|
+
f"<mode>{mode}</mode>"
|
273
|
+
f"{valid}"
|
274
|
+
"</gateway_mode_control_functionality>"
|
275
|
+
)
|
262
276
|
uri = f"{APPLIANCES};id={self._smile_props['gateway_id']}/gateway_mode_control"
|
263
|
-
data = f"<gateway_mode_control_functionality><mode>{mode}</mode>{valid}</gateway_mode_control_functionality>"
|
264
|
-
|
265
277
|
await self.call_request(uri, method="put", data=data)
|
266
278
|
|
267
279
|
async def set_regulation_mode(self, mode: str) -> None:
|
@@ -269,12 +281,17 @@ class SmileAPI(SmileData):
|
|
269
281
|
if mode not in self._reg_allowed_modes:
|
270
282
|
raise PlugwiseError("Plugwise: invalid regulation mode.")
|
271
283
|
|
272
|
-
uri = f"{APPLIANCES};type=gateway/regulation_mode_control"
|
273
284
|
duration = ""
|
274
285
|
if "bleeding" in mode:
|
275
286
|
duration = "<duration>300</duration>"
|
276
|
-
data = f"<regulation_mode_control_functionality>{duration}<mode>{mode}</mode></regulation_mode_control_functionality>"
|
277
287
|
|
288
|
+
data = (
|
289
|
+
"<regulation_mode_control_functionality>"
|
290
|
+
f"{duration}"
|
291
|
+
f"<mode>{mode}</mode>"
|
292
|
+
"</regulation_mode_control_functionality>"
|
293
|
+
)
|
294
|
+
uri = f"{APPLIANCES};type=gateway/regulation_mode_control"
|
278
295
|
await self.call_request(uri, method="put", data=data)
|
279
296
|
|
280
297
|
async def set_schedule_state(
|
@@ -323,18 +340,22 @@ class SmileAPI(SmileData):
|
|
323
340
|
template = f'<template id="{template_id}" />'
|
324
341
|
|
325
342
|
contexts = self.determine_contexts(loc_id, name, new_state, schedule_rule_id)
|
326
|
-
uri = f"{RULES};id={schedule_rule_id}"
|
327
343
|
data = (
|
328
|
-
|
329
|
-
f"{
|
344
|
+
"<rules>"
|
345
|
+
f"<rule id='{schedule_rule_id}'>"
|
346
|
+
f"<name><![CDATA[{name}]]></name>"
|
347
|
+
f"{template}"
|
348
|
+
f"{contexts}"
|
349
|
+
"</rule>"
|
350
|
+
"</rules>"
|
330
351
|
)
|
331
|
-
|
352
|
+
uri = f"{RULES};id={schedule_rule_id}"
|
332
353
|
await self.call_request(uri, method="put", data=data)
|
333
354
|
self._schedule_old_states[loc_id][name] = new_state
|
334
355
|
|
335
356
|
def determine_contexts(
|
336
357
|
self, loc_id: str, name: str, state: str, sched_id: str
|
337
|
-
) ->
|
358
|
+
) -> str:
|
338
359
|
"""Helper-function for set_schedule_state()."""
|
339
360
|
locator = f'.//*[@id="{sched_id}"]/contexts'
|
340
361
|
contexts = self._domain_objects.find(locator)
|
@@ -349,7 +370,7 @@ class SmileAPI(SmileData):
|
|
349
370
|
if state == "on":
|
350
371
|
contexts.append(subject)
|
351
372
|
|
352
|
-
return etree.tostring(contexts, encoding="unicode").rstrip()
|
373
|
+
return str(etree.tostring(contexts, encoding="unicode").rstrip())
|
353
374
|
|
354
375
|
async def set_switch_state(
|
355
376
|
self, appl_id: str, members: list[str] | None, model: str, state: str
|
@@ -378,18 +399,22 @@ class SmileAPI(SmileData):
|
|
378
399
|
return await self._set_groupswitch_member_state(members, state, switch)
|
379
400
|
|
380
401
|
locator = f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}'
|
381
|
-
found
|
402
|
+
found = self._domain_objects.findall(locator)
|
382
403
|
for item in found:
|
404
|
+
# multiple types of e.g. toggle_functionality present
|
383
405
|
if (sw_type := item.find("type")) is not None:
|
384
406
|
if sw_type.text == switch.act_type:
|
385
407
|
switch_id = item.attrib["id"]
|
386
|
-
|
408
|
+
break
|
409
|
+
else: # actuators with a single item like relay_functionality
|
387
410
|
switch_id = item.attrib["id"]
|
388
|
-
break
|
389
411
|
|
412
|
+
data = (
|
413
|
+
f"<{switch.func_type}>"
|
414
|
+
f"<{switch.func}>{state}</{switch.func}>"
|
415
|
+
f"</{switch.func_type}>"
|
416
|
+
)
|
390
417
|
uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}"
|
391
|
-
data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
|
392
|
-
|
393
418
|
if model == "relay":
|
394
419
|
locator = (
|
395
420
|
f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}/lock'
|
@@ -411,8 +436,11 @@ class SmileAPI(SmileData):
|
|
411
436
|
locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}'
|
412
437
|
switch_id = self._domain_objects.find(locator).attrib["id"]
|
413
438
|
uri = f"{APPLIANCES};id={member}/{switch.device};id={switch_id}"
|
414
|
-
data =
|
415
|
-
|
439
|
+
data = (
|
440
|
+
f"<{switch.func_type}>"
|
441
|
+
f"<{switch.func}>{state}</{switch.func}>"
|
442
|
+
f"</{switch.func_type}>"
|
443
|
+
)
|
416
444
|
await self.call_request(uri, method="put", data=data)
|
417
445
|
|
418
446
|
async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
|
@@ -448,12 +476,12 @@ class SmileAPI(SmileData):
|
|
448
476
|
) # pragma: no cover"
|
449
477
|
|
450
478
|
temperature = str(setpoint)
|
451
|
-
uri = self._thermostat_uri(loc_id)
|
452
479
|
data = (
|
453
|
-
"<thermostat_functionality
|
454
|
-
f"{temperature}</setpoint
|
480
|
+
"<thermostat_functionality>"
|
481
|
+
f"<setpoint>{temperature}</setpoint>"
|
482
|
+
"</thermostat_functionality>"
|
455
483
|
)
|
456
|
-
|
484
|
+
uri = self._thermostat_uri(loc_id)
|
457
485
|
await self.call_request(uri, method="put", data=data)
|
458
486
|
|
459
487
|
async def call_request(self, uri: str, **kwargs: Any) -> None:
|
@@ -51,7 +51,7 @@ class SmileComm:
|
|
51
51
|
retry: int = 3,
|
52
52
|
method: str = "get",
|
53
53
|
data: str | None = None,
|
54
|
-
) -> etree:
|
54
|
+
) -> etree.Element:
|
55
55
|
"""Get/put/delete data from a give URL."""
|
56
56
|
resp: ClientResponse
|
57
57
|
url = f"{self._endpoint}{command}"
|
@@ -107,7 +107,9 @@ class SmileComm:
|
|
107
107
|
|
108
108
|
return await self._request_validate(resp, method)
|
109
109
|
|
110
|
-
async def _request_validate(
|
110
|
+
async def _request_validate(
|
111
|
+
self, resp: ClientResponse, method: str
|
112
|
+
) -> etree.Element:
|
111
113
|
"""Helper-function for _request(): validate the returned data."""
|
112
114
|
match resp.status:
|
113
115
|
case 200:
|
@@ -74,7 +74,7 @@ def in_alternative_location(loc: Munch, legacy: bool) -> bool:
|
|
74
74
|
return present
|
75
75
|
|
76
76
|
|
77
|
-
def check_heater_central(xml: etree) -> str:
|
77
|
+
def check_heater_central(xml: etree.Element) -> str:
|
78
78
|
"""Find the valid heater_central, helper-function for _appliance_info_finder().
|
79
79
|
|
80
80
|
Solution for Core Issue #104433,
|
@@ -143,7 +143,7 @@ def collect_power_values(
|
|
143
143
|
def common_match_cases(
|
144
144
|
measurement: str,
|
145
145
|
attrs: DATA | UOM,
|
146
|
-
location: etree,
|
146
|
+
location: etree.Element,
|
147
147
|
data: GwEntityData,
|
148
148
|
) -> None:
|
149
149
|
"""Helper-function for common match-case execution."""
|
@@ -213,7 +213,7 @@ def format_measure(measure: str, unit: str) -> float | int:
|
|
213
213
|
return result
|
214
214
|
|
215
215
|
|
216
|
-
def get_vendor_name(module: etree, model_data: ModuleData) -> ModuleData:
|
216
|
+
def get_vendor_name(module: etree.Element, model_data: ModuleData) -> ModuleData:
|
217
217
|
"""Helper-function for _get_model_data()."""
|
218
218
|
if (vendor_name := module.find("vendor_name").text) is not None:
|
219
219
|
model_data["vendor_name"] = vendor_name
|
@@ -302,12 +302,12 @@ def remove_empty_platform_dicts(data: GwEntityData) -> None:
|
|
302
302
|
data.pop("switches")
|
303
303
|
|
304
304
|
|
305
|
-
def return_valid(value: etree | None, default: etree) -> etree:
|
305
|
+
def return_valid(value: etree.Element | None, default: etree.Element) -> etree.Element:
|
306
306
|
"""Return default when value is None."""
|
307
307
|
return value if value is not None else default
|
308
308
|
|
309
309
|
|
310
|
-
def skip_obsolete_measurements(xml: etree, measurement: str) -> bool:
|
310
|
+
def skip_obsolete_measurements(xml: etree.Element, measurement: str) -> bool:
|
311
311
|
"""Skipping known obsolete measurements."""
|
312
312
|
locator = f".//logs/point_log[type='{measurement}']/updated_date"
|
313
313
|
if (
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: plugwise
|
3
|
-
Version: 1.7.
|
3
|
+
Version: 1.7.3a1
|
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
|
@@ -48,12 +48,13 @@ Requires-Dist: python-dateutil
|
|
48
48
|
|
49
49
|
# Plugwise python module
|
50
50
|
|
51
|
-
This module is the backend for the [`plugwise` component](https://github.com/home-assistant/core/tree/dev/homeassistant/components/plugwise) in Home Assistant Core (which we maintain as
|
51
|
+
This module is the backend for the [`plugwise` component](https://github.com/home-assistant/core/tree/dev/homeassistant/components/plugwise) in Home Assistant Core (which we maintain as code owners).
|
52
52
|
|
53
|
-
This module supports `Smile`s
|
53
|
+
This module supports Hubs such as `Adam`, `Smile`s for Anna and P1 and `Stretch`, i.e. the networked plugwise devices. For the USB (or Stick-standalone version) please refer to upcoming [`plugwise-usb` component](https://github.com/plugwise/plugwise_usb-beta).
|
54
54
|
|
55
55
|
Our main usage for this module is supporting [Home Assistant](https://www.home-assistant.io) / [home-assistant](http://github.com/home-assistant/core/)
|
56
56
|
|
57
|
+

|
57
58
|
[](https://github.com/plugwise)
|
58
59
|
[](https://coderabbit.ai)
|
59
60
|
[](https://github.com/plugwise/python-plugwise/issues/291)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|