plugwise 1.7.1__py3-none-any.whl → 1.7.3__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 CHANGED
@@ -18,7 +18,6 @@ from plugwise.constants import (
18
18
  STATUS,
19
19
  SYSTEM,
20
20
  GwEntityData,
21
- SmileProps,
22
21
  ThermoLoc,
23
22
  )
24
23
  from plugwise.exceptions import (
@@ -69,7 +68,6 @@ class Smile(SmileComm):
69
68
  self._opentherm_device = False
70
69
  self._schedule_old_states: dict[str, dict[str, str]] = {}
71
70
  self._smile_api: SmileAPI | SmileLegacyAPI
72
- self._smile_props: SmileProps = {}
73
71
  self._stretch_v2 = False
74
72
  self._target_smile: str = NONE
75
73
  self.smile_hostname: str = NONE
@@ -86,26 +84,22 @@ class Smile(SmileComm):
86
84
  @property
87
85
  def cooling_present(self) -> bool:
88
86
  """Return the cooling capability."""
89
- if "cooling_present" in self._smile_props:
90
- return self._smile_props["cooling_present"]
91
- return False
87
+ return self._smile_api.cooling_present
92
88
 
93
89
  @property
94
90
  def gateway_id(self) -> str:
95
91
  """Return the gateway-id."""
96
- return self._smile_props["gateway_id"]
92
+ return self._smile_api.gateway_id
97
93
 
98
94
  @property
99
95
  def heater_id(self) -> str:
100
96
  """Return the heater-id."""
101
- if "heater_id" in self._smile_props:
102
- return self._smile_props["heater_id"]
103
- return NONE
97
+ return self._smile_api.heater_id
104
98
 
105
99
  @property
106
100
  def item_count(self) -> int:
107
101
  """Return the item-count."""
108
- return self._smile_props["item_count"]
102
+ return self._smile_api.item_count
109
103
 
110
104
  @property
111
105
  def reboot(self) -> bool:
@@ -162,7 +156,6 @@ class Smile(SmileComm):
162
156
  self._opentherm_device,
163
157
  self._request,
164
158
  self._schedule_old_states,
165
- self._smile_props,
166
159
  self.smile_hostname,
167
160
  self.smile_hw_version,
168
161
  self.smile_mac_address,
@@ -179,7 +172,6 @@ class Smile(SmileComm):
179
172
  self._on_off_device,
180
173
  self._opentherm_device,
181
174
  self._request,
182
- self._smile_props,
183
175
  self._stretch_v2,
184
176
  self._target_smile,
185
177
  self.smile_hostname,
@@ -198,7 +190,9 @@ class Smile(SmileComm):
198
190
 
199
191
  return self.smile_version
200
192
 
201
- async def _smile_detect(self, result: etree, dsmrmain: etree) -> None:
193
+ async def _smile_detect(
194
+ self, result: etree.Element, dsmrmain: etree.Element
195
+ ) -> None:
202
196
  """Helper-function for connect().
203
197
 
204
198
  Detect which type of Plugwise Gateway is being connected.
@@ -256,10 +250,8 @@ class Smile(SmileComm):
256
250
  # For Adam, Anna, determine the system capabilities:
257
251
  # Find the connected heating/cooling device (heater_central),
258
252
  # e.g. heat-pump or gas-fired heater
259
- onoff_boiler: etree = result.find("./module/protocols/onoff_boiler")
260
- open_therm_boiler: etree = result.find(
261
- "./module/protocols/open_therm_boiler"
262
- )
253
+ onoff_boiler = result.find("./module/protocols/onoff_boiler")
254
+ open_therm_boiler = result.find("./module/protocols/open_therm_boiler")
263
255
  self._on_off_device = onoff_boiler is not None
264
256
  self._opentherm_device = open_therm_boiler is not None
265
257
 
@@ -272,7 +264,7 @@ class Smile(SmileComm):
272
264
  self._elga = True
273
265
 
274
266
  async def _smile_detect_legacy(
275
- self, result: etree, dsmrmain: etree, model: str
267
+ self, result: etree.Element, dsmrmain: etree.Element, model: str
276
268
  ) -> str:
277
269
  """Helper-function for _smile_detect().
278
270
 
@@ -296,18 +288,19 @@ class Smile(SmileComm):
296
288
  ):
297
289
  system = await self._request(SYSTEM)
298
290
  self.smile_version = parse(system.find("./gateway/firmware").text)
299
- return_model = system.find("./gateway/product").text
291
+ return_model = str(system.find("./gateway/product").text)
300
292
  self.smile_hostname = system.find("./gateway/hostname").text
301
- # If wlan0 contains data it's active, so eth0 should be checked last
293
+ # If wlan0 contains data it's active, eth0 should be checked last as is preferred
302
294
  for network in ("wlan0", "eth0"):
303
295
  locator = f"./{network}/mac"
304
296
  if (net_locator := system.find(locator)) is not None:
305
297
  self.smile_mac_address = net_locator.text
298
+
306
299
  # P1 legacy:
307
300
  elif dsmrmain is not None:
308
301
  status = await self._request(STATUS)
309
302
  self.smile_version = parse(status.find("./system/version").text)
310
- return_model = status.find("./system/product").text
303
+ return_model = str(status.find("./system/product").text)
311
304
  self.smile_hostname = status.find("./network/hostname").text
312
305
  self.smile_mac_address = status.find("./network/mac_address").text
313
306
  else: # pragma: no cover
plugwise/common.py CHANGED
@@ -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(module: etree, module_data: ModuleData, legacy: bool) -> None:
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,13 +51,18 @@ 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] = {}
56
58
  self.smile_name: str
57
59
  self.smile_type: str
58
60
 
61
+ @property
62
+ def heater_id(self) -> str:
63
+ """Return the heater-id."""
64
+ return self._heater_id
65
+
59
66
  def smile(self, name: str) -> bool:
60
67
  """Helper-function checking the smile-name."""
61
68
  return self.smile_name == name
@@ -63,10 +70,10 @@ class SmileCommon:
63
70
  def _appl_heater_central_info(
64
71
  self,
65
72
  appl: Munch,
66
- xml_1: etree,
73
+ xml_1: etree.Element,
67
74
  legacy: bool,
68
- xml_2: etree = None,
69
- xml_3: etree = None,
75
+ xml_2: etree.Element = None,
76
+ xml_3: etree.Element = None,
70
77
  ) -> Munch:
71
78
  """Helper-function for _appliance_info_finder()."""
72
79
  # Find the valid heater_central
@@ -74,6 +81,9 @@ class SmileCommon:
74
81
  xml_2 = return_valid(xml_2, self._domain_objects)
75
82
  self._heater_id = check_heater_central(xml_2)
76
83
 
84
+ if self._heater_id == NONE:
85
+ return Munch() # pragma: no cover
86
+
77
87
  # Info for On-Off device
78
88
  if self._on_off_device:
79
89
  appl.name = "OnOff" # pragma: no cover
@@ -101,7 +111,7 @@ class SmileCommon:
101
111
  return appl
102
112
 
103
113
  def _appl_thermostat_info(
104
- self, appl: Munch, xml_1: etree, xml_2: etree = None
114
+ self, appl: Munch, xml_1: etree.Element, xml_2: etree.Element = None
105
115
  ) -> Munch:
106
116
  """Helper-function for _appliance_info_finder()."""
107
117
  locator = "./logs/point_log[type='thermostat']/thermostat"
@@ -190,7 +200,7 @@ class SmileCommon:
190
200
  return switch_groups
191
201
 
192
202
  def _get_lock_state(
193
- self, xml: etree, data: GwEntityData, stretch_v2: bool = False
203
+ self, xml: etree.Element, data: GwEntityData, stretch_v2: bool = False
194
204
  ) -> None:
195
205
  """Helper-function for _get_measurement_data().
196
206
 
@@ -209,9 +219,9 @@ class SmileCommon:
209
219
 
210
220
  def _get_module_data(
211
221
  self,
212
- xml_1: etree,
222
+ xml_1: etree.Element,
213
223
  locator: str,
214
- xml_2: etree = None,
224
+ xml_2: etree.Element = None,
215
225
  legacy: bool = False,
216
226
  ) -> ModuleData:
217
227
  """Helper-function for _energy_device_info_finder() and _appliance_info_finder().
plugwise/constants.py CHANGED
@@ -393,17 +393,6 @@ ZONE_THERMOSTATS: Final[tuple[str, ...]] = (
393
393
  )
394
394
 
395
395
 
396
- class SmileProps(TypedDict, total=False):
397
- """The SmileProps Data class."""
398
-
399
- cooling_present: bool
400
- gateway_id: str
401
- heater_id: str
402
- item_count: int
403
- reboot: bool
404
- smile_name: str
405
-
406
-
407
396
  class ModuleData(TypedDict):
408
397
  """The Module data class."""
409
398
 
plugwise/data.py CHANGED
@@ -16,7 +16,6 @@ from plugwise.constants import (
16
16
  OFF,
17
17
  ActuatorData,
18
18
  GwEntityData,
19
- SmileProps,
20
19
  )
21
20
  from plugwise.helper import SmileHelper
22
21
  from plugwise.util import remove_empty_platform_dicts
@@ -27,28 +26,19 @@ class SmileData(SmileHelper):
27
26
 
28
27
  def __init__(self) -> None:
29
28
  """Init."""
30
- self._smile_props: SmileProps
29
+ super().__init__()
31
30
  self._zones: dict[str, GwEntityData] = {}
32
- SmileHelper.__init__(self)
33
31
 
34
32
  def _all_entity_data(self) -> None:
35
33
  """Helper-function for get_all_gateway_entities().
36
34
 
37
- Collect data for each entity and add to self._smile_props and self.gw_entities.
35
+ Collect data for each entity and add to self.gw_entities.
38
36
  """
39
37
  self._update_gw_entities()
40
38
  if self.smile(ADAM):
41
39
  self._update_zones()
42
40
  self.gw_entities.update(self._zones)
43
41
 
44
- self._smile_props["gateway_id"] = self._gateway_id
45
- self._smile_props["item_count"] = self._count
46
- self._smile_props["reboot"] = True
47
- self._smile_props["smile_name"] = self.smile_name
48
- if self._is_thermostat:
49
- self._smile_props["heater_id"] = self._heater_id
50
- self._smile_props["cooling_present"] = self._cooling_present
51
-
52
42
  def _update_zones(self) -> None:
53
43
  """Helper-function for _all_entity_data() and async_update().
54
44
 
@@ -228,6 +218,7 @@ class SmileData(SmileHelper):
228
218
  for msg in item.values():
229
219
  if message in msg:
230
220
  data["available"] = False
221
+ break
231
222
 
232
223
  def _get_adam_data(self, entity: GwEntityData, data: GwEntityData) -> None:
233
224
  """Helper-function for _get_entity_data().
@@ -329,16 +320,14 @@ class SmileData(SmileHelper):
329
320
 
330
321
  Also, replace NONE by OFF when none of the schedules are active.
331
322
  """
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
323
  all_off = True
340
- for state in self._schedule_old_states[location].values():
341
- if state == "on":
324
+ self._schedule_old_states[location] = {}
325
+ for schedule in schedules:
326
+ active: bool = schedule == selected and data["climate_mode"] == "auto"
327
+ self._schedule_old_states[location][schedule] = "off"
328
+ if active:
329
+ self._schedule_old_states[location][schedule] = "on"
342
330
  all_off = False
331
+
343
332
  if all_off:
344
333
  data["select_schedule"] = OFF
plugwise/helper.py CHANGED
@@ -59,7 +59,9 @@ from munch import Munch
59
59
  from packaging import version
60
60
 
61
61
 
62
- def search_actuator_functionalities(appliance: etree, actuator: str) -> etree | None:
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:
@@ -73,6 +75,7 @@ class SmileHelper(SmileCommon):
73
75
 
74
76
  def __init__(self) -> None:
75
77
  """Set the constructor for this class."""
78
+ super().__init__()
76
79
  self._endpoint: str
77
80
  self._elga: bool
78
81
  self._is_thermostat: bool
@@ -87,7 +90,16 @@ class SmileHelper(SmileCommon):
87
90
  self.smile_model: str
88
91
  self.smile_model_id: str | None
89
92
  self.smile_version: version.Version
90
- SmileCommon.__init__(self)
93
+
94
+ @property
95
+ def gateway_id(self) -> str:
96
+ """Return the gateway-id."""
97
+ return self._gateway_id
98
+
99
+ @property
100
+ def item_count(self) -> int:
101
+ """Return the item-count."""
102
+ return self._count
91
103
 
92
104
  def _all_appliances(self) -> None:
93
105
  """Collect all appliances with relevant info.
@@ -195,6 +207,7 @@ class SmileHelper(SmileCommon):
195
207
  other_entities = self.gw_entities
196
208
  priority_entities = {entity_id: priority_entity}
197
209
  self.gw_entities = {**priority_entities, **other_entities}
210
+ break
198
211
 
199
212
  def _all_locations(self) -> None:
200
213
  """Collect all locations."""
@@ -212,7 +225,7 @@ class SmileHelper(SmileCommon):
212
225
  f"./location[@id='{loc.loc_id}']"
213
226
  )
214
227
 
215
- def _appliance_info_finder(self, appl: Munch, appliance: etree) -> Munch:
228
+ def _appliance_info_finder(self, appl: Munch, appliance: etree.Element) -> Munch:
216
229
  """Collect info for all appliances found."""
217
230
  match appl.pwclass:
218
231
  case "gateway":
@@ -230,7 +243,7 @@ class SmileHelper(SmileCommon):
230
243
  appliance, "domestic_hot_water_mode_control_functionality"
231
244
  )
232
245
  # Skip orphaned heater_central (Core Issue #104433)
233
- if appl.entity_id != self._heater_id:
246
+ if appl.entity_id != self.heater_id:
234
247
  return Munch()
235
248
  return appl
236
249
  case _ as s if s.endswith("_plug"):
@@ -250,9 +263,9 @@ class SmileHelper(SmileCommon):
250
263
  appl.zigbee_mac = module_data["zigbee_mac_address"]
251
264
  return appl
252
265
  case _: # pragma: no cover
253
- return appl
266
+ return Munch()
254
267
 
255
- def _appl_gateway_info(self, appl: Munch, appliance: etree) -> Munch:
268
+ def _appl_gateway_info(self, appl: Munch, appliance: etree.Element) -> Munch:
256
269
  """Helper-function for _appliance_info_finder()."""
257
270
  self._gateway_id = appliance.attrib["id"]
258
271
  appl.firmware = str(self.smile_version)
@@ -285,7 +298,7 @@ class SmileHelper(SmileCommon):
285
298
  return appl
286
299
 
287
300
  def _get_appl_actuator_modes(
288
- self, appliance: etree, actuator_type: str
301
+ self, appliance: etree.Element, actuator_type: str
289
302
  ) -> list[str]:
290
303
  """Get allowed modes for the given actuator type."""
291
304
  mode_list: list[str] = []
@@ -341,7 +354,7 @@ class SmileHelper(SmileCommon):
341
354
 
342
355
  # Get non-P1 data from APPLIANCES
343
356
  measurements = DEVICE_MEASUREMENTS
344
- if self._is_thermostat and entity_id == self._heater_id:
357
+ if self._is_thermostat and entity_id == self.heater_id:
345
358
  measurements = HEATER_CENTRAL_MEASUREMENTS
346
359
  # Show the allowed dhw_modes (Loria only)
347
360
  if self._dhw_allowed_modes:
@@ -397,7 +410,7 @@ class SmileHelper(SmileCommon):
397
410
 
398
411
  def _appliance_measurements(
399
412
  self,
400
- appliance: etree,
413
+ appliance: etree.Element,
401
414
  data: GwEntityData,
402
415
  measurements: dict[str, DATA | UOM],
403
416
  ) -> None:
@@ -429,7 +442,7 @@ class SmileHelper(SmileCommon):
429
442
  self._count = count_data_items(self._count, data)
430
443
 
431
444
  def _get_toggle_state(
432
- self, xml: etree, toggle: str, name: ToggleNameType, data: GwEntityData
445
+ self, xml: etree.Element, toggle: str, name: ToggleNameType, data: GwEntityData
433
446
  ) -> None:
434
447
  """Helper-function for _get_measurement_data().
435
448
 
@@ -458,7 +471,7 @@ class SmileHelper(SmileCommon):
458
471
  )
459
472
 
460
473
  def _get_actuator_functionalities(
461
- self, xml: etree, entity: GwEntityData, data: GwEntityData
474
+ self, xml: etree.Element, entity: GwEntityData, data: GwEntityData
462
475
  ) -> None:
463
476
  """Get and process the actuator_functionalities details for an entity.
464
477
 
@@ -520,7 +533,7 @@ class SmileHelper(SmileCommon):
520
533
  data[act_item] = temp_dict
521
534
 
522
535
  def _get_actuator_mode(
523
- self, appliance: etree, entity_id: str, key: str
536
+ self, appliance: etree.Element, entity_id: str, key: str
524
537
  ) -> str | None:
525
538
  """Helper-function for _get_regulation_mode and _get_gateway_mode.
526
539
 
@@ -535,7 +548,7 @@ class SmileHelper(SmileCommon):
535
548
  return None
536
549
 
537
550
  def _get_regulation_mode(
538
- self, appliance: etree, entity_id: str, data: GwEntityData
551
+ self, appliance: etree.Element, entity_id: str, data: GwEntityData
539
552
  ) -> None:
540
553
  """Helper-function for _get_measurement_data().
541
554
 
@@ -551,7 +564,7 @@ class SmileHelper(SmileCommon):
551
564
  self._cooling_enabled = mode == "cooling"
552
565
 
553
566
  def _get_gateway_mode(
554
- self, appliance: etree, entity_id: str, data: GwEntityData
567
+ self, appliance: etree.Element, entity_id: str, data: GwEntityData
555
568
  ) -> None:
556
569
  """Helper-function for _get_measurement_data().
557
570
 
@@ -616,7 +629,7 @@ class SmileHelper(SmileCommon):
616
629
 
617
630
  Support added for Techneco Elga and Thercon Loria/Thermastage.
618
631
  """
619
- if entity_id != self._heater_id:
632
+ if entity_id != self.heater_id:
620
633
  return
621
634
 
622
635
  if "elga_status_code" in data:
@@ -837,9 +850,7 @@ class SmileHelper(SmileCommon):
837
850
  return presets # pragma: no cover
838
851
 
839
852
  for rule_id in rule_ids:
840
- directives: etree = self._domain_objects.find(
841
- f'rule[@id="{rule_id}"]/directives'
842
- )
853
+ directives = self._domain_objects.find(f'rule[@id="{rule_id}"]/directives')
843
854
  for directive in directives:
844
855
  preset = directive.find("then").attrib
845
856
  presets[directive.attrib["preset"]] = [
plugwise/legacy/data.py CHANGED
@@ -7,7 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  # Dict as class
9
9
  # Version detection
10
- from plugwise.constants import NONE, OFF, GwEntityData, SmileProps
10
+ from plugwise.constants import NONE, OFF, GwEntityData
11
11
  from plugwise.legacy.helper import SmileLegacyHelper
12
12
  from plugwise.util import remove_empty_platform_dicts
13
13
 
@@ -15,22 +15,12 @@ from plugwise.util import remove_empty_platform_dicts
15
15
  class SmileLegacyData(SmileLegacyHelper):
16
16
  """The Plugwise Smile main class."""
17
17
 
18
- def __init__(self) -> None:
19
- """Init."""
20
- self._smile_props: SmileProps
21
- SmileLegacyHelper.__init__(self)
22
-
23
18
  def _all_entity_data(self) -> None:
24
19
  """Helper-function for get_all_gateway_entities().
25
20
 
26
- Collect data for each entity and add to self._smile_props and self.gw_entities.
21
+ Collect data for each entity and add to self.gw_entities.
27
22
  """
28
23
  self._update_gw_entities()
29
- self._smile_props["gateway_id"] = self.gateway_id
30
- self._smile_props["item_count"] = self._count
31
- self._smile_props["smile_name"] = self.smile_name
32
- if self._is_thermostat:
33
- self._smile_props["heater_id"] = self._heater_id
34
24
 
35
25
  def _update_gw_entities(self) -> None:
36
26
  """Helper-function for _all_entity_data() and async_update().
plugwise/legacy/helper.py CHANGED
@@ -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,18 +64,29 @@ class SmileLegacyHelper(SmileCommon):
63
64
 
64
65
  def __init__(self) -> None:
65
66
  """Set the constructor for this class."""
66
- self._appliances: etree
67
+ super().__init__()
68
+ self._appliances: etree.Element
69
+ self._gateway_id: str = NONE
67
70
  self._is_thermostat: bool
68
71
  self._loc_data: dict[str, ThermoLoc]
69
- self._locations: etree
70
- self._modules: etree
72
+ self._locations: etree.Element
73
+ self._modules: etree.Element
71
74
  self._stretch_v2: bool
72
75
  self.gw_entities: dict[str, GwEntityData] = {}
73
76
  self.smile_mac_address: str | None
74
77
  self.smile_model: str
75
78
  self.smile_version: Version
76
79
  self.smile_zigbee_mac_address: str | None
77
- SmileCommon.__init__(self)
80
+
81
+ @property
82
+ def gateway_id(self) -> str:
83
+ """Return the gateway-id."""
84
+ return self._gateway_id
85
+
86
+ @property
87
+ def item_count(self) -> int:
88
+ """Return the item-count."""
89
+ return self._count
78
90
 
79
91
  def _all_appliances(self) -> None:
80
92
  """Collect all appliances with relevant info."""
@@ -124,13 +136,13 @@ class SmileLegacyHelper(SmileCommon):
124
136
  continue
125
137
 
126
138
  # Skip orphaned heater_central (Core Issue #104433)
127
- if appl.pwclass == "heater_central" and appl.entity_id != self._heater_id:
139
+ if appl.pwclass == "heater_central" and appl.entity_id != self.heater_id:
128
140
  continue # pragma: no cover
129
141
 
130
142
  self._create_gw_entities(appl)
131
143
 
132
144
  # Place the gateway and optional heater_central devices as 1st and 2nd
133
- for dev_class in ("heater_central", "gateway"):
145
+ for dev_class in PRIORITY_DEVICE_CLASSES:
134
146
  for entity_id, entity in dict(self.gw_entities).items():
135
147
  if entity["dev_class"] == dev_class:
136
148
  tmp_entity = entity
@@ -138,6 +150,7 @@ class SmileLegacyHelper(SmileCommon):
138
150
  cleared_dict = self.gw_entities
139
151
  add_to_front = {entity_id: tmp_entity}
140
152
  self.gw_entities = {**add_to_front, **cleared_dict}
153
+ break
141
154
 
142
155
  def _all_locations(self) -> None:
143
156
  """Collect all locations."""
@@ -171,11 +184,11 @@ class SmileLegacyHelper(SmileCommon):
171
184
 
172
185
  Use the home_location or FAKE_APPL as entity id.
173
186
  """
174
- self.gateway_id = self._home_loc_id
187
+ self._gateway_id = self._home_loc_id
175
188
  if self.smile_type == "power":
176
- self.gateway_id = FAKE_APPL
189
+ self._gateway_id = FAKE_APPL
177
190
 
178
- self.gw_entities[self.gateway_id] = {"dev_class": "gateway"}
191
+ self.gw_entities[self._gateway_id] = {"dev_class": "gateway"}
179
192
  self._count += 1
180
193
  for key, value in {
181
194
  "firmware": str(self.smile_version),
@@ -188,7 +201,7 @@ class SmileLegacyHelper(SmileCommon):
188
201
  }.items():
189
202
  if value is not None:
190
203
  gw_key = cast(ApplianceType, key)
191
- self.gw_entities[self.gateway_id][gw_key] = value
204
+ self.gw_entities[self._gateway_id][gw_key] = value
192
205
  self._count += 1
193
206
 
194
207
  def _appliance_info_finder(self, appliance: etree, appl: Munch) -> Munch:
@@ -266,7 +279,7 @@ class SmileLegacyHelper(SmileCommon):
266
279
  return data
267
280
 
268
281
  measurements = DEVICE_MEASUREMENTS
269
- if self._is_thermostat and entity_id == self._heater_id:
282
+ if self._is_thermostat and entity_id == self.heater_id:
270
283
  measurements = HEATER_CENTRAL_MEASUREMENTS
271
284
 
272
285
  if (
@@ -280,7 +293,7 @@ class SmileLegacyHelper(SmileCommon):
280
293
 
281
294
  # Anna: the Smile outdoor_temperature is present in the Home location
282
295
  # For some Anna's LOCATIONS is empty, falling back to domain_objects!
283
- if self._is_thermostat and entity_id == self.gateway_id:
296
+ if self._is_thermostat and entity_id == self._gateway_id:
284
297
  locator = f"./location[@id='{self._home_loc_id}']/logs/point_log[type='outdoor_temperature']/period/measurement"
285
298
  if (found := self._domain_objects.find(locator)) is not None:
286
299
  value = format_measure(found.text, NONE)
@@ -316,7 +329,7 @@ class SmileLegacyHelper(SmileCommon):
316
329
 
317
330
  def _appliance_measurements(
318
331
  self,
319
- appliance: etree,
332
+ appliance: etree.Element,
320
333
  data: GwEntityData,
321
334
  measurements: dict[str, DATA | UOM],
322
335
  ) -> None:
@@ -345,7 +358,7 @@ class SmileLegacyHelper(SmileCommon):
345
358
  self._count = count_data_items(self._count, data)
346
359
 
347
360
  def _get_actuator_functionalities(
348
- self, xml: etree, entity: GwEntityData, data: GwEntityData
361
+ self, xml: etree.Element, entity: GwEntityData, data: GwEntityData
349
362
  ) -> None:
350
363
  """Helper-function for _get_measurement_data()."""
351
364
  for item in ACTIVE_ACTUATORS:
plugwise/legacy/smile.py CHANGED
@@ -19,7 +19,6 @@ from plugwise.constants import (
19
19
  REQUIRE_APPLIANCES,
20
20
  RULES,
21
21
  GwEntityData,
22
- SmileProps,
23
22
  ThermoLoc,
24
23
  )
25
24
  from plugwise.exceptions import ConnectionFailedError, DataMissingError, PlugwiseError
@@ -41,7 +40,6 @@ class SmileLegacyAPI(SmileLegacyData):
41
40
  _on_off_device: bool,
42
41
  _opentherm_device: bool,
43
42
  _request: Callable[..., Awaitable[Any]],
44
- _smile_props: SmileProps,
45
43
  _stretch_v2: bool,
46
44
  _target_smile: str,
47
45
  smile_hostname: str,
@@ -54,13 +52,13 @@ class SmileLegacyAPI(SmileLegacyData):
54
52
  smile_zigbee_mac_address: str | None,
55
53
  ) -> None:
56
54
  """Set the constructor for this class."""
55
+ super().__init__()
57
56
  self._cooling_present = False
58
57
  self._is_thermostat = _is_thermostat
59
58
  self._loc_data = _loc_data
60
59
  self._on_off_device = _on_off_device
61
60
  self._opentherm_device = _opentherm_device
62
61
  self._request = _request
63
- self._smile_props = _smile_props
64
62
  self._stretch_v2 = _stretch_v2
65
63
  self._target_smile = _target_smile
66
64
  self.smile_hostname = smile_hostname
@@ -71,11 +69,15 @@ class SmileLegacyAPI(SmileLegacyData):
71
69
  self.smile_type = smile_type
72
70
  self.smile_version = smile_version
73
71
  self.smile_zigbee_mac_address = smile_zigbee_mac_address
74
- SmileLegacyData.__init__(self)
75
72
 
76
73
  self._first_update = True
77
74
  self._previous_day_number: str = "0"
78
75
 
76
+ @property
77
+ def cooling_present(self) -> bool:
78
+ """Return the cooling capability."""
79
+ return False
80
+
79
81
  async def full_xml_update(self) -> None:
80
82
  """Perform a first fetch of the Plugwise server XML data."""
81
83
  self._domain_objects = await self._request(DOMAIN_OBJECTS)
@@ -171,9 +173,8 @@ class SmileLegacyAPI(SmileLegacyData):
171
173
  raise PlugwiseError("Plugwise: invalid preset.")
172
174
 
173
175
  locator = f'rule/directives/when/then[@icon="{preset}"].../.../...'
174
- rule = self._domain_objects.find(locator)
175
- data = f'<rules><rule id="{rule.attrib["id"]}"><active>true</active></rule></rules>'
176
-
176
+ rule_id = self._domain_objects.find(locator).attrib["id"]
177
+ data = f"<rules><rule id='{rule_id}'><active>true</active></rule></rules>"
177
178
  await self.call_request(RULES, method="put", data=data)
178
179
 
179
180
  async def set_regulation_mode(self, mode: str) -> None:
@@ -205,6 +206,7 @@ class SmileLegacyAPI(SmileLegacyData):
205
206
  for rule in self._domain_objects.findall("rule"):
206
207
  if rule.find("name").text == name:
207
208
  schedule_rule_id = rule.attrib["id"]
209
+ break
208
210
 
209
211
  if schedule_rule_id is None:
210
212
  raise PlugwiseError(
@@ -216,36 +218,68 @@ class SmileLegacyAPI(SmileLegacyData):
216
218
  new_state = "true"
217
219
 
218
220
  locator = f'.//*[@id="{schedule_rule_id}"]/template'
219
- for rule in self._domain_objects.findall(locator):
220
- template_id = rule.attrib["id"]
221
+ template_id = self._domain_objects.find(locator).attrib["id"]
221
222
 
222
- uri = f"{RULES};id={schedule_rule_id}"
223
223
  data = (
224
- "<rules><rule"
225
- f' id="{schedule_rule_id}"><name><![CDATA[{name}]]></name><template'
226
- f' id="{template_id}" /><active>{new_state}</active></rule></rules>'
224
+ "<rules>"
225
+ f"<rule id='{schedule_rule_id}'>"
226
+ f"<name><![CDATA[{name}]]></name>"
227
+ f"<template id='{template_id}' />"
228
+ f"<active>{new_state}</active>"
229
+ "</rule>"
230
+ "</rules>"
227
231
  )
228
-
232
+ uri = f"{RULES};id={schedule_rule_id}"
229
233
  await self.call_request(uri, method="put", data=data)
230
234
 
231
235
  async def set_switch_state(
232
236
  self, appl_id: str, members: list[str] | None, model: str, state: str
233
237
  ) -> None:
234
- """Set the given State of the relevant Switch."""
238
+ """Set the given state of the relevant switch.
239
+
240
+ For individual switches, sets the state directly.
241
+ For group switches, sets the state for each member in the group separately.
242
+ For switch-locks, sets the lock state using a different data format.
243
+ """
235
244
  switch = Munch()
236
245
  switch.actuator = "actuator_functionalities"
237
246
  switch.func_type = "relay_functionality"
238
247
  if self._stretch_v2:
239
248
  switch.actuator = "actuators"
240
249
  switch.func_type = "relay"
241
- switch.func = "state"
242
250
 
243
- if members is not None:
244
- return await self._set_groupswitch_member_state(members, state, switch)
251
+ # Handle switch-lock
252
+ if model == "lock":
253
+ state = "false" if state == "off" else "true"
254
+ appliance = self._appliances.find(f'appliance[@id="{appl_id}"]')
255
+ appl_name = appliance.find("name").text
256
+ appl_type = appliance.find("type").text
257
+ data = (
258
+ "<appliances>"
259
+ f"<appliance id='{appl_id}'>"
260
+ f"<name><![CDATA[{appl_name}]]></name>"
261
+ f"<description><![CDATA[]]></description>"
262
+ f"<type><![CDATA[{appl_type}]]></type>"
263
+ f"<{switch.actuator}>"
264
+ f"<{switch.func_type}>"
265
+ f"<lock>{state}</lock>"
266
+ f"</{switch.func_type}>"
267
+ f"</{switch.actuator}>"
268
+ "</appliance>"
269
+ "</appliances>"
270
+ )
271
+ await self.call_request(APPLIANCES, method="post", data=data)
272
+ return
245
273
 
246
- data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
247
- uri = f"{APPLIANCES};id={appl_id}/{switch.func_type}"
274
+ # Handle group of switches
275
+ data = f"<{switch.func_type}><state>{state}</state></{switch.func_type}>"
276
+ if members is not None:
277
+ return await self._set_groupswitch_member_state(
278
+ data, members, state, switch
279
+ )
248
280
 
281
+ # Handle individual relay switches
282
+ uri = f"{APPLIANCES};id={appl_id}/relay"
249
283
  if model == "relay":
250
284
  locator = (
251
285
  f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}/lock'
@@ -257,16 +291,14 @@ class SmileLegacyAPI(SmileLegacyData):
257
291
  await self.call_request(uri, method="put", data=data)
258
292
 
259
293
  async def _set_groupswitch_member_state(
260
- self, members: list[str], state: str, switch: Munch
294
+ self, data: str, members: list[str], state: str, switch: Munch
261
295
  ) -> None:
262
296
  """Helper-function for set_switch_state().
263
297
 
264
- Set the given State of the relevant Switch within a group of members.
298
+ Set the given State of the relevant Switch (relay) within a group of members.
265
299
  """
266
300
  for member in members:
267
- uri = f"{APPLIANCES};id={member}/{switch.func_type}"
268
- data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
269
-
301
+ uri = f"{APPLIANCES};id={member}/relay"
270
302
  await self.call_request(uri, method="put", data=data)
271
303
 
272
304
  async def set_temperature(self, _: str, items: dict[str, float]) -> None:
@@ -281,12 +313,12 @@ class SmileLegacyAPI(SmileLegacyData):
281
313
  ) # pragma: no cover"
282
314
 
283
315
  temperature = str(setpoint)
284
- uri = self._thermostat_uri()
285
316
  data = (
286
- "<thermostat_functionality><setpoint>"
287
- f"{temperature}</setpoint></thermostat_functionality>"
317
+ "<thermostat_functionality>"
318
+ f"<setpoint>{temperature}</setpoint>"
319
+ "</thermostat_functionality>"
288
320
  )
289
-
321
+ uri = self._thermostat_uri()
290
322
  await self.call_request(uri, method="put", data=data)
291
323
 
292
324
  async def call_request(self, uri: str, **kwargs: Any) -> None:
plugwise/smile.py CHANGED
@@ -18,11 +18,11 @@ from plugwise.constants import (
18
18
  LOCATIONS,
19
19
  MAX_SETPOINT,
20
20
  MIN_SETPOINT,
21
+ NONE,
21
22
  NOTIFICATIONS,
22
23
  OFF,
23
24
  RULES,
24
25
  GwEntityData,
25
- SmileProps,
26
26
  ThermoLoc,
27
27
  )
28
28
  from plugwise.data import SmileData
@@ -51,7 +51,6 @@ class SmileAPI(SmileData):
51
51
  _opentherm_device: bool,
52
52
  _request: Callable[..., Awaitable[Any]],
53
53
  _schedule_old_states: dict[str, dict[str, str]],
54
- _smile_props: SmileProps,
55
54
  smile_hostname: str | None,
56
55
  smile_hw_version: str | None,
57
56
  smile_mac_address: str | None,
@@ -62,6 +61,7 @@ class SmileAPI(SmileData):
62
61
  smile_version: Version,
63
62
  ) -> None:
64
63
  """Set the constructor for this class."""
64
+ super().__init__()
65
65
  self._cooling_present = _cooling_present
66
66
  self._elga = _elga
67
67
  self._is_thermostat = _is_thermostat
@@ -71,7 +71,6 @@ class SmileAPI(SmileData):
71
71
  self._opentherm_device = _opentherm_device
72
72
  self._request = _request
73
73
  self._schedule_old_states = _schedule_old_states
74
- self._smile_props = _smile_props
75
74
  self.smile_hostname = smile_hostname
76
75
  self.smile_hw_version = smile_hw_version
77
76
  self.smile_mac_address = smile_mac_address
@@ -81,7 +80,11 @@ class SmileAPI(SmileData):
81
80
  self.smile_type = smile_type
82
81
  self.smile_version = smile_version
83
82
  self.therms_with_offset_func: list[str] = []
84
- SmileData.__init__(self)
83
+
84
+ @property
85
+ def cooling_present(self) -> bool:
86
+ """Return the cooling capability."""
87
+ return self._cooling_present
85
88
 
86
89
  async def full_xml_update(self) -> None:
87
90
  """Perform a first fetch of the Plugwise server XML data."""
@@ -121,8 +124,8 @@ class SmileAPI(SmileData):
121
124
  self.get_all_gateway_entities()
122
125
  # Set self._cooling_enabled - required for set_temperature(),
123
126
  # also, check for a failed data-retrieval
124
- if "heater_id" in self._smile_props:
125
- heat_cooler = self.gw_entities[self._smile_props["heater_id"]]
127
+ if self.heater_id != NONE:
128
+ heat_cooler = self.gw_entities[self.heater_id]
126
129
  if (
127
130
  "binary_sensors" in heat_cooler
128
131
  and "cooling_enabled" in heat_cooler["binary_sensors"]
@@ -131,7 +134,7 @@ class SmileAPI(SmileData):
131
134
  "cooling_enabled"
132
135
  ]
133
136
  else: # cover failed data-retrieval for P1
134
- _ = self.gw_entities[self._smile_props["gateway_id"]]["location"]
137
+ _ = self.gw_entities[self.gateway_id]["location"]
135
138
  except KeyError as err:
136
139
  raise DataMissingError("No Plugwise actual data received") from err
137
140
 
@@ -174,8 +177,12 @@ class SmileAPI(SmileData):
174
177
  if thermostat_id is None:
175
178
  raise PlugwiseError(f"Plugwise: cannot change setpoint, {key} not found.")
176
179
 
180
+ data = (
181
+ "<thermostat_functionality>"
182
+ f"<setpoint>{temp}</setpoint>"
183
+ "</thermostat_functionality>"
184
+ )
177
185
  uri = f"{APPLIANCES};id={self._heater_id}/thermostat;id={thermostat_id}"
178
- data = f"<thermostat_functionality><setpoint>{temp}</setpoint></thermostat_functionality>"
179
186
  await self.call_request(uri, method="put", data=data)
180
187
 
181
188
  async def set_offset(self, dev_id: str, offset: float) -> None:
@@ -186,9 +193,8 @@ class SmileAPI(SmileData):
186
193
  )
187
194
 
188
195
  value = str(offset)
189
- uri = f"{APPLIANCES};id={dev_id}/offset;type=temperature_offset"
190
196
  data = f"<offset_functionality><offset>{value}</offset></offset_functionality>"
191
-
197
+ uri = f"{APPLIANCES};id={dev_id}/offset;type=temperature_offset"
192
198
  await self.call_request(uri, method="put", data=data)
193
199
 
194
200
  async def set_preset(self, loc_id: str, preset: str) -> None:
@@ -201,14 +207,16 @@ class SmileAPI(SmileData):
201
207
  current_location = self._domain_objects.find(f'location[@id="{loc_id}"]')
202
208
  location_name = current_location.find("name").text
203
209
  location_type = current_location.find("type").text
204
-
205
- uri = f"{LOCATIONS};id={loc_id}"
206
210
  data = (
207
- "<locations><location"
208
- f' id="{loc_id}"><name>{location_name}</name><type>{location_type}'
209
- f"</type><preset>{preset}</preset></location></locations>"
211
+ "<locations>"
212
+ f'<location id="{loc_id}">'
213
+ f"<name>{location_name}</name>"
214
+ f"<type>{location_type}</type>"
215
+ f"<preset>{preset}</preset>"
216
+ "</location>"
217
+ "</locations>"
210
218
  )
211
-
219
+ uri = f"{LOCATIONS};id={loc_id}"
212
220
  await self.call_request(uri, method="put", data=data)
213
221
 
214
222
  async def set_select(
@@ -231,9 +239,12 @@ class SmileAPI(SmileData):
231
239
  if mode not in self._dhw_allowed_modes:
232
240
  raise PlugwiseError("Plugwise: invalid dhw mode.")
233
241
 
242
+ data = (
243
+ "<domestic_hot_water_mode_control_functionality>"
244
+ f"<mode>{mode}</mode>"
245
+ "</domestic_hot_water_mode_control_functionality>"
246
+ )
234
247
  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
248
  await self.call_request(uri, method="put", data=data)
238
249
 
239
250
  async def set_gateway_mode(self, mode: str) -> None:
@@ -259,9 +270,13 @@ class SmileAPI(SmileData):
259
270
  vacation_time = time_2 + "T23:00:00.000Z"
260
271
  valid = f"<valid_from>{vacation_time}</valid_from><valid_to>{end_time}</valid_to>"
261
272
 
262
- 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
-
273
+ data = (
274
+ "<gateway_mode_control_functionality>"
275
+ f"<mode>{mode}</mode>"
276
+ f"{valid}"
277
+ "</gateway_mode_control_functionality>"
278
+ )
279
+ uri = f"{APPLIANCES};id={self.gateway_id}/gateway_mode_control"
265
280
  await self.call_request(uri, method="put", data=data)
266
281
 
267
282
  async def set_regulation_mode(self, mode: str) -> None:
@@ -269,12 +284,17 @@ class SmileAPI(SmileData):
269
284
  if mode not in self._reg_allowed_modes:
270
285
  raise PlugwiseError("Plugwise: invalid regulation mode.")
271
286
 
272
- uri = f"{APPLIANCES};type=gateway/regulation_mode_control"
273
287
  duration = ""
274
288
  if "bleeding" in mode:
275
289
  duration = "<duration>300</duration>"
276
- data = f"<regulation_mode_control_functionality>{duration}<mode>{mode}</mode></regulation_mode_control_functionality>"
277
290
 
291
+ data = (
292
+ "<regulation_mode_control_functionality>"
293
+ f"{duration}"
294
+ f"<mode>{mode}</mode>"
295
+ "</regulation_mode_control_functionality>"
296
+ )
297
+ uri = f"{APPLIANCES};type=gateway/regulation_mode_control"
278
298
  await self.call_request(uri, method="put", data=data)
279
299
 
280
300
  async def set_schedule_state(
@@ -323,18 +343,22 @@ class SmileAPI(SmileData):
323
343
  template = f'<template id="{template_id}" />'
324
344
 
325
345
  contexts = self.determine_contexts(loc_id, name, new_state, schedule_rule_id)
326
- uri = f"{RULES};id={schedule_rule_id}"
327
346
  data = (
328
- f'<rules><rule id="{schedule_rule_id}"><name><![CDATA[{name}]]></name>'
329
- f"{template}{contexts}</rule></rules>"
347
+ "<rules>"
348
+ f"<rule id='{schedule_rule_id}'>"
349
+ f"<name><![CDATA[{name}]]></name>"
350
+ f"{template}"
351
+ f"{contexts}"
352
+ "</rule>"
353
+ "</rules>"
330
354
  )
331
-
355
+ uri = f"{RULES};id={schedule_rule_id}"
332
356
  await self.call_request(uri, method="put", data=data)
333
357
  self._schedule_old_states[loc_id][name] = new_state
334
358
 
335
359
  def determine_contexts(
336
360
  self, loc_id: str, name: str, state: str, sched_id: str
337
- ) -> etree:
361
+ ) -> str:
338
362
  """Helper-function for set_schedule_state()."""
339
363
  locator = f'.//*[@id="{sched_id}"]/contexts'
340
364
  contexts = self._domain_objects.find(locator)
@@ -349,7 +373,7 @@ class SmileAPI(SmileData):
349
373
  if state == "on":
350
374
  contexts.append(subject)
351
375
 
352
- return etree.tostring(contexts, encoding="unicode").rstrip()
376
+ return str(etree.tostring(contexts, encoding="unicode").rstrip())
353
377
 
354
378
  async def set_switch_state(
355
379
  self, appl_id: str, members: list[str] | None, model: str, state: str
@@ -378,18 +402,22 @@ class SmileAPI(SmileData):
378
402
  return await self._set_groupswitch_member_state(members, state, switch)
379
403
 
380
404
  locator = f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}'
381
- found: list[etree] = self._domain_objects.findall(locator)
405
+ found = self._domain_objects.findall(locator)
382
406
  for item in found:
407
+ # multiple types of e.g. toggle_functionality present
383
408
  if (sw_type := item.find("type")) is not None:
384
409
  if sw_type.text == switch.act_type:
385
410
  switch_id = item.attrib["id"]
386
- else:
411
+ break
412
+ else: # actuators with a single item like relay_functionality
387
413
  switch_id = item.attrib["id"]
388
- break
389
414
 
415
+ data = (
416
+ f"<{switch.func_type}>"
417
+ f"<{switch.func}>{state}</{switch.func}>"
418
+ f"</{switch.func_type}>"
419
+ )
390
420
  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
421
  if model == "relay":
394
422
  locator = (
395
423
  f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}/lock'
@@ -411,8 +439,11 @@ class SmileAPI(SmileData):
411
439
  locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}'
412
440
  switch_id = self._domain_objects.find(locator).attrib["id"]
413
441
  uri = f"{APPLIANCES};id={member}/{switch.device};id={switch_id}"
414
- data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
415
-
442
+ data = (
443
+ f"<{switch.func_type}>"
444
+ f"<{switch.func}>{state}</{switch.func}>"
445
+ f"</{switch.func_type}>"
446
+ )
416
447
  await self.call_request(uri, method="put", data=data)
417
448
 
418
449
  async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
@@ -448,12 +479,12 @@ class SmileAPI(SmileData):
448
479
  ) # pragma: no cover"
449
480
 
450
481
  temperature = str(setpoint)
451
- uri = self._thermostat_uri(loc_id)
452
482
  data = (
453
- "<thermostat_functionality><setpoint>"
454
- f"{temperature}</setpoint></thermostat_functionality>"
483
+ "<thermostat_functionality>"
484
+ f"<setpoint>{temperature}</setpoint>"
485
+ "</thermostat_functionality>"
455
486
  )
456
-
487
+ uri = self._thermostat_uri(loc_id)
457
488
  await self.call_request(uri, method="put", data=data)
458
489
 
459
490
  async def call_request(self, uri: str, **kwargs: Any) -> None:
plugwise/smilecomm.py CHANGED
@@ -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(self, resp: ClientResponse, method: str) -> etree:
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:
plugwise/util.py CHANGED
@@ -13,6 +13,7 @@ from plugwise.constants import (
13
13
  ELECTRIC_POTENTIAL_VOLT,
14
14
  ENERGY_KILO_WATT_HOUR,
15
15
  HW_MODELS,
16
+ NONE,
16
17
  OBSOLETE_MEASUREMENTS,
17
18
  PERCENTAGE,
18
19
  POWER_WATT,
@@ -74,7 +75,7 @@ def in_alternative_location(loc: Munch, legacy: bool) -> bool:
74
75
  return present
75
76
 
76
77
 
77
- def check_heater_central(xml: etree) -> str:
78
+ def check_heater_central(xml: etree.Element) -> str:
78
79
  """Find the valid heater_central, helper-function for _appliance_info_finder().
79
80
 
80
81
  Solution for Core Issue #104433,
@@ -93,14 +94,16 @@ def check_heater_central(xml: etree) -> str:
93
94
  if heater_central.find("name").text == "Central heating boiler":
94
95
  hc_list.append({hc_id: has_actuators})
95
96
 
97
+ if not hc_list:
98
+ return NONE # pragma: no cover
99
+
96
100
  heater_central_id = list(hc_list[0].keys())[0]
97
101
  if hc_count > 1:
98
- for item in hc_list: # pragma: no cover
99
- for key, value in item.items(): # pragma: no cover
100
- if value: # pragma: no cover
101
- heater_central_id = key # pragma: no cover
102
- # Stop when a valid id is found
103
- break # pragma: no cover
102
+ for item in hc_list:
103
+ hc_id, has_actuators = next(iter(item.items()))
104
+ if has_actuators:
105
+ heater_central_id = hc_id
106
+ break
104
107
 
105
108
  return heater_central_id
106
109
 
@@ -135,7 +138,7 @@ def collect_power_values(
135
138
  if not loc.found:
136
139
  continue
137
140
 
138
- data = power_data_energy_diff(loc.measurement, loc.net_string, loc.f_val, data)
141
+ power_data_energy_diff(loc.measurement, loc.net_string, loc.f_val, data)
139
142
  key = cast(SensorType, loc.key_string)
140
143
  data["sensors"][key] = loc.f_val
141
144
 
@@ -143,7 +146,7 @@ def collect_power_values(
143
146
  def common_match_cases(
144
147
  measurement: str,
145
148
  attrs: DATA | UOM,
146
- location: etree,
149
+ location: etree.Element,
147
150
  data: GwEntityData,
148
151
  ) -> None:
149
152
  """Helper-function for common match-case execution."""
@@ -192,8 +195,6 @@ def escape_illegal_xml_characters(xmldata: str) -> str:
192
195
 
193
196
  def format_measure(measure: str, unit: str) -> float | int:
194
197
  """Format measure to correct type."""
195
- result: float | int = 0
196
-
197
198
  float_measure = float(measure)
198
199
  if unit == PERCENTAGE and 0 < float_measure <= 1:
199
200
  return int(float_measure * 100)
@@ -202,18 +203,18 @@ def format_measure(measure: str, unit: str) -> float | int:
202
203
  float_measure = float_measure / 1000
203
204
 
204
205
  if unit in SPECIAL_FORMAT:
205
- result = float(f"{round(float_measure, 3):.3f}")
206
+ result = round(float_measure, 3)
206
207
  elif unit == ELECTRIC_POTENTIAL_VOLT:
207
- result = float(f"{round(float_measure, 1):.1f}")
208
+ result = round(float_measure, 1)
208
209
  elif abs(float_measure) < 10:
209
- result = float(f"{round(float_measure, 2):.2f}")
210
- elif abs(float_measure) >= 10:
211
- result = float(f"{round(float_measure, 1):.1f}")
210
+ result = round(float_measure, 2)
211
+ else: # abs(float_measure) >= 10
212
+ result = round(float_measure, 1)
212
213
 
213
214
  return result
214
215
 
215
216
 
216
- def get_vendor_name(module: etree, model_data: ModuleData) -> ModuleData:
217
+ def get_vendor_name(module: etree.Element, model_data: ModuleData) -> ModuleData:
217
218
  """Helper-function for _get_model_data()."""
218
219
  if (vendor_name := module.find("vendor_name").text) is not None:
219
220
  model_data["vendor_name"] = vendor_name
@@ -228,31 +229,21 @@ def power_data_energy_diff(
228
229
  net_string: SensorType,
229
230
  f_val: float | int,
230
231
  data: GwEntityData,
231
- ) -> GwEntityData:
232
+ ) -> None:
232
233
  """Calculate differential energy."""
233
234
  if (
234
235
  "electricity" in measurement
235
236
  and "phase" not in measurement
236
237
  and "interval" not in net_string
237
238
  ):
238
- diff = 1
239
- if "produced" in measurement:
240
- diff = -1
241
- if net_string not in data["sensors"]:
242
- tmp_val: float | int = 0
243
- else:
244
- tmp_val = data["sensors"][net_string]
245
-
246
- if isinstance(f_val, int):
247
- tmp_val += f_val * diff
248
- else:
249
- tmp_val += float(f_val * diff)
250
- tmp_val = float(f"{round(tmp_val, 3):.3f}")
239
+ diff = 1 if "consumed" in measurement else -1
240
+ tmp_val = data["sensors"].get(net_string, 0)
241
+ tmp_val += f_val * diff
242
+ if isinstance(f_val, float):
243
+ tmp_val = round(tmp_val, 3)
251
244
 
252
245
  data["sensors"][net_string] = tmp_val
253
246
 
254
- return data
255
-
256
247
 
257
248
  def power_data_local_format(
258
249
  attrs: dict[str, str], key_string: str, val: str
@@ -302,12 +293,12 @@ def remove_empty_platform_dicts(data: GwEntityData) -> None:
302
293
  data.pop("switches")
303
294
 
304
295
 
305
- def return_valid(value: etree | None, default: etree) -> etree:
296
+ def return_valid(value: etree.Element | None, default: etree.Element) -> etree.Element:
306
297
  """Return default when value is None."""
307
298
  return value if value is not None else default
308
299
 
309
300
 
310
- def skip_obsolete_measurements(xml: etree, measurement: str) -> bool:
301
+ def skip_obsolete_measurements(xml: etree.Element, measurement: str) -> bool:
311
302
  """Skipping known obsolete measurements."""
312
303
  locator = f".//logs/point_log[type='{measurement}']/updated_date"
313
304
  if (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: plugwise
3
- Version: 1.7.1
3
+ Version: 1.7.3
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Home-page: https://github.com/plugwise/python-plugwise
6
6
  Author: Plugwise device owners
@@ -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 co-code owners).
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 (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).
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
+ ![Static Badge](https://img.shields.io/badge/Plugwise_Discord-Join_now-purple?style=social&logo=discord&link=https%3A%2F%2Fdiscord.gg%2FmFVhF8Ar6A)
57
58
  [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/plugwise)
58
59
  [![CodeRabbit.ai is Awesome](https://img.shields.io/badge/AI-orange?label=CodeRabbit&color=orange&link=https%3A%2F%2Fcoderabbit.ai)](https://coderabbit.ai)
59
60
  [![renovate maintained](https://img.shields.io/badge/maintained%20with-renovate-blue?logo=renovatebot)](https://github.com/plugwise/python-plugwise/issues/291)
@@ -0,0 +1,18 @@
1
+ plugwise/__init__.py,sha256=BKT3BagtOOy2Efk2m8uQLFPE4vizEjjPMj61Zs7MwMw,17492
2
+ plugwise/common.py,sha256=_O7cC7fPGHZkrxQTaK2y8_trr9lOjEtgbS6plOfp2jk,9589
3
+ plugwise/constants.py,sha256=zJFm0J14PJWasKSvepriOc6mYLljRVGNGAF5QDtzyl0,16869
4
+ plugwise/data.py,sha256=OxZufaAmnyVDkRGJBdk6tf-I65FaCwcT69scNsZnmiA,12490
5
+ plugwise/exceptions.py,sha256=Ce-tO9uNsMB-8FP6VAxBvsHNJ-NIM9F0onUZOdZI4Ys,1110
6
+ plugwise/helper.py,sha256=AdQ4Tno5zheFI5y5A-YovVGYW_islxCxkt0B77TfC-o,39701
7
+ plugwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ plugwise/smile.py,sha256=WPZ0v45RpgJxBWEy_Sy_vsX87V4UPaVkkSgY7yZouAQ,19550
9
+ plugwise/smilecomm.py,sha256=DRQ3toRNEf3oo_mej49fPJ47m5das-jvo-8GnIrSPzw,5208
10
+ plugwise/util.py,sha256=rMcqfaB4dkQEZFJY-bBJISmlYgTnb6Ns3-Doxelf92Q,10689
11
+ plugwise/legacy/data.py,sha256=Z-7nw21s9-L4DcwPCZ_yoRGeI_fWBS1Q48kHmyh2pkY,3145
12
+ plugwise/legacy/helper.py,sha256=cDi8zvUtoCqxVrLksqjlCHUmjXjulXCf-l9fMjFPOfs,17417
13
+ plugwise/legacy/smile.py,sha256=Q133Ub5W6VB1MyxzAZze3cFyXvTxIeIbCxSUP4eXNXM,12867
14
+ plugwise-1.7.3.dist-info/LICENSE,sha256=mL22BjmXtg_wnoDnnaqps5_Bg_VGj_yHueX5lsKwbCc,1144
15
+ plugwise-1.7.3.dist-info/METADATA,sha256=5sR52dUprms7TDT3wI7zO5-87uRT_ZhCCz420kCz970,9328
16
+ plugwise-1.7.3.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
17
+ plugwise-1.7.3.dist-info/top_level.txt,sha256=MYOmktMFf8ZmX6_OE1y9MoCZFfY-L8DA0F2tA2IvE4s,9
18
+ plugwise-1.7.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,18 +0,0 @@
1
- plugwise/__init__.py,sha256=hLwUqdGAo39LzTXYz6Lkua_9RelJx5k-uAeJSrZdfYk,17760
2
- plugwise/common.py,sha256=_41FLLjgccaHjaV0Ndn1YEkxjB_qKev1k3ZnhSUzXjc,9305
3
- plugwise/constants.py,sha256=Vd8tvOHsRtZxtUUFXBXolZj3QiKnxRt0bnwDT9DoEqE,17073
4
- plugwise/data.py,sha256=ITGnS5RiRbd3O2KbYLc9_5okw81zLl7azXNXtuTwaoY,13022
5
- plugwise/exceptions.py,sha256=Ce-tO9uNsMB-8FP6VAxBvsHNJ-NIM9F0onUZOdZI4Ys,1110
6
- plugwise/helper.py,sha256=v8qXFau2bXnJ4xGde9NuljA-LLEWSL1SJVcnXQX-jiE,39397
7
- plugwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- plugwise/smile.py,sha256=J8_CevN7FSYnAccYv4Hp_H3o9lfHGM_fYlLYAvuqJKw,18921
9
- plugwise/smilecomm.py,sha256=aQ2KkebDTou18k-flrVmTnFkgls33Xu8D9ZZQQt2R5k,5178
10
- plugwise/util.py,sha256=d3nOS9fzZ1MwJX8_77KOgW3CtQnGYc0oGcnqHrlxlLo,11015
11
- plugwise/legacy/data.py,sha256=s2WYjgxwcuAGS8UOxJVf7xLSxS38Zgr8GsjxlxfD98w,3574
12
- plugwise/legacy/helper.py,sha256=CjLGUhWRgNCOyooVyylLABdv0H2zoiKzjhQ-g7E8x9M,17059
13
- plugwise/legacy/smile.py,sha256=aCG5uaOCS4GOGfIbxTp8U-CFE3Cib-UspB4qSamm_eY,11659
14
- plugwise-1.7.1.dist-info/LICENSE,sha256=mL22BjmXtg_wnoDnnaqps5_Bg_VGj_yHueX5lsKwbCc,1144
15
- plugwise-1.7.1.dist-info/METADATA,sha256=Dnc7KdcTcDjT33JYXSkkDkHXkxKHXEet2gXQcQpSuyo,9148
16
- plugwise-1.7.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
17
- plugwise-1.7.1.dist-info/top_level.txt,sha256=MYOmktMFf8ZmX6_OE1y9MoCZFfY-L8DA0F2tA2IvE4s,9
18
- plugwise-1.7.1.dist-info/RECORD,,