diff --git a/pyopensprinkler/__init__.py b/pyopensprinkler/__init__.py index bb59101..c7771b5 100644 --- a/pyopensprinkler/__init__.py +++ b/pyopensprinkler/__init__.py @@ -93,8 +93,10 @@ def __init__(self, url, password, opts=None): self._opts = opts self._programs = {} self._stations = {} + self._previous_state = None self._state = None self._last_refresh_time = None + self._program_observers = [] self._skip_all_endpoint = os.environ.get( "PYOPENSPRINKLER_SKIP_ALL_ENDPOINT", None ) @@ -194,12 +196,67 @@ def request_http(self, url): def refresh(self): """Refresh programs and stations""" self._refresh_state() - self._last_refresh_time = int(round(datetime.datetime.now().timestamp())) + # trigger program removed + if self._previous_state is not None: + for i, _ in enumerate(self._previous_state["programs"]["pd"]): + # index no longer exists + # names no longer match + if (i >= len(self._state["programs"]["pd"])) or ( + (i < len(self._state["programs"]["pd"])) + and self._previous_state["programs"]["pd"][i][5] + != self._state["programs"]["pd"][i][5] + ): + # invoke removal callback on current instance + for observer in self.programs[i]._on_removed_observers: + observer(self.programs[i]) + + # call observers + for observer in self._program_observers: + observer("removed", self.programs[i]) + + # make controller current for i, _ in enumerate(self._state["programs"]["pd"]): if i not in self._programs: self._programs[i] = Program(self, i) - + else: + # check same name + if ( + (i < len(self._previous_state["programs"]["pd"])) + and self._previous_state["programs"]["pd"][i][5] + != self._state["programs"]["pd"][i][5] + ): + self._programs[i] = Program(self, i) + + # remove any dangling programs + keys = list(self.programs.keys()) + for i in keys: + try: + self._state["programs"]["pd"][i] + except IndexError: + del self._programs[i] + + # trigger program added + if self._previous_state is not None: + for i, _ in enumerate(self._state["programs"]["pd"]): + # index is new + # names no longer match + if (i >= len(self._previous_state["programs"]["pd"])) or ( + (i < len(self._previous_state["programs"]["pd"])) + and self._previous_state["programs"]["pd"][i][5] + != self._state["programs"]["pd"][i][5] + ): + # call observers + for observer in self._program_observers: + observer("added", self.programs[i]) + else: + # all are newly added + for i, _ in enumerate(self._state["programs"]["pd"]): + # call observers + for observer in self._program_observers: + observer("added", self.programs[i]) + + # setup stations for i, _ in enumerate(self._state["stations"]["snames"]): if i not in self._stations: self._stations[i] = Station(self, i) @@ -212,7 +269,7 @@ def _refresh_state(self): if use_ja: try: (_, content) = self.request("/ja") - self._state = content + self._set_state(content) return except OpenSprinklerApiError as exc: (_, err_code) = exc.args @@ -237,7 +294,12 @@ def _refresh_state(self): "programs": programs, } + self._set_state(content) + + def _set_state(self, content): + self._previous_state = self._state self._state = content + self._last_refresh_time = int(round(datetime.datetime.now().timestamp())) def _retrieve_state(self): if self._state == None: @@ -342,6 +404,9 @@ def _timestamp_to_utc(self, timestamp): offset = (self._get_option("tz") - 48) * 15 * 60 return timestamp if timestamp == 0 else timestamp - offset + def add_program_observer(self, observer): + self._program_observers.append(observer) + # controller variables def enable(self): """Enable operation""" diff --git a/pyopensprinkler/program.py b/pyopensprinkler/program.py index 1271d63..c421b4f 100644 --- a/pyopensprinkler/program.py +++ b/pyopensprinkler/program.py @@ -16,6 +16,7 @@ def __init__(self, controller, index): """Program class initializer.""" self._controller = controller self._index = index + self._on_removed_observers = [] def _get_program_data(self): return self._controller._state["programs"]["pd"][self._index] @@ -67,6 +68,9 @@ def _get_data_flag_bits(self): def _set_data_flag_bit(self, index, value): print("foo") + def add_on_removed_observer(self, observer): + self._on_removed_observers.append(observer) + def enable(self): """Enable operation""" return self.set_enabled(True)