Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: program added/removed events #48

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions pyopensprinkler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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"""
Expand Down
4 changes: 4 additions & 0 deletions pyopensprinkler/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically it is both add and remove so the name is a bit off

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually just remove. Chicken/egg for added.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaning if you’re adding a callback for added on a program...the program is already added (in the context of a program instance).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea but you have both "added" and "removed" "events". Maybe just add_observer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The event on the program directly is only removed..

self._on_removed_observers.append(observer)

def enable(self):
"""Enable operation"""
return self.set_enabled(True)
Expand Down