diff --git a/README.md b/README.md index 7a8e3684..42212cf8 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Each relay has the following settings *(in order of appearance)*: | Icon `ON` / `OFF` | An image or emoji to indicate the relay state (supports HTML) | | GPIO Number | The [GPIO pin on the Raspberry Pi](https://pinout.xyz/) | | Inverted output | For normally closed relay: the relay is `ON` without power | -| Warn if turning `OFF` | Enables a confirmation dialog when turning the relay `OFF` | +| Confirm turning `OFF` | Enables a confirmation dialog when turning the relay `OFF` | | Alert on switches ahead | Notifies on upcoming switch with an ability to cancel it | | **Events:** | Behavior customization (automation) | | on Startup | The state to switch the relay to when OctoPrint started | diff --git a/octoprint_octorelay/__init__.py b/octoprint_octorelay/__init__.py index 671f347a..cb4580cb 100755 --- a/octoprint_octorelay/__init__.py +++ b/octoprint_octorelay/__init__.py @@ -14,7 +14,7 @@ from .const import ( get_default_settings, get_templates, get_ui_vars, RELAY_INDEXES, ASSETS, SWITCH_PERMISSION, UPDATES_CONFIG, POLLING_INTERVAL, UPDATE_COMMAND, GET_STATUS_COMMAND, LIST_ALL_COMMAND, AT_COMMAND, SETTINGS_VERSION, - STARTUP, PRINTING_STOPPED, PRINTING_STARTED, CANCELLATION_EXCEPTIONS, PREEMPTIVE_CANCELLATION_CUTOFF, + STARTUP, PRINTING_STOPPED, PRINTING_STARTED, PRIORITIES, FALLBACK_PRIORITY, PREEMPTIVE_CANCELLATION_CUTOFF, CANCEL_TASK_COMMAND, USER_ACTION, TURNED_ON ) from .driver import Relay @@ -187,16 +187,17 @@ def handle_plugin_event(self, event, scope = None): needs_ui_update = False for index in scope: if bool(settings[index]["active"]): + did_cancel = self.cancel_tasks(subject = index, initiator = event) # issue 205 + needs_ui_update = needs_ui_update or did_cancel target = settings[index]["rules"][event]["state"] if target is not None: target = bool(target) if target and event == TURNED_ON: self._logger.debug(f"Skipping {index} to avoid infinite loop") continue # avoid infinite loop - self.cancel_tasks(subject = index, initiator = event) delay = int(settings[index]["rules"][event]["delay"] or 0) if delay == 0: - self.toggle_relay(index, target) + self.toggle_relay(index, target) # UI update conducted by the polling thread else: self._logger.debug(f"Postponing the switching of the relay {index} by {delay}s") task = Task( @@ -237,15 +238,19 @@ def toggle_relay(self, index, target: Optional[bool] = None): if state: self.handle_plugin_event(TURNED_ON, scope = [index]) - def cancel_tasks(self, subject: str, initiator: str, target: Optional[bool] = None, owner: Optional[str] = None): + def cancel_tasks( + self, subject: str, initiator: str, + target: Optional[bool] = None, owner: Optional[str] = None + ) -> bool: # returns True when cancelled some tasks self._logger.debug(f"Cancelling tasks by request from {initiator} for relay {subject}") - exceptions = CANCELLATION_EXCEPTIONS.get(initiator) or [] + priority = PRIORITIES.get(initiator) or FALLBACK_PRIORITY + count_before = len(self.tasks) def handler(task: Task): - not_exception = task.owner not in exceptions + lower_priority = (PRIORITIES.get(task.owner) or FALLBACK_PRIORITY) >= priority same_subject = subject == task.subject same_target = True if target is None else task.target == target same_owner = True if owner is None else task.owner == owner - if same_subject and not_exception and same_target and same_owner: + if same_subject and lower_priority and same_target and same_owner: try: task.cancel_timer() self._logger.info(f"Cancelled the task: {task}") @@ -254,7 +259,13 @@ def handler(task: Task): return False # exclude return True # include self.tasks = list(filter(handler, self.tasks)) - self._logger.debug("The cancelled tasks removed from the registry") + count_cancelled = count_before - len(self.tasks) + did_cancel = count_cancelled > 0 + self._logger.debug( + f"Cancelled ({count_cancelled}) tasks and removed from the registry" + if did_cancel else "No tasks cancelled" + ) + return did_cancel def run_system_command(self, cmd): if cmd: diff --git a/octoprint_octorelay/const.py b/octoprint_octorelay/const.py index a81f13e2..b6633a52 100644 --- a/octoprint_octorelay/const.py +++ b/octoprint_octorelay/const.py @@ -8,9 +8,19 @@ TURNED_ON = "TURNED_ON" USER_ACTION = "USER_ACTION" -# Task cancellation exceptions -# { eventHappened: [ events which postponed timers should NOT be cancelled ] -CANCELLATION_EXCEPTIONS = {} +# Event having higher or same priority (lower or equal number here) cancells the tasks placed by previous events +# Highest priority is 1. +PRIORITIES = { + USER_ACTION: 1, + STARTUP: 2, + PRINTING_STARTED: 2, + PRINTING_STOPPED: 2, + TURNED_ON: 3 +} + +# Missing events above will be treated as ones having this priority +FALLBACK_PRIORITY = 5 + # min seconds before the task can be cancelled PREEMPTIVE_CANCELLATION_CUTOFF = 2 diff --git a/octoprint_octorelay/static/css/octorelay.css b/octoprint_octorelay/static/css/octorelay.css index 0e210b18..b363a99d 100644 --- a/octoprint_octorelay/static/css/octorelay.css +++ b/octoprint_octorelay/static/css/octorelay.css @@ -19,7 +19,8 @@ color: inherit; } -#settings_plugin_octorelay .control-label span.label { +#settings_plugin_octorelay .control-label span.label, +#settings_plugin_octorelay .help-inline span.label { zoom: 0.85; /* not scale */ } diff --git a/octoprint_octorelay/templates/octorelay_settings.jinja2 b/octoprint_octorelay/templates/octorelay_settings.jinja2 index f8ce414e..68e58c78 100755 --- a/octoprint_octorelay/templates/octorelay_settings.jinja2 +++ b/octoprint_octorelay/templates/octorelay_settings.jinja2 @@ -85,6 +85,10 @@ {% endfor %} + + {{ _('Disconnects when turning') }} + {{ _('OFF') }} + @@ -163,7 +167,7 @@