Skip to content

Commit 3dd985c

Browse files
committed
Improve update event handling efficiency
1 parent c123048 commit 3dd985c

File tree

1 file changed

+90
-60
lines changed

1 file changed

+90
-60
lines changed

home_connect_async/appliance.py

Lines changed: 90 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class Option():
6969
max:Optional[int] = None
7070
stepsize:Optional[int] = None
7171
allowedvalues:Optional[list[str]] = None
72-
displayallowedvalues:Optional[list[str]] = None
72+
allowedvaluesdisplay:Optional[list[str]] = None
7373
execution:Optional[str] = None
7474
liveupdate:Optional[bool] = None
7575
default:Optional[str] = None
@@ -92,7 +92,7 @@ def create(cls, data:dict):
9292
option.max = constraints.get('max')
9393
option.stepsize = constraints.get('stepsize')
9494
option.allowedvalues = constraints.get('allowedvalues')
95-
option.displayallowedvalues = constraints.get('displayvalues')
95+
option.allowedvaluesdisplay = constraints.get('displayvalues')
9696
option.execution = constraints.get('execution')
9797
option.liveupdate = constraints.get('liveupdate')
9898
option.default = constraints.get('default')
@@ -485,46 +485,9 @@ async def async_update_data(self, data:dict) -> None:
485485
await self.async_fetch_data()
486486
await self._callbacks.async_broadcast_event(self, Events.PAIRED)
487487
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
488-
else:
489-
# update options, statuses and settings in the data model
490-
if self.selected_program and self.selected_program.options and key in self.selected_program.options:
491-
self.selected_program.options[key].value = value
492-
self.selected_program.options[key].name = data.get("name")
493-
self.selected_program.options[key].displayvalue = data.get("displayvalue")
494-
elif "programs/selected" in uri:
495-
_LOGGER.debug("Got event for unknown property: %s", data)
496-
self.active_program = await self._async_fetch_programs("selected")
497-
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
498-
499-
if self.active_program and self.active_program.options and key in self.active_program.options:
500-
self.active_program.options[key].value = value
501-
self.active_program.options[key].name = data.get("name")
502-
self.active_program.options[key].displayvalue = data.get("displayvalue")
503-
elif "programs/active" in uri:
504-
_LOGGER.debug("Got event for unknown property: %s", data)
505-
self.active_program = await self._async_fetch_programs("active")
506-
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
507-
508-
if key in self.status:
509-
self.status[key].value = value
510-
self.status[key].name = data.get("name")
511-
self.status[key].displayvalue = data.get("displayvalue")
512-
elif "/status/" in uri:
513-
_LOGGER.debug("Got event for unknown property: %s", data)
514-
self.status = await self._async_fetch_status()
515-
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
516-
517-
if key in self.settings:
518-
self.settings[key].value = value
519-
self.settings[key].name = data.get("name")
520-
self.settings[key].displayvalue = data.get("displayvalue")
521-
elif "/settings/" in uri:
522-
_LOGGER.debug("Got event for unknown property: %s", data)
523-
self.settings = await self._async_fetch_settings()
524-
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
525488

526489
# Fetch data from the API on major events
527-
if key == "BSH.Common.Root.SelectedProgram" and (not self.selected_program or self.selected_program.key != value):
490+
elif key == "BSH.Common.Root.SelectedProgram" and (not self.selected_program or self.selected_program.key != value):
528491
# handle selected program
529492
async with Synchronization.selected_program_lock:
530493
# Have to check again after aquiring the lock
@@ -551,7 +514,7 @@ async def async_update_data(self, data:dict) -> None:
551514
# apparently it is possible to get progress notifications without getting the ActiveProgram event first so we handle that
552515
(key in ["BSH.Common.Option.ProgramProgress", "BSH.Common.Option.RemainingProgramTime"]) or
553516
# it is also possible to get operation state Run without getting the ActiveProgram event
554-
(key == "BSH.Common.Status.OperationState" and value=="BSH.Common.EnumType.OperationState.Run")
517+
(key == "BSH.Common.Status.OperationState" and value in ["BSH.Common.EnumType.OperationState.Run", "BSH.Common.EnumType.OperationState.DelayedStart"])
555518
) and \
556519
(not self.active_program or (key == "BSH.Common.Root.ActiveProgram" and self.active_program.key != value) ) and \
557520
self._active_program_fail_count < 3 :
@@ -560,53 +523,120 @@ async def async_update_data(self, data:dict) -> None:
560523
if self.active_program:
561524
self._active_program_fail_count = 0
562525
else:
563-
# This is a workaround to prevent rate limiting when receiving progress events but avaialable_programs returns 404
526+
# This is a workaround to prevent rate limiting when receiving progress events but active_program returns 404
564527
self._active_program_fail_count += 1
565528
self.available_programs = await self._async_fetch_programs("available")
566-
self.settings = await self._async_fetch_settings()
529+
if not self.settings:
530+
self.settings = await self._async_fetch_settings()
567531
self.commands = await self._async_fetch_commands()
568532
await self._callbacks.async_broadcast_event(self, Events.PROGRAM_STARTED, value)
569533
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
570534

571535
elif ( (key == "BSH.Common.Root.ActiveProgram" and not value) or
572-
(key == "BSH.Common.Status.OperationState" and value in ["BSH.Common.EnumType.OperationState.Ready", "BSH.Common.EnumType.OperationState.Finished"]) or
536+
(key == "BSH.Common.Status.OperationState" and value == "BSH.Common.EnumType.OperationState.Finished") or
573537
(key == "BSH.Common.Event.ProgramFinished")
574538
) and self.active_program:
575539
# handle program end
540+
541+
# NOTE: Depending on the received event there may still be an active program provided by the API
542+
# This creates an inconsistency of HA is restarted while the appliance is in this state
543+
# however, it still seems bettert than relying on the order of received events which is very inconsistent
544+
576545
prev_prog = self.active_program.key if self.active_program else None
577546
self.active_program = None
578547
self._active_program_fail_count = 0
579-
self.settings = await self._async_fetch_settings()
580548
self.commands = await self._async_fetch_commands()
581-
self.available_programs = await self._async_fetch_programs("available")
549+
# TODO: should self.available_programs = None ????
582550
await self._callbacks.async_broadcast_event(self, Events.PROGRAM_FINISHED, prev_prog)
583551
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
584552

585-
elif key == "BSH.Common.Status.OperationState" and \
586-
value!="BSH.Common.EnumType.OperationState.Run" and \
587-
self.status.get("BSH.Common.Status.OperationState") != value: # ignore repeat notifiations of the same state
588-
self.active_program = await self._async_fetch_programs("active")
553+
elif key == "BSH.Common.Status.OperationState" and value == "BSH.Common.EnumType.OperationState.Ready" \
554+
and self.status.get("BSH.Common.Status.OperationState", {"value": None}).value != value: # ignore repeating events
555+
prev_prog = self.active_program.key if self.active_program else None
556+
self.active_program = None
557+
self._active_program_fail_count = 0
589558
self.selected_program = await self._async_fetch_programs("selected")
590559
self.available_programs = await self._async_fetch_programs("available")
591-
self.settings = await self._async_fetch_settings()
592560
self.commands = await self._async_fetch_commands()
561+
if not self.settings:
562+
self.settings = await self._async_fetch_settings()
563+
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
564+
if prev_prog:
565+
await self._callbacks.async_broadcast_event(self, Events.PROGRAM_FINISHED, prev_prog)
566+
567+
elif key == "BSH.Common.Status.OperationState" and value == "BSH.Common.EnumType.OperationState.Inactive" \
568+
and self.status.get("BSH.Common.Status.OperationState", {"value": None}).value != value:
569+
self.active_program = None
570+
self._active_program_fail_count = 0
571+
self.selected_program = None
572+
self.available_programs = None
573+
self.commands = {}
593574
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
594575

576+
elif key == "BSH.Common.Status.OperationState" and value == "BSH.Common.EnumType.OperationState.Pause" \
577+
and self.status.get("BSH.Common.Status.OperationState", {"value": None}).value != value:
578+
self.commands = await self._async_fetch_commands()
579+
580+
elif key == "BSH.Common.Status.OperationState" \
581+
and value in [ "BSH.Common.EnumType.OperationState.ActionRequired", "BSH.Common.EnumType.OperationState.Error", "BSH.Common.EnumType.OperationState.Aborting" ] \
582+
and self.status.get("BSH.Common.Status.OperationState", {"value": None}).value != value:
583+
_LOGGER.debug("The appliance entered and error operation state: %s", data)
584+
595585
elif key =="BSH.Common.Status.RemoteControlStartAllowed":
596586
self.available_programs = await self._async_fetch_programs("available")
587+
self.commands = await self._async_fetch_commands()
597588
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
598589

599-
elif ( not self.available_programs or len(self.available_programs) < 2) and \
600-
( key in ["BSH.Common.Status.OperationState", "BSH.Common.Status.RemoteControlActive"] ) and \
601-
( "BSH.Common.Status.OperationState" not in self.status or self.status["BSH.Common.Status.OperationState"].value == "BSH.Common.EnumType.OperationState.Ready" ) and \
602-
( "BSH.Common.Status.RemoteControlActive" not in self.status or self.status["BSH.Common.Status.RemoteControlActive"].value):
603-
# Handle cases were the appliance data was loaded without getting all the programs (for example when HA is restarted while a program is active)
604-
# If the state is Ready and remote control is possible and we didn't load the available programs before then load them now
605-
available_programs = await self._async_fetch_programs("available")
606-
self.available_programs = available_programs
607-
await self._callbacks.async_broadcast_event(self, Events.PAIRED)
590+
# elif ( not self.available_programs or len(self.available_programs) < 2) and \
591+
# ( key in ["BSH.Common.Status.OperationState", "BSH.Common.Status.RemoteControlActive"] ) and \
592+
# ( "BSH.Common.Status.OperationState" not in self.status or self.status["BSH.Common.Status.OperationState"].value == "BSH.Common.EnumType.OperationState.Ready" ) and \
593+
# ( "BSH.Common.Status.RemoteControlActive" not in self.status or self.status["BSH.Common.Status.RemoteControlActive"].value):
594+
# # Handle cases were the appliance data was loaded without getting all the programs (for example when HA is restarted while a program is active)
595+
# # If the state is Ready and remote control is possible and we didn't load the available programs before then load them now
596+
# available_programs = await self._async_fetch_programs("available")
597+
# self.available_programs = available_programs
598+
# await self._callbacks.async_broadcast_event(self, Events.PAIRED)
599+
# await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
600+
601+
if self.selected_program and self.selected_program.options and key in self.selected_program.options:
602+
self.selected_program.options[key].value = value
603+
self.selected_program.options[key].name = data.get("name")
604+
self.selected_program.options[key].displayvalue = data.get("displayvalue")
605+
elif "programs/selected" in uri and key != "BSH.Common.Root.SelectedProgram":
606+
_LOGGER.debug("Got event for unknown property: %s", data)
607+
self.active_program = await self._async_fetch_programs("selected")
608608
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
609609

610+
if self.active_program and self.active_program.options and key in self.active_program.options:
611+
self.active_program.options[key].value = value
612+
self.active_program.options[key].name = data.get("name")
613+
self.active_program.options[key].displayvalue = data.get("displayvalue")
614+
elif ( "programs/active" in uri and key != "BSH.Common.Root.ActiveProgram"
615+
# ignore late active program events coming after the program has finished
616+
# this implies that an unknown event will not triger the first active program fetch, which should be fine
617+
and self.active_program
618+
):
619+
_LOGGER.debug("Got event for unknown property: %s", data)
620+
self.active_program = await self._async_fetch_programs("active")
621+
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
622+
623+
if key in self.status:
624+
self.status[key].value = value
625+
self.status[key].name = data.get("name")
626+
self.status[key].displayvalue = data.get("displayvalue")
627+
elif "/status/" in uri:
628+
_LOGGER.debug("Got event for unknown property: %s", data)
629+
self.status = await self._async_fetch_status()
630+
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
631+
632+
if key in self.settings:
633+
self.settings[key].value = value
634+
self.settings[key].name = data.get("name")
635+
self.settings[key].displayvalue = data.get("displayvalue")
636+
elif "/settings/" in uri:
637+
_LOGGER.debug("Got event for unknown property: %s", data)
638+
self.settings = await self._async_fetch_settings()
639+
await self._callbacks.async_broadcast_event(self, Events.DATA_CHANGED)
610640
# broadcast the specific event that was received
611641
await self._callbacks.async_broadcast_event(self, key, value)
612642

0 commit comments

Comments
 (0)