From 67ba23e987e0d1be0c7b615a08db8c70dbab860a Mon Sep 17 00:00:00 2001 From: rgc99 Date: Sun, 26 Nov 2023 02:43:30 +0000 Subject: [PATCH] Make master a slave --- .../irrigation_unlimited.py | 71 +++++++------------ tests/iu_test_support.py | 38 ++++++++-- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/custom_components/irrigation_unlimited/irrigation_unlimited.py b/custom_components/irrigation_unlimited/irrigation_unlimited.py index 8683f0a..01c94d5 100644 --- a/custom_components/irrigation_unlimited/irrigation_unlimited.py +++ b/custom_components/irrigation_unlimited/irrigation_unlimited.py @@ -1105,6 +1105,15 @@ def __init__( self._remaining_time: timedelta = self._end_time - self._start_time self._percent_complete: int = 0 self._status = self._get_status(stime) + self.master_ref: weakref.ReferenceType["IURun"] = None + + @property + def master_obj(self) -> "IURun": + """Return the associated master run""" + # pylint: disable=not-callable + if self.master_ref is not None and self.master_ref() is not None: + return self.master_ref() + return None @property def expired(self) -> bool: @@ -1450,6 +1459,9 @@ def pop_run(self, index) -> "IURun": self._next_run = None if run == self._next_run: self._next_run = None + if (obj := run.master_obj) is not None: + run.zone.controller.runs.remove_run(obj) + run.master_ref = None return run def remove_run(self, run: IURun) -> "IURun": @@ -2171,30 +2183,12 @@ def clear_runs(self) -> bool: # pylint: disable=arguments-differ return super().clear_runs(True) - def find_run( - self, - start_time: datetime, - zone_run: IURun, - ) -> IURun: - """Find the specified run in the queue""" - for run in self: - if ( - start_time == run.start_time - and zone_run.zone == run.zone - and run.schedule is not None - and zone_run.schedule is not None - and run.schedule == zone_run.schedule - ): - return run - return None - def add_zone( self, stime: datetime, zone_run: IURun, preamble: timedelta, postamble: timedelta, - no_find: bool, ) -> IURun: """Add a new master run to the queue""" start_time = zone_run.start_time @@ -2204,20 +2198,15 @@ def add_zone( duration += preamble if postamble is not None: duration += postamble - if not no_find: - run = self.find_run(start_time, zone_run) - else: - run = None - if run is None: - run = self.add( - stime, - start_time, - duration, - zone_run.zone, - zone_run.schedule, - zone_run.sequence_run, - zone_run, - ) + run = self.add( + stime, + start_time, + duration, + zone_run.zone, + zone_run.schedule, + zone_run.sequence_run, + zone_run, + ) return run def rebuild_schedule( @@ -2226,19 +2215,17 @@ def rebuild_schedule( zones: list[IUZone], preamble: timedelta, postamble: timedelta, - clear_all: bool, ) -> IURQStatus: """Create a superset of all the zones.""" # pylint: disable=too-many-arguments + status = IURQStatus(0) - if clear_all: - self.clear_all() - else: - self.clear_runs() for zone in zones: for run in zone.runs: - if not run.expired: - self.add_zone(stime, run, preamble, postamble, clear_all) + if run.master_obj is None: + run.master_ref = weakref.ref( + self.add_zone(stime, run, preamble, postamble) + ) status |= IURQStatus.EXTENDED | IURQStatus.REDUCED return status @@ -3913,9 +3900,8 @@ def muster(self, stime: datetime, force: bool) -> IURQStatus: | IURQStatus.CANCELED | IURQStatus.CHANGED ): - clear_all = zone_status.has_any(IURQStatus.CLEARED | IURQStatus.CANCELED) status |= self._run_queue.rebuild_schedule( - stime, self._zones, self._preamble, self._postamble, clear_all + stime, self._zones, self._preamble, self._postamble ) status |= self._run_queue.update_queue() @@ -4171,7 +4157,6 @@ def s2b(test: bool, service: str) -> bool: sequence.runs.clear_runs() sequences_changed = True if sequences_changed: - self._run_queue.clear_runs() self.request_update(True) result = True return result @@ -4205,7 +4190,6 @@ def service_suspend(self, data: MappingProxyType, stime: datetime) -> bool: sequence.runs.clear_runs() sequences_changed = True if sequences_changed: - self._run_queue.clear_runs() self.request_update(True) result = True return result @@ -4236,7 +4220,6 @@ def service_adjust_time(self, data: MappingProxyType, stime: datetime) -> bool: sequence.runs.clear_runs() sequences_changed = True if sequences_changed: - self._run_queue.clear_runs() self.request_update(True) result = True return result diff --git a/tests/iu_test_support.py b/tests/iu_test_support.py index 7267cb4..611aac8 100644 --- a/tests/iu_test_support.py +++ b/tests/iu_test_support.py @@ -337,6 +337,14 @@ def check_summary(self, config_file: str = None) -> None: def check_labyrinth(self) -> None: """Check the run integrity between controller, zone and sequence runs""" + def print_run(run: IURun) -> None: + schedule = run.schedule.name if run.schedule else "manual" + print( + f"zone: {run.zone.id1}, " + f"start: {fmt_local(run.start_time)}, " + f"schedule: {schedule}" + ) + def check_controller(controller: IUController) -> None: lst = list(controller.runs) controller_runs: set[IURun] = set(lst) @@ -365,14 +373,32 @@ def check_controller(controller: IUController) -> None: sqr.symmetric_difference(sequence_runs), key=lambda run: run.start_time, ): - schedule = run.schedule.name if run.schedule else "manual" - print( - f"zone: {run.zone.id1}, " - f"start: {fmt_local(run.start_time)}, " - f"schedule: {schedule}" - ) + print_run(run) assert False, "Zone and sequence runs not identical" + referred = set( + run.master_obj for run in zone_runs if run.master_obj is not None + ) + if controller_runs != referred: + for run in sorted( + referred.symmetric_difference(controller_runs), + key=lambda run: run.start_time, + ): + print_run(run) + assert False, "Controller and referred zones runs not identical" + + referrer = set(run for run in zone_runs if run.master_obj is not None) + if zone_runs != referrer: + for run in sorted( + referrer.symmetric_difference(zone_runs), + key=lambda run: run.start_time, + ): + print_run(run) + + assert len(zone_runs) == len( + controller_runs + ), f"Controller ({len(controller_runs)}) and zones ({len(zone_runs)}) not identical" + for controller in self._coordinator.controllers: check_controller(controller)