plugwise 1.7.4__py3-none-any.whl → 1.7.6__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
@@ -15,6 +15,8 @@ from plugwise.constants import (
15
15
  MODULES,
16
16
  NONE,
17
17
  SMILES,
18
+ STATE_OFF,
19
+ STATE_ON,
18
20
  STATUS,
19
21
  SYSTEM,
20
22
  GwEntityData,
@@ -398,10 +400,21 @@ class Smile(SmileComm):
398
400
 
399
401
  async def set_switch_state(
400
402
  self, appl_id: str, members: list[str] | None, model: str, state: str
401
- ) -> None:
402
- """Set the given State of the relevant Switch."""
403
+ ) -> bool:
404
+ """Set the given State of the relevant Switch.
405
+
406
+ Return the result:
407
+ - True when switched to state on,
408
+ - False when switched to state off,
409
+ - the unchanged state when the switch is for instance locked.
410
+ """
411
+ if state not in (STATE_OFF, STATE_ON):
412
+ raise PlugwiseError("Invalid state supplied to set_switch_state")
413
+
403
414
  try:
404
- await self._smile_api.set_switch_state(appl_id, members, model, state)
415
+ return await self._smile_api.set_switch_state(
416
+ appl_id, members, model, state
417
+ )
405
418
  except ConnectionFailedError as exc:
406
419
  raise ConnectionFailedError(
407
420
  f"Failed to set switch state: {str(exc)}"
plugwise/constants.py CHANGED
@@ -23,6 +23,8 @@ POWER_WATT: Final = "W"
23
23
  PRESET_AWAY: Final = "away"
24
24
  PRESSURE_BAR: Final = "bar"
25
25
  SIGNAL_STRENGTH_DECIBELS_MILLIWATT: Final = "dBm"
26
+ STATE_OFF: Final = "off"
27
+ STATE_ON: Final = "on"
26
28
  TEMP_CELSIUS: Final = "°C"
27
29
  TEMP_KELVIN: Final = "°K"
28
30
  TIME_MILLISECONDS: Final = "ms"
plugwise/legacy/smile.py CHANGED
@@ -18,6 +18,8 @@ from plugwise.constants import (
18
18
  OFF,
19
19
  REQUIRE_APPLIANCES,
20
20
  RULES,
21
+ STATE_OFF,
22
+ STATE_ON,
21
23
  GwEntityData,
22
24
  ThermoLoc,
23
25
  )
@@ -195,7 +197,7 @@ class SmileLegacyAPI(SmileLegacyData):
195
197
  Determined from - DOMAIN_OBJECTS.
196
198
  Used in HA Core to set the hvac_mode: in practice switch between schedule on - off.
197
199
  """
198
- if state not in ("on", "off"):
200
+ if state not in (STATE_OFF, STATE_ON):
199
201
  raise PlugwiseError("Plugwise: invalid schedule state.")
200
202
 
201
203
  # Handle no schedule-name / Off-schedule provided
@@ -214,7 +216,7 @@ class SmileLegacyAPI(SmileLegacyData):
214
216
  ) # pragma: no cover
215
217
 
216
218
  new_state = "false"
217
- if state == "on":
219
+ if state == STATE_ON:
218
220
  new_state = "true"
219
221
 
220
222
  locator = f'.//*[@id="{schedule_rule_id}"]/template'
@@ -234,13 +236,16 @@ class SmileLegacyAPI(SmileLegacyData):
234
236
 
235
237
  async def set_switch_state(
236
238
  self, appl_id: str, members: list[str] | None, model: str, state: str
237
- ) -> None:
239
+ ) -> bool:
238
240
  """Set the given state of the relevant switch.
239
241
 
240
242
  For individual switches, sets the state directly.
241
243
  For group switches, sets the state for each member in the group separately.
242
244
  For switch-locks, sets the lock state using a different data format.
245
+ Return the requested state when succesful, the current state otherwise.
243
246
  """
247
+ current_state = self.gw_entities[appl_id]["switches"]["relay"]
248
+ requested_state = state == STATE_ON
244
249
  switch = Munch()
245
250
  switch.actuator = "actuator_functionalities"
246
251
  switch.func_type = "relay_functionality"
@@ -250,7 +255,7 @@ class SmileLegacyAPI(SmileLegacyData):
250
255
 
251
256
  # Handle switch-lock
252
257
  if model == "lock":
253
- state = "false" if state == "off" else "true"
258
+ state = "true" if state == STATE_ON else "false"
254
259
  appliance = self._appliances.find(f'appliance[@id="{appl_id}"]')
255
260
  appl_name = appliance.find("name").text
256
261
  appl_type = appliance.find("type").text
@@ -269,37 +274,45 @@ class SmileLegacyAPI(SmileLegacyData):
269
274
  "</appliances>"
270
275
  )
271
276
  await self.call_request(APPLIANCES, method="post", data=data)
272
- return
277
+ return requested_state
273
278
 
274
279
  # Handle group of switches
275
280
  data = f"<{switch.func_type}><state>{state}</state></{switch.func_type}>"
276
281
  if members is not None:
277
282
  return await self._set_groupswitch_member_state(
278
- data, members, state, switch
283
+ appl_id, data, members, state, switch
279
284
  )
280
285
 
281
286
  # Handle individual relay switches
282
287
  uri = f"{APPLIANCES};id={appl_id}/relay"
283
- if model == "relay":
284
- locator = (
285
- f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}/lock'
286
- )
288
+ if model == "relay" and self.gw_entities[appl_id]["switches"]["lock"]:
287
289
  # Don't bother switching a relay when the corresponding lock-state is true
288
- if self._appliances.find(locator).text == "true":
289
- raise PlugwiseError("Plugwise: the locked Relay was not switched.")
290
+ return current_state
290
291
 
291
292
  await self.call_request(uri, method="put", data=data)
293
+ return requested_state
292
294
 
293
295
  async def _set_groupswitch_member_state(
294
- self, data: str, members: list[str], state: str, switch: Munch
295
- ) -> None:
296
+ self, appl_id: str, data: str, members: list[str], state: str, switch: Munch
297
+ ) -> bool:
296
298
  """Helper-function for set_switch_state().
297
299
 
298
- Set the given State of the relevant Switch (relay) within a group of members.
300
+ Set the requested state of the relevant switch within a group of switches.
301
+ Return the current group-state when none of the switches has changed its state, the requested state otherwise.
299
302
  """
303
+ current_state = self.gw_entities[appl_id]["switches"]["relay"]
304
+ requested_state = state == STATE_ON
305
+ switched = 0
300
306
  for member in members:
301
- uri = f"{APPLIANCES};id={member}/relay"
302
- await self.call_request(uri, method="put", data=data)
307
+ if not self.gw_entities[member]["switches"]["lock"]:
308
+ uri = f"{APPLIANCES};id={member}/relay"
309
+ await self.call_request(uri, method="put", data=data)
310
+ switched += 1
311
+
312
+ if switched > 0:
313
+ return requested_state
314
+
315
+ return current_state # pragma: no cover
303
316
 
304
317
  async def set_temperature(self, _: str, items: dict[str, float]) -> None:
305
318
  """Set the given Temperature on the relevant Thermostat."""
@@ -310,7 +323,7 @@ class SmileLegacyAPI(SmileLegacyData):
310
323
  if setpoint is None:
311
324
  raise PlugwiseError(
312
325
  "Plugwise: failed setting temperature: no valid input provided"
313
- ) # pragma: no cover"
326
+ ) # pragma: no cover
314
327
 
315
328
  temperature = str(setpoint)
316
329
  data = (
plugwise/smile.py CHANGED
@@ -7,7 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  from collections.abc import Awaitable, Callable
9
9
  import datetime as dt
10
- from typing import Any
10
+ from typing import Any, cast
11
11
 
12
12
  from plugwise.constants import (
13
13
  ADAM,
@@ -22,7 +22,10 @@ from plugwise.constants import (
22
22
  NOTIFICATIONS,
23
23
  OFF,
24
24
  RULES,
25
+ STATE_OFF,
26
+ STATE_ON,
25
27
  GwEntityData,
28
+ SwitchType,
26
29
  ThermoLoc,
27
30
  )
28
31
  from plugwise.data import SmileData
@@ -309,12 +312,12 @@ class SmileAPI(SmileData):
309
312
  Used in HA Core to set the hvac_mode: in practice switch between schedule on - off.
310
313
  """
311
314
  # Input checking
312
- if new_state not in ("on", "off"):
315
+ if new_state not in (STATE_OFF, STATE_ON):
313
316
  raise PlugwiseError("Plugwise: invalid schedule state.")
314
317
 
315
318
  # Translate selection of Off-schedule-option to disabling the active schedule
316
319
  if name == OFF:
317
- new_state = "off"
320
+ new_state = STATE_OFF
318
321
 
319
322
  # Handle no schedule-name / Off-schedule provided
320
323
  if name is None or name == OFF:
@@ -367,18 +370,27 @@ class SmileAPI(SmileData):
367
370
  subject = f'<context><zone><location id="{loc_id}" /></zone></context>'
368
371
  subject = etree.fromstring(subject)
369
372
 
370
- if state == "off":
373
+ if state == STATE_OFF:
371
374
  self._last_active[loc_id] = name
372
375
  contexts.remove(subject)
373
- if state == "on":
376
+ if state == STATE_ON:
374
377
  contexts.append(subject)
375
378
 
376
379
  return str(etree.tostring(contexts, encoding="unicode").rstrip())
377
380
 
378
381
  async def set_switch_state(
379
382
  self, appl_id: str, members: list[str] | None, model: str, state: str
380
- ) -> None:
381
- """Set the given State of the relevant Switch."""
383
+ ) -> bool:
384
+ """Set the given state of the relevant Switch.
385
+
386
+ For individual switches, sets the state directly.
387
+ For group switches, sets the state for each member in the group separately.
388
+ For switch-locks, sets the lock state using a different data format.
389
+ Return the requested state when succesful, the current state otherwise.
390
+ """
391
+ model_type = cast(SwitchType, model)
392
+ current_state = self.gw_entities[appl_id]["switches"][model_type]
393
+ requested_state = state == STATE_ON
382
394
  switch = Munch()
383
395
  switch.actuator = "actuator_functionalities"
384
396
  switch.device = "relay"
@@ -396,10 +408,18 @@ class SmileAPI(SmileData):
396
408
 
397
409
  if model == "lock":
398
410
  switch.func = "lock"
399
- state = "false" if state == "off" else "true"
411
+ state = "true" if state == STATE_ON else "false"
412
+
413
+ data = (
414
+ f"<{switch.func_type}>"
415
+ f"<{switch.func}>{state}</{switch.func}>"
416
+ f"</{switch.func_type}>"
417
+ )
400
418
 
401
419
  if members is not None:
402
- return await self._set_groupswitch_member_state(members, state, switch)
420
+ return await self._set_groupswitch_member_state(
421
+ appl_id, data, members, state, switch
422
+ )
403
423
 
404
424
  locator = f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}'
405
425
  found = self._domain_objects.findall(locator)
@@ -412,39 +432,42 @@ class SmileAPI(SmileData):
412
432
  else: # actuators with a single item like relay_functionality
413
433
  switch_id = item.attrib["id"]
414
434
 
415
- data = (
416
- f"<{switch.func_type}>"
417
- f"<{switch.func}>{state}</{switch.func}>"
418
- f"</{switch.func_type}>"
419
- )
420
435
  uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}"
421
436
  if model == "relay":
422
- locator = (
423
- f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}/lock'
424
- )
425
- # Don't bother switching a relay when the corresponding lock-state is true
426
- if self._domain_objects.find(locator).text == "true":
427
- raise PlugwiseError("Plugwise: the locked Relay was not switched.")
437
+ lock_blocked = self.gw_entities[appl_id]["switches"].get("lock")
438
+ if lock_blocked or lock_blocked is None:
439
+ # Don't switch a relay when its corresponding lock-state is true or no
440
+ # lock is present. That means the relay can't be controlled by the user.
441
+ return current_state
428
442
 
429
443
  await self.call_request(uri, method="put", data=data)
444
+ return requested_state
430
445
 
431
446
  async def _set_groupswitch_member_state(
432
- self, members: list[str], state: str, switch: Munch
433
- ) -> None:
447
+ self, appl_id: str, data: str, members: list[str], state: str, switch: Munch
448
+ ) -> bool:
434
449
  """Helper-function for set_switch_state().
435
450
 
436
- Set the given State of the relevant Switch within a group of members.
451
+ Set the requested state of the relevant switch within a group of switches.
452
+ Return the current group-state when none of the switches has changed its state, the requested state otherwise.
437
453
  """
454
+ current_state = self.gw_entities[appl_id]["switches"]["relay"]
455
+ requested_state = state == STATE_ON
456
+ switched = 0
438
457
  for member in members:
439
458
  locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}'
440
459
  switch_id = self._domain_objects.find(locator).attrib["id"]
441
460
  uri = f"{APPLIANCES};id={member}/{switch.device};id={switch_id}"
442
- data = (
443
- f"<{switch.func_type}>"
444
- f"<{switch.func}>{state}</{switch.func}>"
445
- f"</{switch.func_type}>"
446
- )
447
- await self.call_request(uri, method="put", data=data)
461
+ lock_blocked = self.gw_entities[member]["switches"].get("lock")
462
+ # Assume Plugs under Plugwise control are not part of a group
463
+ if lock_blocked is not None and not lock_blocked:
464
+ await self.call_request(uri, method="put", data=data)
465
+ switched += 1
466
+
467
+ if switched > 0:
468
+ return requested_state
469
+
470
+ return current_state
448
471
 
449
472
  async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
450
473
  """Set the given Temperature on the relevant Thermostat."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugwise
3
- Version: 1.7.4
3
+ Version: 1.7.6
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Author: Plugwise device owners
6
6
  Maintainer: bouwew, CoMPaTech
@@ -11,10 +11,9 @@ Keywords: home,automation,plugwise,module
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
16
15
  Classifier: Topic :: Home Automation
17
- Requires-Python: >=3.12.0
16
+ Requires-Python: >=3.13
18
17
  Description-Content-Type: text/markdown
19
18
  License-File: LICENSE
20
19
  Requires-Dist: aiohttp
@@ -1,18 +1,18 @@
1
- plugwise/__init__.py,sha256=BKT3BagtOOy2Efk2m8uQLFPE4vizEjjPMj61Zs7MwMw,17492
1
+ plugwise/__init__.py,sha256=yLUc573EjJTvT4ZyLKbILPYgKpPZ9oqR4DQYev-7UWI,17883
2
2
  plugwise/common.py,sha256=_O7cC7fPGHZkrxQTaK2y8_trr9lOjEtgbS6plOfp2jk,9589
3
- plugwise/constants.py,sha256=zJFm0J14PJWasKSvepriOc6mYLljRVGNGAF5QDtzyl0,16869
3
+ plugwise/constants.py,sha256=eivym_wEqoysjPBDKG-9gbrUdG3MuCLxxx3KEHjDO7I,16917
4
4
  plugwise/data.py,sha256=OxZufaAmnyVDkRGJBdk6tf-I65FaCwcT69scNsZnmiA,12490
5
5
  plugwise/exceptions.py,sha256=Ce-tO9uNsMB-8FP6VAxBvsHNJ-NIM9F0onUZOdZI4Ys,1110
6
6
  plugwise/helper.py,sha256=AdQ4Tno5zheFI5y5A-YovVGYW_islxCxkt0B77TfC-o,39701
7
7
  plugwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- plugwise/smile.py,sha256=WPZ0v45RpgJxBWEy_Sy_vsX87V4UPaVkkSgY7yZouAQ,19550
8
+ plugwise/smile.py,sha256=lHlaqvGAVbyy3Qnds1U_pv1piJutF5_a6P8ZdgGSvHA,20601
9
9
  plugwise/smilecomm.py,sha256=DRQ3toRNEf3oo_mej49fPJ47m5das-jvo-8GnIrSPzw,5208
10
10
  plugwise/util.py,sha256=rMcqfaB4dkQEZFJY-bBJISmlYgTnb6Ns3-Doxelf92Q,10689
11
11
  plugwise/legacy/data.py,sha256=Z-7nw21s9-L4DcwPCZ_yoRGeI_fWBS1Q48kHmyh2pkY,3145
12
12
  plugwise/legacy/helper.py,sha256=cDi8zvUtoCqxVrLksqjlCHUmjXjulXCf-l9fMjFPOfs,17417
13
- plugwise/legacy/smile.py,sha256=Q133Ub5W6VB1MyxzAZze3cFyXvTxIeIbCxSUP4eXNXM,12867
14
- plugwise-1.7.4.dist-info/licenses/LICENSE,sha256=mL22BjmXtg_wnoDnnaqps5_Bg_VGj_yHueX5lsKwbCc,1144
15
- plugwise-1.7.4.dist-info/METADATA,sha256=zmepto8Ge4w2XtJWEa6SDvF_lRANPs3XxVheTYX5Mbk,7932
16
- plugwise-1.7.4.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
17
- plugwise-1.7.4.dist-info/top_level.txt,sha256=MYOmktMFf8ZmX6_OE1y9MoCZFfY-L8DA0F2tA2IvE4s,9
18
- plugwise-1.7.4.dist-info/RECORD,,
13
+ plugwise/legacy/smile.py,sha256=2GyKn0yAcg-GdVHvyoe5LfPzs0WAlIRwHWc8bhRg6DQ,13452
14
+ plugwise-1.7.6.dist-info/licenses/LICENSE,sha256=mL22BjmXtg_wnoDnnaqps5_Bg_VGj_yHueX5lsKwbCc,1144
15
+ plugwise-1.7.6.dist-info/METADATA,sha256=XabqKhZBkpv6YfSQ52kWubwR1k-dapgFRZYvc3fiupY,7879
16
+ plugwise-1.7.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ plugwise-1.7.6.dist-info/top_level.txt,sha256=MYOmktMFf8ZmX6_OE1y9MoCZFfY-L8DA0F2tA2IvE4s,9
18
+ plugwise-1.7.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5