77
88from collections .abc import Awaitable , Callable
99import datetime as dt
10- from typing import Any
10+ from typing import Any , cast
1111
1212from plugwise .constants import (
1313 ADAM ,
2222 NOTIFICATIONS ,
2323 OFF ,
2424 RULES ,
25+ STATE_OFF ,
26+ STATE_ON ,
2527 GwEntityData ,
28+ SwitchType ,
2629 ThermoLoc ,
2730)
2831from plugwise .data import SmileData
@@ -309,12 +312,12 @@ async def set_schedule_state(
309312 Used in HA Core to set the hvac_mode: in practice switch between schedule on - off.
310313 """
311314 # Input checking
312- if new_state not in ("on" , "off" ):
315+ if new_state not in (STATE_OFF , STATE_ON ):
313316 raise PlugwiseError ("Plugwise: invalid schedule state." )
314317
315318 # Translate selection of Off-schedule-option to disabling the active schedule
316319 if name == OFF :
317- new_state = "off"
320+ new_state = STATE_OFF
318321
319322 # Handle no schedule-name / Off-schedule provided
320323 if name is None or name == OFF :
@@ -367,18 +370,27 @@ def determine_contexts(
367370 subject = f'<context><zone><location id="{ loc_id } " /></zone></context>'
368371 subject = etree .fromstring (subject )
369372
370- if state == "off" :
373+ if state == STATE_OFF :
371374 self ._last_active [loc_id ] = name
372375 contexts .remove (subject )
373- if state == "on" :
376+ if state == STATE_ON :
374377 contexts .append (subject )
375378
376379 return str (etree .tostring (contexts , encoding = "unicode" ).rstrip ())
377380
378381 async def set_switch_state (
379382 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
382394 switch = Munch ()
383395 switch .actuator = "actuator_functionalities"
384396 switch .device = "relay"
@@ -396,10 +408,18 @@ async def set_switch_state(
396408
397409 if model == "lock" :
398410 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+ )
400418
401419 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+ )
403423
404424 locator = f'appliance[@id="{ appl_id } "]/{ switch .actuator } /{ switch .func_type } '
405425 found = self ._domain_objects .findall (locator )
@@ -412,39 +432,42 @@ async def set_switch_state(
412432 else : # actuators with a single item like relay_functionality
413433 switch_id = item .attrib ["id" ]
414434
415- data = (
416- f"<{ switch .func_type } >"
417- f"<{ switch .func } >{ state } </{ switch .func } >"
418- f"</{ switch .func_type } >"
419- )
420435 uri = f"{ APPLIANCES } ;id={ appl_id } /{ switch .device } ;id={ switch_id } "
421436 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
428442
429443 await self .call_request (uri , method = "put" , data = data )
444+ return requested_state
430445
431446 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 :
434449 """Helper-function for set_switch_state().
435450
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.
437453 """
454+ current_state = self .gw_entities [appl_id ]["switches" ]["relay" ]
455+ requested_state = state == STATE_ON
456+ switched = 0
438457 for member in members :
439458 locator = f'appliance[@id="{ member } "]/{ switch .actuator } /{ switch .func_type } '
440459 switch_id = self ._domain_objects .find (locator ).attrib ["id" ]
441460 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
448471
449472 async def set_temperature (self , loc_id : str , items : dict [str , float ]) -> None :
450473 """Set the given Temperature on the relevant Thermostat."""
0 commit comments