plugwise 1.5.2__tar.gz → 1.6.1__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.5.2 → plugwise-1.6.1}/PKG-INFO +3 -2
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/__init__.py +10 -10
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/common.py +60 -50
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/constants.py +21 -9
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/data.py +95 -67
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/helper.py +193 -137
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/legacy/data.py +23 -23
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/legacy/helper.py +54 -55
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/legacy/smile.py +18 -16
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/smile.py +24 -21
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/util.py +5 -5
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise.egg-info/PKG-INFO +3 -2
- {plugwise-1.5.2 → plugwise-1.6.1}/pyproject.toml +8 -6
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_adam.py +10 -8
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_anna.py +17 -21
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_init.py +70 -61
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_legacy_anna.py +2 -2
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_legacy_p1.py +2 -3
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_legacy_stretch.py +3 -3
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_p1.py +2 -26
- {plugwise-1.5.2 → plugwise-1.6.1}/LICENSE +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/README.md +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/exceptions.py +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise/py.typed +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise.egg-info/SOURCES.txt +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise.egg-info/dependency_links.txt +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise.egg-info/requires.txt +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/plugwise.egg-info/top_level.txt +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/setup.cfg +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/setup.py +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_generic.py +0 -0
- {plugwise-1.5.2 → plugwise-1.6.1}/tests/test_legacy_generic.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: plugwise
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.6.1
|
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
|
@@ -36,8 +36,9 @@ Classifier: Intended Audience :: Developers
|
|
36
36
|
Classifier: License :: OSI Approved :: MIT License
|
37
37
|
Classifier: Operating System :: OS Independent
|
38
38
|
Classifier: Programming Language :: Python :: 3.12
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
39
40
|
Classifier: Topic :: Home Automation
|
40
|
-
Requires-Python: >=3.
|
41
|
+
Requires-Python: >=3.12.0
|
41
42
|
Description-Content-Type: text/markdown
|
42
43
|
License-File: LICENSE
|
43
44
|
Requires-Dist: aiohttp
|
@@ -69,6 +69,7 @@ class Smile(SmileComm):
|
|
69
69
|
self._elga = False
|
70
70
|
self._is_thermostat = False
|
71
71
|
self._last_active: dict[str, str | None] = {}
|
72
|
+
self._loc_data: dict[str, ThermoLoc] = {}
|
72
73
|
self._on_off_device = False
|
73
74
|
self._opentherm_device = False
|
74
75
|
self._schedule_old_states: dict[str, dict[str, str]] = {}
|
@@ -76,7 +77,6 @@ class Smile(SmileComm):
|
|
76
77
|
self._stretch_v2 = False
|
77
78
|
self._target_smile: str = NONE
|
78
79
|
self.gateway_id: str = NONE
|
79
|
-
self.loc_data: dict[str, ThermoLoc] = {}
|
80
80
|
self.smile_fw_version: Version | None = None
|
81
81
|
self.smile_hostname: str = NONE
|
82
82
|
self.smile_hw_version: str | None = None
|
@@ -134,11 +134,11 @@ class Smile(SmileComm):
|
|
134
134
|
self._elga,
|
135
135
|
self._is_thermostat,
|
136
136
|
self._last_active,
|
137
|
+
self._loc_data,
|
137
138
|
self._on_off_device,
|
138
139
|
self._opentherm_device,
|
139
140
|
self._schedule_old_states,
|
140
141
|
self.gateway_id,
|
141
|
-
self.loc_data,
|
142
142
|
self.smile_fw_version,
|
143
143
|
self.smile_hostname,
|
144
144
|
self.smile_hw_version,
|
@@ -155,11 +155,11 @@ class Smile(SmileComm):
|
|
155
155
|
self._request,
|
156
156
|
self._websession,
|
157
157
|
self._is_thermostat,
|
158
|
+
self._loc_data,
|
158
159
|
self._on_off_device,
|
159
160
|
self._opentherm_device,
|
160
161
|
self._stretch_v2,
|
161
162
|
self._target_smile,
|
162
|
-
self.loc_data,
|
163
163
|
self.smile_fw_version,
|
164
164
|
self.smile_hostname,
|
165
165
|
self.smile_hw_version,
|
@@ -173,7 +173,7 @@ class Smile(SmileComm):
|
|
173
173
|
)
|
174
174
|
|
175
175
|
# Update all endpoints on first connect
|
176
|
-
await self._smile_api.
|
176
|
+
await self._smile_api.full_xml_update()
|
177
177
|
|
178
178
|
return self.smile_version
|
179
179
|
|
@@ -298,17 +298,17 @@ class Smile(SmileComm):
|
|
298
298
|
self.smile_legacy = True
|
299
299
|
return return_model
|
300
300
|
|
301
|
-
async def
|
301
|
+
async def full_xml_update(self) -> None:
|
302
302
|
"""Helper-function used for testing."""
|
303
|
-
await self._smile_api.
|
303
|
+
await self._smile_api.full_xml_update()
|
304
304
|
|
305
|
-
def
|
305
|
+
def get_all_gateway_entities(self) -> None:
|
306
306
|
"""Helper-function used for testing."""
|
307
|
-
self._smile_api.
|
307
|
+
self._smile_api.get_all_gateway_entities()
|
308
308
|
|
309
309
|
async def async_update(self) -> PlugwiseData:
|
310
|
-
"""
|
311
|
-
data = PlugwiseData({}, {})
|
310
|
+
"""Update the various entities and their states."""
|
311
|
+
data = PlugwiseData(devices={}, gateway={})
|
312
312
|
try:
|
313
313
|
data = await self._smile_api.async_update()
|
314
314
|
self.gateway_id = data.gateway["gateway_id"]
|
@@ -11,8 +11,8 @@ from plugwise.constants import (
|
|
11
11
|
SPECIAL_PLUG_TYPES,
|
12
12
|
SWITCH_GROUP_TYPES,
|
13
13
|
ApplianceType,
|
14
|
-
|
15
|
-
|
14
|
+
GwEntityData,
|
15
|
+
ModuleData,
|
16
16
|
SensorType,
|
17
17
|
)
|
18
18
|
from plugwise.util import (
|
@@ -40,7 +40,7 @@ class SmileCommon:
|
|
40
40
|
self._heater_id: str
|
41
41
|
self._on_off_device: bool
|
42
42
|
self._opentherm_device: bool
|
43
|
-
self.
|
43
|
+
self.gw_entities: dict[str, GwEntityData]
|
44
44
|
self.smile_name: str
|
45
45
|
self.smile_type: str
|
46
46
|
|
@@ -108,7 +108,7 @@ class SmileCommon:
|
|
108
108
|
|
109
109
|
return appl
|
110
110
|
|
111
|
-
def _collect_power_values(self, data:
|
111
|
+
def _collect_power_values(self, data: GwEntityData, loc: Munch, tariff: str, legacy: bool = False) -> None:
|
112
112
|
"""Something."""
|
113
113
|
for loc.peak_select in ("nl_peak", "nl_offpeak"):
|
114
114
|
loc.locator = (
|
@@ -131,6 +131,19 @@ class SmileCommon:
|
|
131
131
|
key = cast(SensorType, loc.key_string)
|
132
132
|
data["sensors"][key] = loc.f_val
|
133
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
|
+
|
134
147
|
def _power_data_peak_value(self, loc: Munch, legacy: bool) -> Munch:
|
135
148
|
"""Helper-function for _power_data_from_location() and _power_data_from_modules()."""
|
136
149
|
loc.found = True
|
@@ -160,8 +173,8 @@ class SmileCommon:
|
|
160
173
|
measurement: str,
|
161
174
|
net_string: SensorType,
|
162
175
|
f_val: float | int,
|
163
|
-
|
164
|
-
) ->
|
176
|
+
data: GwEntityData,
|
177
|
+
) -> GwEntityData:
|
165
178
|
"""Calculate differential energy."""
|
166
179
|
if (
|
167
180
|
"electricity" in measurement
|
@@ -171,10 +184,10 @@ class SmileCommon:
|
|
171
184
|
diff = 1
|
172
185
|
if "produced" in measurement:
|
173
186
|
diff = -1
|
174
|
-
if net_string not in
|
187
|
+
if net_string not in data["sensors"]:
|
175
188
|
tmp_val: float | int = 0
|
176
189
|
else:
|
177
|
-
tmp_val =
|
190
|
+
tmp_val = data["sensors"][net_string]
|
178
191
|
|
179
192
|
if isinstance(f_val, int):
|
180
193
|
tmp_val += f_val * diff
|
@@ -182,13 +195,13 @@ class SmileCommon:
|
|
182
195
|
tmp_val += float(f_val * diff)
|
183
196
|
tmp_val = float(f"{round(tmp_val, 3):.3f}")
|
184
197
|
|
185
|
-
|
198
|
+
data["sensors"][net_string] = tmp_val
|
186
199
|
|
187
|
-
return
|
200
|
+
return data
|
188
201
|
|
189
|
-
def
|
190
|
-
"""Helper-function for creating/updating
|
191
|
-
self.
|
202
|
+
def _create_gw_entities(self, appl: Munch) -> None:
|
203
|
+
"""Helper-function for creating/updating gw_entities."""
|
204
|
+
self.gw_entities[appl.entity_id] = {"dev_class": appl.pwclass}
|
192
205
|
self._count += 1
|
193
206
|
for key, value in {
|
194
207
|
"available": appl.available,
|
@@ -204,30 +217,30 @@ class SmileCommon:
|
|
204
217
|
}.items():
|
205
218
|
if value is not None or key == "location":
|
206
219
|
appl_key = cast(ApplianceType, key)
|
207
|
-
self.
|
220
|
+
self.gw_entities[appl.entity_id][appl_key] = value
|
208
221
|
self._count += 1
|
209
222
|
|
210
|
-
def
|
211
|
-
self,
|
223
|
+
def _entity_switching_group(
|
224
|
+
self, entity: GwEntityData, data: GwEntityData
|
212
225
|
) -> None:
|
213
|
-
"""Helper-function for
|
226
|
+
"""Helper-function for _get_device_zone_data().
|
214
227
|
|
215
228
|
Determine switching group device data.
|
216
229
|
"""
|
217
|
-
if
|
230
|
+
if entity["dev_class"] in SWITCH_GROUP_TYPES:
|
218
231
|
counter = 0
|
219
|
-
for member in
|
220
|
-
if self.
|
232
|
+
for member in entity["members"]:
|
233
|
+
if self.gw_entities[member]["switches"].get("relay"):
|
221
234
|
counter += 1
|
222
235
|
data["switches"]["relay"] = counter != 0
|
223
236
|
self._count += 1
|
224
237
|
|
225
|
-
def _get_group_switches(self) -> dict[str,
|
226
|
-
"""Helper-function for smile.py:
|
238
|
+
def _get_group_switches(self) -> dict[str, GwEntityData]:
|
239
|
+
"""Helper-function for smile.py: get_all_gateway_entities().
|
227
240
|
|
228
241
|
Collect switching- or pump-group info.
|
229
242
|
"""
|
230
|
-
switch_groups: dict[str,
|
243
|
+
switch_groups: dict[str, GwEntityData] = {}
|
231
244
|
# P1 and Anna don't have switchgroups
|
232
245
|
if self.smile_type == "power" or self.smile(ANNA):
|
233
246
|
return switch_groups
|
@@ -240,25 +253,22 @@ class SmileCommon:
|
|
240
253
|
group_appliances = group.findall("appliances/appliance")
|
241
254
|
for item in group_appliances:
|
242
255
|
# Check if members are not orphaned - stretch
|
243
|
-
if item.attrib["id"] in self.
|
256
|
+
if item.attrib["id"] in self.gw_entities:
|
244
257
|
members.append(item.attrib["id"])
|
245
258
|
|
246
259
|
if group_type in SWITCH_GROUP_TYPES and members:
|
247
|
-
switch_groups
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
},
|
255
|
-
},
|
256
|
-
)
|
260
|
+
switch_groups[group_id] = {
|
261
|
+
"dev_class": group_type,
|
262
|
+
"model": "Switchgroup",
|
263
|
+
"name": group_name,
|
264
|
+
"members": members,
|
265
|
+
"vendor": "Plugwise",
|
266
|
+
}
|
257
267
|
self._count += 4
|
258
268
|
|
259
269
|
return switch_groups
|
260
270
|
|
261
|
-
def _get_lock_state(self, xml: etree, data:
|
271
|
+
def _get_lock_state(self, xml: etree, data: GwEntityData, stretch_v2: bool = False) -> None:
|
262
272
|
"""Helper-function for _get_measurement_data().
|
263
273
|
|
264
274
|
Adam & Stretches: obtain the relay-switch lock state.
|
@@ -280,12 +290,12 @@ class SmileCommon:
|
|
280
290
|
locator: str,
|
281
291
|
xml_2: etree = None,
|
282
292
|
legacy: bool = False,
|
283
|
-
) ->
|
293
|
+
) -> ModuleData:
|
284
294
|
"""Helper-function for _energy_device_info_finder() and _appliance_info_finder().
|
285
295
|
|
286
296
|
Collect requested info from MODULES.
|
287
297
|
"""
|
288
|
-
|
298
|
+
module_data: ModuleData = {
|
289
299
|
"contents": False,
|
290
300
|
"firmware_version": None,
|
291
301
|
"hardware_version": None,
|
@@ -304,25 +314,25 @@ class SmileCommon:
|
|
304
314
|
search = return_valid(xml_2, self._domain_objects)
|
305
315
|
module = search.find(loc)
|
306
316
|
if module is not None: # pylint: disable=consider-using-assignment-expr
|
307
|
-
|
308
|
-
get_vendor_name(module,
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
self._get_zigbee_data(module,
|
317
|
+
module_data["contents"] = True
|
318
|
+
get_vendor_name(module, module_data)
|
319
|
+
module_data["vendor_model"] = module.find("vendor_model").text
|
320
|
+
module_data["hardware_version"] = module.find("hardware_version").text
|
321
|
+
module_data["firmware_version"] = module.find("firmware_version").text
|
322
|
+
self._get_zigbee_data(module, module_data, legacy)
|
313
323
|
|
314
|
-
return
|
324
|
+
return module_data
|
315
325
|
|
316
|
-
def _get_zigbee_data(self, module: etree,
|
317
|
-
"""Helper-function for
|
326
|
+
def _get_zigbee_data(self, module: etree, module_data: ModuleData, legacy: bool) -> None:
|
327
|
+
"""Helper-function for _get_module_data()."""
|
318
328
|
if legacy:
|
319
329
|
# Stretches
|
320
330
|
if (router := module.find("./protocols/network_router")) is not None:
|
321
|
-
|
331
|
+
module_data["zigbee_mac_address"] = router.find("mac_address").text
|
322
332
|
# Also look for the Circle+/Stealth M+
|
323
333
|
if (coord := module.find("./protocols/network_coordinator")) is not None:
|
324
|
-
|
334
|
+
module_data["zigbee_mac_address"] = coord.find("mac_address").text
|
325
335
|
# Adam
|
326
336
|
elif (zb_node := module.find("./protocols/zig_bee_node")) is not None:
|
327
|
-
|
328
|
-
|
337
|
+
module_data["zigbee_mac_address"] = zb_node.find("mac_address").text
|
338
|
+
module_data["reachable"] = zb_node.find("reachable").text == "true"
|
@@ -190,6 +190,14 @@ OBSOLETE_MEASUREMENTS: Final[tuple[str, ...]] = (
|
|
190
190
|
"outdoor_temperature",
|
191
191
|
)
|
192
192
|
|
193
|
+
# Zone/climate related measurements
|
194
|
+
ZONE_MEASUREMENTS: Final[dict[str, DATA | UOM]] = {
|
195
|
+
"electricity_consumed": UOM(POWER_WATT),
|
196
|
+
"electricity_produced": UOM(POWER_WATT),
|
197
|
+
"relay": UOM(NONE),
|
198
|
+
"temperature": UOM(TEMP_CELSIUS), # HA Core thermostat current_temperature
|
199
|
+
}
|
200
|
+
|
193
201
|
# Literals
|
194
202
|
SMILE_P1 = "Smile P1"
|
195
203
|
POWER = "power"
|
@@ -397,8 +405,8 @@ class GatewayData(TypedDict, total=False):
|
|
397
405
|
smile_name: str
|
398
406
|
|
399
407
|
|
400
|
-
class
|
401
|
-
"""The
|
408
|
+
class ModuleData(TypedDict):
|
409
|
+
"""The Module data class."""
|
402
410
|
|
403
411
|
contents: bool
|
404
412
|
firmware_version: str | None
|
@@ -492,9 +500,9 @@ class ThermoLoc(TypedDict, total=False):
|
|
492
500
|
"""Thermo Location class."""
|
493
501
|
|
494
502
|
name: str
|
495
|
-
primary: str
|
503
|
+
primary: list[str]
|
496
504
|
primary_prio: int
|
497
|
-
secondary:
|
505
|
+
secondary: list[str]
|
498
506
|
|
499
507
|
|
500
508
|
class ActuatorData(TypedDict, total=False):
|
@@ -508,8 +516,11 @@ class ActuatorData(TypedDict, total=False):
|
|
508
516
|
upper_bound: float
|
509
517
|
|
510
518
|
|
511
|
-
class
|
512
|
-
"""The
|
519
|
+
class GwEntityData(TypedDict, total=False):
|
520
|
+
"""The Gateway Entity data class.
|
521
|
+
|
522
|
+
Covering the collected output-data per device or location.
|
523
|
+
"""
|
513
524
|
|
514
525
|
# Appliance base data
|
515
526
|
dev_class: str
|
@@ -544,13 +555,13 @@ class DeviceData(TypedDict, total=False):
|
|
544
555
|
select_gateway_mode: str
|
545
556
|
select_regulation_mode: str
|
546
557
|
|
547
|
-
#
|
558
|
+
# Thermostat-related
|
559
|
+
thermostats: dict[str, list[str]]
|
548
560
|
# Presets:
|
549
561
|
active_preset: str | None
|
550
562
|
preset_modes: list[str] | None
|
551
563
|
# Schedules:
|
552
564
|
available_schedules: list[str]
|
553
|
-
last_used: str | None
|
554
565
|
select_schedule: str
|
555
566
|
|
556
567
|
climate_mode: str
|
@@ -571,5 +582,6 @@ class DeviceData(TypedDict, total=False):
|
|
571
582
|
class PlugwiseData:
|
572
583
|
"""Plugwise data provided as output."""
|
573
584
|
|
585
|
+
devices: dict[str, GwEntityData]
|
574
586
|
gateway: GatewayData
|
575
|
-
|
587
|
+
|