Skip to content

Commit

Permalink
Merge pull request #63 from crysxd/pause_notifications
Browse files Browse the repository at this point in the history
Pause notifications
  • Loading branch information
crysxd authored Mar 30, 2024
2 parents 46acd3e + 0d209ac commit 0a62d9f
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 6 deletions.
20 changes: 18 additions & 2 deletions octoapp/notificationsender.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def SendNotification(self, event, state=None):
if event == self.EVENT_DONE:
state["ProgressPercentage"] = 100


self.LastPrintState = state
helper = AppStorageHelper.Get()
Sentry.Info("SENDER", "Preparing notification for %s" % event)
Expand All @@ -68,13 +69,20 @@ def SendNotification(self, event, state=None):
if not targets:
Sentry.Debug("SENDER", "No targets, skipping notification")
return


target_count_before_filter = len(targets)
targets = self._processFilters(targets=targets, event=event)
ios_targets = helper.GetIosApps(targets)
activity_targets = helper.GetActivities(targets)
android_targets = helper.GetAndroidApps(targets)
apnsData = self._createApnsPushData(event, state) if len(ios_targets) or len(activity_targets) else None

# Some clients might have user interaction disbaled. First send pause so all live activities etc
if event == self.EVENT_USER_INTERACTION_NEEDED and target_count_before_filter != len(targets):
Sentry.Info("SENDER", "User interaction needed, first sending pause")
self.SendNotification(self.EVENT_PAUSED)
time.sleep(2)

if not len(android_targets) and apnsData is None:
Sentry.Info("SENDER", "Skipping push, no Android targets and no APNS data, skipping notification")
return
Expand Down Expand Up @@ -138,6 +146,14 @@ def _processFilters(self, targets, event):
filterName = "layer_1"
elif event == self.EVENT_THIRD_LAYER_DONE:
filterName = "layer_3"
elif event == self.EVENT_FILAMENT_REQUIRED:
filterName = "filament_required"
elif event == self.EVENT_ERROR:
filterName = "error"
elif event == self.EVENT_USER_INTERACTION_NEEDED:
filterName = "interaction"
elif event == self.EVENT_BEEP:
filterName = "beep"
else:
return targets

Expand Down Expand Up @@ -312,7 +328,7 @@ def _createApnsPushData(self, event, state):
notificationTitle = "Filament required"
notificationTitleKey = "print_notification___filament_change_required_title"
notificationTitleArgs = [self.PrinterName]
notificationBody = tate.get("FileName", None)
notificationBody = state.get("FileName", None)
notificationSound = "notification_filament_change.wav"
liveActivityState = "filamentRequired"

Expand Down
53 changes: 50 additions & 3 deletions octoapp/notificationshandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def __init__(self, printerStateInterface):
self.ProgressTimer = None
self.FirstLayerTimer = None
self.FinalSnapObj:FinalSnap = None
self.PauseThread = None
# self.Gadget = Gadget(logger, self, self.PrinterStateInterface)

# Define all the vars
Expand Down Expand Up @@ -219,6 +220,11 @@ def OnRestorePrintIfNeeded(self, moonrakerPrintStatsState, fileName_CanBeNone, t
# On paused, make sure they are stopped.
self.StopTimers()

def _cancelDelayedPause(self):
if self.PauseThread is not None and self.PauseThread.is_alive():
Sentry.Info("NOTIFICATION", "Cancelling delayed pause")
self.PauseThread.stop()
self.PauseThread = None

# Only used for testing.
def OnTest(self):
Expand Down Expand Up @@ -261,6 +267,7 @@ def OnFailed(self, fileName, durationSecStr, reason):
self._updateCurrentFileName(fileName)
self._updateToKnownDuration(durationSecStr)
self.StopTimers()
self._cancelDelayedPause()
self._sendEvent(NotificationSender.EVENT_CANCELLED, { "Reason": reason})


Expand All @@ -272,20 +279,45 @@ def OnDone(self, fileName_CanBeNone, durationSecStr_CanBeNone):
self._updateCurrentFileName(fileName_CanBeNone)
self._updateToKnownDuration(durationSecStr_CanBeNone)
self.StopTimers()
self.__cancelDelayedPause()
self._sendEvent(NotificationSender.EVENT_DONE, useFinalSnapSnapshot=True)


# Fired when a print is paused
def OnPaused(self, fileName):
if self._shouldIgnoreEvent(fileName):
return

def firePause(delay, event):
_self = self.PauseThread
Sentry.Info("NOTIFICATION", "Delaying pause for %d seconds" % delay)
time.sleep(delay)
if _self.stopped() is False:
Sentry.Info("NOTIFICATION", "Delayed pause not stopped, executing")
self._sendEvent(event)
self.PauseThread = None
else:
Sentry.Info("NOTIFICATION", "Delayed pause was stopped, dropping")

def scheduleSent(delay, event):
if self.PauseThread is None or self.PauseThread.is_alive() is False:
if delay == 0:
self._sendEvent(event)
else:
self.PauseThread = StoppableThread(target=firePause, args=(delay, event))
self.PauseThread.start()
else:
Sentry.Error("NOTIFICATION", "Skipping pause, already scheduled")

# Always update the file name.
self._updateCurrentFileName(fileName)

delay = 0
event = NotificationSender.EVENT_PAUSED
if Compat.IsMoonraker():
# Because filament runout doesn't work, let's treat every pause as "interaction needed"
# Also delay in case of timlapse photo the pause will be super short. If the print is resumed within the delay, drop the pause
delay = 3
event = NotificationSender.EVENT_USER_INTERACTION_NEEDED

# See if there is a pause notification suppression set. If this is not null and it was recent enough
Expand All @@ -294,20 +326,21 @@ def OnPaused(self, fileName):
if Compat.HasSmartPauseInterface():
lastSuppressTimeSec = Compat.GetSmartPauseInterface().GetAndResetLastPauseNotificationSuppressionTimeSec()
if lastSuppressTimeSec is None or time.time() - lastSuppressTimeSec > 20.0:
self._sendEvent(event)
scheduleSent(delay, event)
else:
Sentry.Info("NOTIFICATION", "Not firing the pause notification due to a Smart Pause suppression.")
else:
self._sendEvent(event)
scheduleSent(delay, event)

# Stop the ping timer, so we don't report progress while we are paused.
self.StopTimers()


# Fired when a print is resumed
def OnResume(self, fileName):
if self._shouldIgnoreEvent(fileName):
return

self._cancelDelayedPause()
self._updateCurrentFileName(fileName)
self._sendEvent(NotificationSender.EVENT_RESUME)

Expand All @@ -324,6 +357,7 @@ def OnError(self, error):
return

self.StopTimers()
self._cancelDelayedPause()

# This might be spammy from OctoPrint, so limit how often we bug the user with them.
if self._shouldSendSpammyEvent("on-error"+str(error), 30.0) is False:
Expand Down Expand Up @@ -1208,6 +1242,19 @@ def _shouldIgnoreEvent(self, fileName:str = None) -> bool:
return True
return False

class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""

def __init__(self, *args, **kwargs):
super(StoppableThread, self).__init__(*args, **kwargs)
self._stop_event = threading.Event()

def stop(self):
self._stop_event.set()

def stopped(self):
return self._stop_event.is_set()

class SpammyEventContext:

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
# Note that this is also parsed by the moonraker module to pull the version, so the string and format must remain the same!
plugin_version = "2.0.8"
plugin_version = "2.0.9"

# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module
Expand Down

0 comments on commit 0a62d9f

Please sign in to comment.