plugwise 0.35.0__tar.gz → 0.35.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 0.35.0
3
+ Version: 0.35.2
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
@@ -17,6 +17,7 @@ import semver
17
17
 
18
18
  from .constants import (
19
19
  ADAM,
20
+ ANNA,
20
21
  APPLIANCES,
21
22
  DEFAULT_PORT,
22
23
  DEFAULT_TIMEOUT,
@@ -51,7 +52,7 @@ from .exceptions import (
51
52
  from .helper import SmileComm, SmileHelper
52
53
 
53
54
 
54
- def remove_empty_platform_dicts(data: DeviceData) -> DeviceData:
55
+ def remove_empty_platform_dicts(data: DeviceData) -> None:
55
56
  """Helper-function for removing any empty platform dicts."""
56
57
  if not data["binary_sensors"]:
57
58
  data.pop("binary_sensors")
@@ -60,16 +61,47 @@ def remove_empty_platform_dicts(data: DeviceData) -> DeviceData:
60
61
  if not data["switches"]:
61
62
  data.pop("switches")
62
63
 
63
- return data
64
-
65
64
 
66
65
  class SmileData(SmileHelper):
67
66
  """The Plugwise Smile main class."""
68
67
 
69
- def update_for_cooling(self, device: DeviceData) -> DeviceData:
68
+ def _update_gw_devices(self) -> None:
69
+ """Helper-function for _all_device_data() and async_update().
70
+
71
+ Collect data for each device and add to self.gw_devices.
72
+ """
73
+ for device_id, device in self.gw_devices.items():
74
+ data = self._get_device_data(device_id)
75
+ self._add_or_update_notifications(device_id, device, data)
76
+ device.update(data)
77
+ self._update_for_cooling(device)
78
+ remove_empty_platform_dicts(device)
79
+
80
+ def _add_or_update_notifications(
81
+ self, device_id: str, device: DeviceData, data: DeviceData
82
+ ) -> None:
83
+ """Helper-function adding or updating the Plugwise notifications."""
84
+ if (
85
+ device_id == self.gateway_id
86
+ and (
87
+ self._is_thermostat
88
+ or (self.smile_type == "power" and not self._smile_legacy)
89
+ )
90
+ ) or (
91
+ "binary_sensors" in device
92
+ and "plugwise_notification" in device["binary_sensors"]
93
+ ):
94
+ data["binary_sensors"]["plugwise_notification"] = bool(self._notifications)
95
+ self._count += 1
96
+
97
+ def _update_for_cooling(self, device: DeviceData) -> None:
70
98
  """Helper-function for adding/updating various cooling-related values."""
71
- # For heating + cooling, replace setpoint with setpoint_high/_low
72
- if self._cooling_present:
99
+ # For Anna and heating + cooling, replace setpoint with setpoint_high/_low
100
+ if (
101
+ self.smile(ANNA)
102
+ and self._cooling_present
103
+ and device["dev_class"] == "thermostat"
104
+ ):
73
105
  thermostat = device["thermostat"]
74
106
  sensors = device["sensors"]
75
107
  temp_dict: ActuatorData = {
@@ -90,36 +122,27 @@ class SmileData(SmileHelper):
90
122
  sensors["setpoint_high"] = temp_dict["setpoint_high"]
91
123
  self._count += 2
92
124
 
93
- return device
94
-
95
- def _update_gw_devices(self) -> None:
96
- """Helper-function for _all_device_data() and async_update().
125
+ def get_all_devices(self) -> None:
126
+ """Determine the evices present from the obtained XML-data.
97
127
 
98
- Collect data for each device and add to self.gw_devices.
128
+ Run this functions once to gather the initial device configuration,
129
+ then regularly run async_update() to refresh the device data.
99
130
  """
100
- for device_id, device in self.gw_devices.items():
101
- data = self._get_device_data(device_id)
102
- if (
103
- "binary_sensors" in device
104
- and "plugwise_notification" in device["binary_sensors"]
105
- ) or (
106
- device_id == self.gateway_id
107
- and (
108
- self._is_thermostat
109
- or (self.smile_type == "power" and not self._smile_legacy)
110
- )
111
- ):
112
- data["binary_sensors"]["plugwise_notification"] = bool(
113
- self._notifications
114
- )
115
- self._count += 1
116
- device.update(data)
131
+ # Gather all the devices and their initial data
132
+ self._all_appliances()
133
+ if self._is_thermostat:
134
+ self._scan_thermostats()
135
+ # Collect a list of thermostats with offset-capability
136
+ self.therms_with_offset_func = (
137
+ self._get_appliances_with_offset_functionality()
138
+ )
117
139
 
118
- # Update for cooling
119
- if device["dev_class"] in ZONE_THERMOSTATS and not self.smile(ADAM):
120
- self.update_for_cooling(device)
140
+ # Collect and add switching- and/or pump-group devices
141
+ if group_data := self._get_group_switches():
142
+ self.gw_devices.update(group_data)
121
143
 
122
- remove_empty_platform_dicts(device)
144
+ # Collect the remaining data for all devices
145
+ self._all_device_data()
123
146
 
124
147
  def _all_device_data(self) -> None:
125
148
  """Helper-function for get_all_devices().
@@ -145,67 +168,49 @@ class SmileData(SmileHelper):
145
168
  {"heater_id": self._heater_id, "cooling_present": self._cooling_present}
146
169
  )
147
170
 
148
- def get_all_devices(self) -> None:
149
- """Determine the evices present from the obtained XML-data.
150
-
151
- Run this functions once to gather the initial device configuration,
152
- then regularly run async_update() to refresh the device data.
153
- """
154
- # Gather all the devices and their initial data
155
- self._all_appliances()
156
- if self.smile_type == "thermostat":
157
- self._scan_thermostats()
158
- # Collect a list of thermostats with offset-capability
159
- self.therms_with_offset_func = (
160
- self._get_appliances_with_offset_functionality()
161
- )
162
-
163
- # Collect switching- or pump-group data
164
- if group_data := self._get_group_switches():
165
- self.gw_devices.update(group_data)
166
-
167
- # Collect the remaining data for all device
168
- self._all_device_data()
169
-
170
171
  def _device_data_switching_group(
171
- self, device: DeviceData, device_data: DeviceData
172
- ) -> DeviceData:
172
+ self, device: DeviceData, data: DeviceData
173
+ ) -> None:
173
174
  """Helper-function for _get_device_data().
174
175
 
175
176
  Determine switching group device data.
176
177
  """
177
- if device["dev_class"] not in SWITCH_GROUP_TYPES:
178
- return device_data
179
-
180
- counter = 0
181
- for member in device["members"]:
182
- if self.gw_devices[member]["switches"].get("relay"):
183
- counter += 1
184
- device_data["switches"]["relay"] = counter != 0
185
- self._count += 1
186
- return device_data
178
+ if device["dev_class"] in SWITCH_GROUP_TYPES:
179
+ counter = 0
180
+ for member in device["members"]:
181
+ if self.gw_devices[member]["switches"].get("relay"):
182
+ counter += 1
183
+ data["switches"]["relay"] = counter != 0
184
+ self._count += 1
187
185
 
188
- def _device_data_adam(
189
- self, device: DeviceData, device_data: DeviceData
190
- ) -> DeviceData:
186
+ def _device_data_adam(self, device: DeviceData, data: DeviceData) -> None:
191
187
  """Helper-function for _get_device_data().
192
188
 
193
- Determine Adam heating-status for on-off heating via valves.
189
+ Determine Adam heating-status for on-off heating via valves,
190
+ available regulations_modes and thermostat control_states.
194
191
  """
195
- # Indicate heating_state based on valves being open in case of city-provided heating
196
- if (
197
- self.smile(ADAM)
198
- and device.get("dev_class") == "heater_central"
199
- and self._on_off_device
200
- and isinstance(self._heating_valves(), int)
201
- ):
202
- device_data["binary_sensors"]["heating_state"] = self._heating_valves() != 0
192
+ if self.smile(ADAM):
193
+ # Indicate heating_state based on valves being open in case of city-provided heating
194
+ if (
195
+ device["dev_class"] == "heater_central"
196
+ and self._on_off_device
197
+ and isinstance(self._heating_valves(), int)
198
+ ):
199
+ data["binary_sensors"]["heating_state"] = self._heating_valves() != 0
200
+
201
+ # Show the allowed regulation modes for Adam
202
+ if device["dev_class"] == "gateway" and self._reg_allowed_modes:
203
+ data["regulation_modes"] = self._reg_allowed_modes
204
+ self._count += 1
203
205
 
204
- return device_data
206
+ # Control_state, only for Adam master thermostats
207
+ if device["dev_class"] in ZONE_THERMOSTATS:
208
+ loc_id = device["location"]
209
+ if ctrl_state := self._control_state(loc_id):
210
+ data["control_state"] = ctrl_state
211
+ self._count += 1
205
212
 
206
- def _device_data_climate(
207
- self, device: DeviceData, device_data: DeviceData
208
- ) -> DeviceData:
213
+ def _device_data_climate(self, device: DeviceData, data: DeviceData) -> None:
209
214
  """Helper-function for _get_device_data().
210
215
 
211
216
  Determine climate-control device data.
@@ -213,44 +218,34 @@ class SmileData(SmileHelper):
213
218
  loc_id = device["location"]
214
219
 
215
220
  # Presets
216
- device_data["preset_modes"] = None
217
- device_data["active_preset"] = None
221
+ data["preset_modes"] = None
222
+ data["active_preset"] = None
218
223
  self._count += 2
219
224
  if presets := self._presets(loc_id):
220
- device_data["preset_modes"] = list(presets)
221
- device_data["active_preset"] = self._preset(loc_id)
225
+ data["preset_modes"] = list(presets)
226
+ data["active_preset"] = self._preset(loc_id)
222
227
 
223
228
  # Schedule
224
229
  avail_schedules, sel_schedule = self._schedules(loc_id)
225
- device_data["available_schedules"] = avail_schedules
226
- device_data["select_schedule"] = sel_schedule
230
+ data["available_schedules"] = avail_schedules
231
+ data["select_schedule"] = sel_schedule
227
232
  self._count += 2
228
233
 
229
- # Control_state, only for Adam master thermostats
230
- if ctrl_state := self._control_state(loc_id):
231
- device_data["control_state"] = ctrl_state
232
- self._count += 1
233
-
234
234
  # Operation modes: auto, heat, heat_cool, cool and off
235
- device_data["mode"] = "auto"
235
+ data["mode"] = "auto"
236
236
  self._count += 1
237
237
  if sel_schedule == NONE:
238
- device_data["mode"] = "heat"
238
+ data["mode"] = "heat"
239
239
  if self._cooling_present:
240
- device_data["mode"] = (
241
- "cool" if self.check_reg_mode("cooling") else "heat_cool"
242
- )
240
+ data["mode"] = "cool" if self.check_reg_mode("cooling") else "heat_cool"
243
241
 
244
242
  if self.check_reg_mode("off"):
245
- device_data["mode"] = "off"
243
+ data["mode"] = "off"
246
244
 
247
- if NONE in avail_schedules:
248
- return device_data
249
-
250
- device_data = self._get_schedule_states_with_off(
251
- loc_id, avail_schedules, sel_schedule, device_data
252
- )
253
- return device_data
245
+ if NONE not in avail_schedules:
246
+ self._get_schedule_states_with_off(
247
+ loc_id, avail_schedules, sel_schedule, data
248
+ )
254
249
 
255
250
  def check_reg_mode(self, mode: str) -> bool:
256
251
  """Helper-function for device_data_climate()."""
@@ -261,7 +256,7 @@ class SmileData(SmileHelper):
261
256
 
262
257
  def _get_schedule_states_with_off(
263
258
  self, location: str, schedules: list[str], selected: str, data: DeviceData
264
- ) -> DeviceData:
259
+ ) -> None:
265
260
  """Collect schedules with states for each thermostat.
266
261
 
267
262
  Also, replace NONE by OFF when none of the schedules are active,
@@ -282,34 +277,20 @@ class SmileData(SmileHelper):
282
277
  if all_off:
283
278
  data["select_schedule"] = OFF
284
279
 
285
- return data
286
-
287
280
  def _check_availability(
288
- self, device: DeviceData, device_data: DeviceData
289
- ) -> DeviceData:
281
+ self, device: DeviceData, dev_class: str, data: DeviceData, message: str
282
+ ) -> None:
290
283
  """Helper-function for _get_device_data().
291
284
 
292
285
  Provide availability status for the wired-commected devices.
293
286
  """
294
- # OpenTherm device
295
- if device["dev_class"] == "heater_central" and device["name"] != "OnOff":
296
- device_data["available"] = True
287
+ if device["dev_class"] == dev_class:
288
+ data["available"] = True
297
289
  self._count += 1
298
- for data in self._notifications.values():
299
- for msg in data.values():
300
- if "no OpenTherm communication" in msg:
301
- device_data["available"] = False
302
-
303
- # Smartmeter
304
- if device["dev_class"] == "smartmeter":
305
- device_data["available"] = True
306
- self._count += 1
307
- for data in self._notifications.values():
308
- for msg in data.values():
309
- if "P1 does not seem to be connected to a smart meter" in msg:
310
- device_data["available"] = False
311
-
312
- return device_data
290
+ for item in self._notifications.values():
291
+ for msg in item.values():
292
+ if message in msg:
293
+ data["available"] = False
313
294
 
314
295
  def _get_device_data(self, dev_id: str) -> DeviceData:
315
296
  """Helper-function for _all_device_data() and async_update().
@@ -317,44 +298,32 @@ class SmileData(SmileHelper):
317
298
  Provide device-data, based on Location ID (= dev_id), from APPLIANCES.
318
299
  """
319
300
  device = self.gw_devices[dev_id]
320
- device_data = self._get_measurement_data(dev_id)
321
- # Generic
322
- if self.smile_type == "thermostat" and device["dev_class"] == "gateway":
323
- # Adam & Anna: the Smile outdoor_temperature is present in DOMAIN_OBJECTS and LOCATIONS - under Home
324
- # The outdoor_temperature present in APPLIANCES is a local sensor connected to the active device
325
- outdoor_temperature = self._object_value(
326
- self._home_location, "outdoor_temperature"
327
- )
328
- if outdoor_temperature is not None:
329
- device_data["sensors"]["outdoor_temperature"] = outdoor_temperature
330
- self._count += 1
331
-
332
- # Show the allowed regulation modes
333
- if self._reg_allowed_modes:
334
- device_data["regulation_modes"] = self._reg_allowed_modes
335
- self._count += 1
336
-
337
- # Show the allowed dhw_modes
338
- if device["dev_class"] == "heater_central" and self._dhw_allowed_modes:
339
- device_data["dhw_modes"] = self._dhw_allowed_modes
340
- self._count += 1
301
+ data = self._get_measurement_data(dev_id)
341
302
 
342
303
  # Check availability of non-legacy wired-connected devices
343
304
  if not self._smile_legacy:
344
- self._check_availability(device, device_data)
305
+ # Smartmeter
306
+ self._check_availability(
307
+ device, "smartmeter", data, "P1 does not seem to be connected"
308
+ )
309
+ # OpenTherm device
310
+ if device["name"] != "OnOff":
311
+ self._check_availability(
312
+ device, "heater_central", data, "no OpenTherm communication"
313
+ )
345
314
 
346
315
  # Switching groups data
347
- device_data = self._device_data_switching_group(device, device_data)
348
- # Specific, not generic Adam data
349
- device_data = self._device_data_adam(device, device_data)
350
- # No need to obtain thermostat data when the device is not a thermostat
316
+ self._device_data_switching_group(device, data)
317
+ # Adam data
318
+ self._device_data_adam(device, data)
319
+ # Skip obtaining data for non master-thermostats
351
320
  if device["dev_class"] not in ZONE_THERMOSTATS:
352
- return device_data
321
+ return data
353
322
 
354
323
  # Thermostat data (presets, temperatures etc)
355
- device_data = self._device_data_climate(device, device_data)
324
+ self._device_data_climate(device, data)
356
325
 
357
- return device_data
326
+ return data
358
327
 
359
328
 
360
329
  class Smile(SmileComm, SmileData):
@@ -539,14 +508,18 @@ class Smile(SmileComm, SmileData):
539
508
  if result.find(locator_2) is not None:
540
509
  self._elga = True
541
510
 
542
- async def _update_domain_objects(self) -> None:
543
- """Helper-function for smile.py: full_update_device() and async_update().
544
-
545
- Request domain_objects data.
546
- """
511
+ async def _full_update_device(self) -> None:
512
+ """Perform a first fetch of all XML data, needed for initialization."""
547
513
  self._domain_objects = await self._request(DOMAIN_OBJECTS)
514
+ self._get_plugwise_notifications()
515
+ self._locations = await self._request(LOCATIONS)
516
+ self._modules = await self._request(MODULES)
517
+ # P1 legacy has no appliances
518
+ if not (self.smile_type == "power" and self._smile_legacy):
519
+ self._appliances = await self._request(APPLIANCES)
548
520
 
549
- # If Plugwise notifications present:
521
+ def _get_plugwise_notifications(self) -> None:
522
+ """Collect the Plugwise notifications."""
550
523
  self._notifications = {}
551
524
  for notification in self._domain_objects.findall("./notification"):
552
525
  try:
@@ -561,15 +534,6 @@ class Smile(SmileComm, SmileData):
561
534
  f"{self._endpoint}{DOMAIN_OBJECTS}",
562
535
  )
563
536
 
564
- async def _full_update_device(self) -> None:
565
- """Perform a first fetch of all XML data, needed for initialization."""
566
- await self._update_domain_objects()
567
- self._locations = await self._request(LOCATIONS)
568
- self._modules = await self._request(MODULES)
569
- # P1 legacy has no appliances
570
- if not (self.smile_type == "power" and self._smile_legacy):
571
- self._appliances = await self._request(APPLIANCES)
572
-
573
537
  async def async_update(self) -> PlugwiseData:
574
538
  """Perform an incremental update for updating the various device states."""
575
539
  # Perform a full update at day-change
@@ -587,7 +551,8 @@ class Smile(SmileComm, SmileData):
587
551
  self.get_all_devices()
588
552
  # Otherwise perform an incremental update
589
553
  else:
590
- await self._update_domain_objects()
554
+ self._domain_objects = await self._request(DOMAIN_OBJECTS)
555
+ self._get_plugwise_notifications()
591
556
  match self._target_smile:
592
557
  case "smile_v2":
593
558
  self._modules = await self._request(MODULES)
@@ -701,7 +666,7 @@ class Smile(SmileComm, SmileData):
701
666
  template = (
702
667
  '<template tag="zone_preset_based_on_time_and_presence_with_override" />'
703
668
  )
704
- if not self.smile(ADAM):
669
+ if self.smile(ANNA):
705
670
  locator = f'.//*[@id="{schedule_rule_id}"]/template'
706
671
  template_id = self._domain_objects.find(locator).attrib["id"]
707
672
  template = f'<template id="{template_id}" />'
@@ -755,7 +720,7 @@ class Smile(SmileComm, SmileData):
755
720
  if "setpoint" in items:
756
721
  setpoint = items["setpoint"]
757
722
 
758
- if self._cooling_present and not self.smile(ADAM):
723
+ if self.smile(ANNA) and self._cooling_present:
759
724
  if "setpoint_high" not in items:
760
725
  raise PlugwiseError(
761
726
  "Plugwise: failed setting temperature: no valid input provided"
@@ -80,7 +80,7 @@ HW_MODELS: Final[dict[str, str]] = {
80
80
  MAX_SETPOINT: Final[float] = 30.0
81
81
  MIN_SETPOINT: Final[float] = 4.0
82
82
  NONE: Final = "None"
83
- OFF: Final = "Off"
83
+ OFF: Final = "off"
84
84
 
85
85
  # XML data paths
86
86
  APPLIANCES: Final = "/core/appliances"
@@ -257,12 +257,6 @@ BinarySensorType = Literal[
257
257
  ]
258
258
  BINARY_SENSORS: Final[tuple[str, ...]] = get_args(BinarySensorType)
259
259
 
260
- NumberType = Literal[
261
- "maximum_boiler_temperature",
262
- "max_dhw_temperature",
263
- "temperature_offset",
264
- ]
265
-
266
260
  LIMITS: Final[tuple[str, ...]] = (
267
261
  "offset",
268
262
  "setpoint",
@@ -271,17 +265,6 @@ LIMITS: Final[tuple[str, ...]] = (
271
265
  "upper_bound",
272
266
  )
273
267
 
274
- SelectType = Literal[
275
- "select_dhw_mode",
276
- "select_regulation_mode",
277
- "select_schedule",
278
- ]
279
- SelectOptionsType = Literal[
280
- "dhw_modes",
281
- "regulation_modes",
282
- "available_schedules",
283
- ]
284
-
285
268
  SensorType = Literal[
286
269
  "battery",
287
270
  "cooling_activation_outdoor_temperature",
@@ -505,6 +488,7 @@ class DeviceData(TypedDict, total=False):
505
488
  """The Device Data class, covering the collected and ordered output-data per device."""
506
489
 
507
490
  # Appliance base data
491
+ has_actuators: bool
508
492
  dev_class: str
509
493
  firmware: str | None
510
494
  hardware: str
@@ -570,15 +570,21 @@ class SmileHelper:
570
570
  # Legacy P1 has no more devices
571
571
  return
572
572
 
573
+ hc_count = 0
573
574
  for appliance in self._appliances.findall("./appliance"):
574
575
  appl = Munch()
575
576
  appl.pwclass = appliance.find("type").text
576
- # Skip thermostats that have this key, should be an orphaned device (Core #81712)
577
+ # Count amount of heater_central's
578
+ if appl.pwclass == "heater_central":
579
+ hc_count += 1
580
+ # Mark heater_central and thermostat that don't have actuator_functionalities,
581
+ # could be an orphaned device (Core #81712, #104433)
582
+ appl.has_actuators = True
577
583
  if (
578
- appl.pwclass == "thermostat"
584
+ appl.pwclass in ["heater_central", "thermostat"]
579
585
  and appliance.find("actuator_functionalities/") is None
580
586
  ):
581
- continue
587
+ appl.has_actuators = False
582
588
 
583
589
  appl.location = None
584
590
  if (appl_loc := appliance.find("location")) is not None:
@@ -622,6 +628,7 @@ class SmileHelper:
622
628
  for key, value in {
623
629
  "firmware": appl.firmware,
624
630
  "hardware": appl.hardware,
631
+ "has_actuators": appl.has_actuators,
625
632
  "location": appl.location,
626
633
  "mac_address": appl.mac,
627
634
  "model": appl.model,
@@ -634,6 +641,22 @@ class SmileHelper:
634
641
  self.gw_devices[appl.dev_id][appl_key] = value
635
642
  self._count += 1
636
643
 
644
+ # Remove thermostat with empty actuator_functionalities (Core #81712), remove heater_central
645
+ # with empty actuator_functionalities but only when there are more than one (Core #104433).
646
+ for dev_id, device in dict(self.gw_devices).items():
647
+ if device["dev_class"] == "thermostat" or (
648
+ device["dev_class"] == "heater_central" and hc_count > 1
649
+ ):
650
+ if not self.gw_devices[dev_id]["has_actuators"]:
651
+ self._count -= len(self.gw_devices[dev_id])
652
+ self.gw_devices.pop(dev_id)
653
+ else:
654
+ self.gw_devices[dev_id].pop("has_actuators")
655
+ self._count -= 1
656
+ elif "has_actuators" in self.gw_devices[dev_id]:
657
+ self.gw_devices[dev_id].pop("has_actuators")
658
+ self._count -= 1
659
+
637
660
  # For non-legacy P1 collect the connected SmartMeter info
638
661
  if self.smile_type == "power":
639
662
  self._p1_smartmeter_info_finder(appl)
@@ -1016,6 +1039,10 @@ class SmileHelper:
1016
1039
  measurements = DEVICE_MEASUREMENTS
1017
1040
  if self._is_thermostat and dev_id == self._heater_id:
1018
1041
  measurements = HEATER_CENTRAL_MEASUREMENTS
1042
+ # Show the allowed dhw_modes (Loria only)
1043
+ if self._dhw_allowed_modes:
1044
+ data["dhw_modes"] = self._dhw_allowed_modes
1045
+ # Counting of this item is done in _appliance_measurements()
1019
1046
 
1020
1047
  if (
1021
1048
  appliance := self._appliances.find(f'./appliance[@id="{dev_id}"]')
@@ -1032,8 +1059,18 @@ class SmileHelper:
1032
1059
  # Collect availability-status for wireless connected devices to Adam
1033
1060
  self._wireless_availablity(appliance, data)
1034
1061
 
1035
- if dev_id == self.gateway_id and self.smile(ADAM):
1036
- self._get_regulation_mode(appliance, data)
1062
+ if dev_id == self.gateway_id and self.smile(ADAM):
1063
+ self._get_regulation_mode(appliance, data)
1064
+
1065
+ # Adam & Anna: the Smile outdoor_temperature is present in DOMAIN_OBJECTS and LOCATIONS - under Home
1066
+ # The outdoor_temperature present in APPLIANCES is a local sensor connected to the active device
1067
+ if self._is_thermostat and dev_id == self.gateway_id:
1068
+ outdoor_temperature = self._object_value(
1069
+ self._home_location, "outdoor_temperature"
1070
+ )
1071
+ if outdoor_temperature is not None:
1072
+ data.update({"sensors": {"outdoor_temperature": outdoor_temperature}})
1073
+ self._count += 1
1037
1074
 
1038
1075
  if "c_heating_state" in data:
1039
1076
  self._process_c_heating_state(data)
@@ -1507,15 +1544,12 @@ class SmileHelper:
1507
1544
  Obtain the toggle state of a 'toggle' = switch.
1508
1545
  """
1509
1546
  if xml.find("type").text == "heater_central":
1510
- locator = "./actuator_functionalities/toggle_functionality"
1511
- if found := xml.findall(locator):
1512
- for item in found:
1513
- if (toggle_type := item.find("type")) is not None:
1514
- if toggle_type.text == toggle:
1515
- data["switches"][name] = item.find("state").text == "on"
1516
- self._count += 1
1517
- # Remove the cooling_enabled binary_sensor when the corresponding switch is present
1518
- # Except for Elga
1519
- if toggle == "cooling_enabled" and not self._elga:
1520
- data["binary_sensors"].pop("cooling_enabled")
1521
- self._count -= 1
1547
+ locator = f"./actuator_functionalities/toggle_functionality[type='{toggle}']/state"
1548
+ if (state := xml.find(locator)) is not None:
1549
+ data["switches"][name] = state.text == "on"
1550
+ self._count += 1
1551
+ # Remove the cooling_enabled binary_sensor when the corresponding switch is present
1552
+ # Except for Elga
1553
+ if toggle == "cooling_enabled" and not self._elga:
1554
+ data["binary_sensors"].pop("cooling_enabled")
1555
+ self._count -= 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 0.35.0
3
+ Version: 0.35.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plugwise"
7
- version = "0.35.0"
7
+ version = "0.35.2"
8
8
  license = {file = "LICENSE"}
9
9
  description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3."
10
10
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes