From 8e0942a9d736e137e5af6cfda78a54566ecb17d9 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:22:59 +0200 Subject: [PATCH 1/7] sdp: suspend/resume --- dev/sample_smartdevice_plugin/__init__.py | 14 ++++- dev/sample_smartdevice_plugin/plugin.yaml | 14 +++++ .../__init__.py | 12 ++++ .../plugin.yaml | 14 +++++ lib/model/sdp/globals.py | 4 +- lib/model/smartdeviceplugin.py | 58 ++++++++++++++----- 6 files changed, 99 insertions(+), 17 deletions(-) diff --git a/dev/sample_smartdevice_plugin/__init__.py b/dev/sample_smartdevice_plugin/__init__.py index 9cf03130aa..2a4ba3b26f 100644 --- a/dev/sample_smartdevice_plugin/__init__.py +++ b/dev/sample_smartdevice_plugin/__init__.py @@ -62,7 +62,7 @@ class example(SmartDevicePlugin): def _set_device_defaults(self): # you can add initialisations and internal defaults here - + # for demonstation purposes, we want to use the null connection self._parameters[PLUGIN_ATTR_CONNECTION] = CONN_NULL self._use_callbacks = True @@ -87,6 +87,18 @@ def on_disconnect(self, by=None): """ callback if connection is broken. """ self.logger.info('example plugin disconnected') + # if you want to use the suspend/resume feature, you can overwrite these + # methods and customize to your liking. If not, you can safely delete them + # These are then called after suspending or resuming the plugin. + + def on_suspend(self): + """ called when suspend is enabled. Overwrite as needed """ + self.logger.info('suspend enabled, on_suspend called') + + def on_resume(self): + """ called when suspend is disabled. Overwrite as needed """ + self.logger.info('suspend disabled, plugin resumed, on_resume called') + # needed to start operation in standalone mode # as we don't have a run_standalone() method, only struct generation can be used diff --git a/dev/sample_smartdevice_plugin/plugin.yaml b/dev/sample_smartdevice_plugin/plugin.yaml index 9d2f104ba1..72836cb519 100644 --- a/dev/sample_smartdevice_plugin/plugin.yaml +++ b/dev/sample_smartdevice_plugin/plugin.yaml @@ -59,6 +59,20 @@ parameters: de: 'Intervall für regelmäßiges Lesen' en: 'interval for cyclic reading' + delay_initial_read: + type: num + default: 0 + description: + de: 'Verzögerung für das erstmalige Lesen beim Start (in Sekunden)' + en: 'delay for initial command read on start (in seconds)' + + resume_initial_read: + type: bool + defaul: false + description: + de: 'Bei resume vom Plugin erstmaliges Lesen erneut durchführen' + en: 'Repeat initial read on resume' + # select if ex_custom1/2/3 item attributes should be inherited by sub-items # !! irrelevant if ex_custom1/2/3 not implemented recursive_custom: diff --git a/dev/sample_smartdevice_standalone_plugin/__init__.py b/dev/sample_smartdevice_standalone_plugin/__init__.py index dabbe37b3e..d1c4f28cc7 100644 --- a/dev/sample_smartdevice_standalone_plugin/__init__.py +++ b/dev/sample_smartdevice_standalone_plugin/__init__.py @@ -87,6 +87,18 @@ def on_disconnect(self, by=None): """ callback if connection is broken. """ self.logger.info('example plugin disconnected') + # if you want to use the suspend/resume feature, you can overwrite these + # methods and customize to your liking. If not, you can safely delete them + # These are then called after suspending or resuming the plugin. + + def on_suspend(self): + """ called when suspend is enabled. Overwrite as needed """ + self.logger.info('suspend enabled, on_suspend called') + + def on_resume(self): + """ called when suspend is disabled. Overwrite as needed """ + self.logger.info('suspend disabled, plugin resumed, on_resume called') + # # methods for standalone mode # diff --git a/dev/sample_smartdevice_standalone_plugin/plugin.yaml b/dev/sample_smartdevice_standalone_plugin/plugin.yaml index 9d2f104ba1..72836cb519 100644 --- a/dev/sample_smartdevice_standalone_plugin/plugin.yaml +++ b/dev/sample_smartdevice_standalone_plugin/plugin.yaml @@ -59,6 +59,20 @@ parameters: de: 'Intervall für regelmäßiges Lesen' en: 'interval for cyclic reading' + delay_initial_read: + type: num + default: 0 + description: + de: 'Verzögerung für das erstmalige Lesen beim Start (in Sekunden)' + en: 'delay for initial command read on start (in seconds)' + + resume_initial_read: + type: bool + defaul: false + description: + de: 'Bei resume vom Plugin erstmaliges Lesen erneut durchführen' + en: 'Repeat initial read on resume' + # select if ex_custom1/2/3 item attributes should be inherited by sub-items # !! irrelevant if ex_custom1/2/3 not implemented recursive_custom: diff --git a/lib/model/sdp/globals.py b/lib/model/sdp/globals.py index 4247c96a13..bbb9bb52e9 100644 --- a/lib/model/sdp/globals.py +++ b/lib/model/sdp/globals.py @@ -47,6 +47,8 @@ PLUGIN_ATTR_RECURSIVE = 'recursive_custom' # indices of custom item attributes for which to enable recursive lookup (number or list of numbers) PLUGIN_ATTR_SUSPEND_ITEM = 'suspend_item' # item to toggle suspend/resume mode PLUGIN_ATTR_CYCLE = 'cycle' # plugin-wide cyclic update interval +PLUGIN_ATTR_DELAY_INITIAL = 'delay_initial_read' # delay reading of initial commands +PLUGIN_ATTR_REREAD_INITIAL = 'resume_initial_read' # repeat initial read on resume # general connection attributes PLUGIN_ATTR_CONNECTION = 'conn_type' # manually set connection class, classname or type (see below) @@ -82,7 +84,7 @@ PLUGIN_ATTR_CB_SUSPEND = 'suspend_callback' # callback function, called if connection attempts are aborted PLUGIN_ATTRS = (PLUGIN_ATTR_MODEL, PLUGIN_ATTR_CMD_CLASS, PLUGIN_ATTR_RECURSIVE, PLUGIN_ATTR_CYCLE, - PLUGIN_ATTR_SUSPEND_ITEM, PLUGIN_ATTR_CONNECTION, + PLUGIN_ATTR_SUSPEND_ITEM, PLUGIN_ATTR_CONNECTION, PLUGIN_ATTR_DELAY_INITIAL, PLUGIN_ATTR_REREAD_INITIAL, PLUGIN_ATTR_CONN_TIMEOUT, PLUGIN_ATTR_CONN_TERMINATOR, PLUGIN_ATTR_CONN_BINARY, PLUGIN_ATTR_CONN_RETRIES, PLUGIN_ATTR_CONN_CYCLE, PLUGIN_ATTR_CONN_AUTO_RECONN, PLUGIN_ATTR_CONN_AUTO_CONN, PLUGIN_ATTR_CONN_RETRY_CYCLE, PLUGIN_ATTR_CONN_RETRY_SUSPD, PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_NET_PORT, diff --git a/lib/model/smartdeviceplugin.py b/lib/model/smartdeviceplugin.py index f2de968d38..4ea7dbc83e 100644 --- a/lib/model/smartdeviceplugin.py +++ b/lib/model/smartdeviceplugin.py @@ -52,9 +52,9 @@ INDEX_GENERIC, INDEX_MODEL, ITEM_ATTR_COMMAND, ITEM_ATTR_CUSTOM1, ITEM_ATTR_CYCLE, ITEM_ATTR_GROUP, ITEM_ATTR_LOOKUP, ITEM_ATTR_READ, ITEM_ATTR_READ_GRP, ITEM_ATTR_READ_INIT, ITEM_ATTR_WRITE, - PLUGIN_ATTR_CB_ON_CONNECT, PLUGIN_ATTR_CB_ON_DISCONNECT, + PLUGIN_ATTR_CB_ON_CONNECT, PLUGIN_ATTR_CB_ON_DISCONNECT, PLUGIN_ATTR_DELAY_INITIAL, PLUGIN_ATTR_CMD_CLASS, PLUGIN_ATTR_CONNECTION, PLUGIN_ATTR_SUSPEND_ITEM, - PLUGIN_ATTR_CONN_AUTO_RECONN, PLUGIN_ATTR_CONN_AUTO_CONN, + PLUGIN_ATTR_CONN_AUTO_RECONN, PLUGIN_ATTR_CONN_AUTO_CONN, PLUGIN_ATTR_REREAD_INITIAL, PLUGIN_ATTR_PROTOCOL, PLUGIN_ATTR_RECURSIVE, PLUGIN_PATH, PLUGIN_ATTR_CYCLE, PLUGIN_ATTR_CB_SUSPEND, CMD_IATTR_CYCLIC, ITEM_ATTR_READAFTERWRITE, ITEM_ATTR_CYCLIC) @@ -179,6 +179,10 @@ def __init__(self, sh, logger=None, **kwargs): self._cycle = self.get_parameter_value(PLUGIN_ATTR_CYCLE) if self._cycle is None: self._cycle = -1 + # delay initial read + self._initial_value_read_delay = self.get_parameter_value(PLUGIN_ATTR_DELAY_INITIAL) + # resend initial commands on resume + self._resume_initial_read = self.get_parameter_value(PLUGIN_ATTR_REREAD_INITIAL) # set (overwritable) callback self._dispatch_callback = self.dispatch_data @@ -321,8 +325,11 @@ def suspend(self, by=None): self.suspended = True if self._suspend_item is not None: self._suspend_item(True, self.get_fullname()) - if hasattr(self, 'disconnect'): - self.disconnect() + self.disconnect() + self.scheduler_remove_all() + + # call user-defined suspend actions + self.on_suspend() def resume(self, by=None): """ @@ -333,8 +340,25 @@ def resume(self, by=None): self.suspended = False if self._suspend_item is not None: self._suspend_item(False, self.get_fullname()) - if hasattr(self, 'connect'): - self.connect() + self.connect() + if self._connection.connected(): + if self._resume_initial_read: + # make sure to read again on resume (if configured) + self._initial_value_read_done = False + self.read_initial_values() + if not SDP_standalone: + self._create_cyclic_scheduler() + + # call user-defined resume actions + self.on_resume() + + def on_suspend(self): + """ called when suspend is enabled. Overwrite as needed """ + pass + + def on_resume(self): + """ called when suspend is disabled. Overwrite as needed """ + pass def set_suspend(self, suspend_active=None, by=None): """ @@ -363,14 +387,6 @@ def set_suspend(self, suspend_active=None, by=None): else: self.resume(by) - if suspend_active: - if self.scheduler_get(self.get_shortname() + '_cyclic'): - self.scheduler_remove(self.get_shortname() + '_cyclic') - - else: - if self._connection.connected() and not SDP_standalone: - self._create_cyclic_scheduler() - def run(self): """ Run method for the plugin @@ -385,7 +401,9 @@ def run(self): self.set_suspend(by='run()') if self._connection.connected(): - self._read_initial_values() + # make sure to read again on restart + self._initial_value_read_done = False + self.read_initial_values() def stop(self): """ @@ -1112,6 +1130,16 @@ def _create_cyclic_scheduler(self): self._cyclic_errors = 0 self.logger.info(f'Added cyclic worker thread {self.get_shortname()}_cyclic with {workercycle} s cycle. Shortest item update cycle found was {shortestcycle} s') + def read_initial_values(self): + """ control call of _read_initial_values - run instantly or delay """ + if self.scheduler_get('read_initial_values'): + return + elif self._initial_value_read_delay > 0: + self.logger.dbghigh(f"Delaying reading initial values for {self._initial_value_read_delay} seconds.") + self.scheduler_add('read_initial_values', self._read_initial_values, next=self.shtime.now() + datetime.timedelta(seconds=self._initial_value_read_delay)) + else: + self._read_initial_values() + def _read_initial_values(self): """ Read all values configured to be read/triggered at startup From 1e032edcb37161660fe6587805a62d40c81ea919 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:01:34 +0200 Subject: [PATCH 2/7] sdp: fix missing parameter --- lib/model/smartdeviceplugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model/smartdeviceplugin.py b/lib/model/smartdeviceplugin.py index 4ea7dbc83e..44324f48d9 100644 --- a/lib/model/smartdeviceplugin.py +++ b/lib/model/smartdeviceplugin.py @@ -1134,7 +1134,7 @@ def read_initial_values(self): """ control call of _read_initial_values - run instantly or delay """ if self.scheduler_get('read_initial_values'): return - elif self._initial_value_read_delay > 0: + elif self._initial_value_read_delay: self.logger.dbghigh(f"Delaying reading initial values for {self._initial_value_read_delay} seconds.") self.scheduler_add('read_initial_values', self._read_initial_values, next=self.shtime.now() + datetime.timedelta(seconds=self._initial_value_read_delay)) else: From 28a8fabc3ac2b7af0ada9a0bef1184b781a73271 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 7 Jul 2024 11:11:05 +0200 Subject: [PATCH 3/7] sdp: move to sdp v1.0.2, pass plugin reference to connection/protocol --- lib/model/sdp/connection.py | 1 + lib/model/smartdeviceplugin.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/model/sdp/connection.py b/lib/model/sdp/connection.py index ab584d3ff1..9fa52abcfe 100644 --- a/lib/model/sdp/connection.py +++ b/lib/model/sdp/connection.py @@ -112,6 +112,7 @@ def __init__(self, data_received_callback, name=None, **kwargs): # "import" options from plugin self._params.update(kwargs) + self._plugin = self._params.get('plugin') # check if some of the arguments are usable self._set_connection_params() diff --git a/lib/model/smartdeviceplugin.py b/lib/model/smartdeviceplugin.py index 44324f48d9..95036e920e 100644 --- a/lib/model/smartdeviceplugin.py +++ b/lib/model/smartdeviceplugin.py @@ -87,7 +87,7 @@ class SmartDevicePlugin(SmartPlugin): """ # this is the internal SDP version - SDP_VERSION = '1.0.1' + SDP_VERSION = '1.0.2' # this is the placeholder version of the derived plugin, not of SDP PLUGIN_VERSION = '0.0.1' @@ -208,6 +208,9 @@ def __init__(self, sh, logger=None, **kwargs): # init device + # allow other classes to access plugin + self._parameters['plugin'] = selfD + # possibly initialize additional (overwrite _set_device_defaults) self._set_device_defaults() From 58c8e5491eb894e9d4b1e1b0f255263412484917 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 7 Jul 2024 12:55:33 +0200 Subject: [PATCH 4/7] sdp: fix typo --- lib/model/smartdeviceplugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model/smartdeviceplugin.py b/lib/model/smartdeviceplugin.py index 95036e920e..1913761a84 100644 --- a/lib/model/smartdeviceplugin.py +++ b/lib/model/smartdeviceplugin.py @@ -209,7 +209,7 @@ def __init__(self, sh, logger=None, **kwargs): # init device # allow other classes to access plugin - self._parameters['plugin'] = selfD + self._parameters['plugin'] = self # possibly initialize additional (overwrite _set_device_defaults) self._set_device_defaults() From 33149c691fd8dd3d9afa6159cdb1fc1baf5d3843 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 7 Jul 2024 12:56:43 +0200 Subject: [PATCH 5/7] sdp: change to scheduler_remove_all --- lib/model/smartdeviceplugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/model/smartdeviceplugin.py b/lib/model/smartdeviceplugin.py index 1913761a84..bea9db5783 100644 --- a/lib/model/smartdeviceplugin.py +++ b/lib/model/smartdeviceplugin.py @@ -415,8 +415,7 @@ def stop(self): self.logger.dbghigh(self.translate("Methode '{method}' aufgerufen", {'method': 'stop()'})) self.alive = False - if self.scheduler_get(self.get_shortname() + '_cyclic'): - self.scheduler_remove(self.get_shortname() + '_cyclic') + self.scheduler_remove_all() self.disconnect() def connect(self): From 362130197c81c815ab7f22d139f204d823f118cb Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:29:45 +0200 Subject: [PATCH 6/7] sdp: modify run logics --- lib/model/smartdeviceplugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/model/smartdeviceplugin.py b/lib/model/smartdeviceplugin.py index bea9db5783..1b3620936c 100644 --- a/lib/model/smartdeviceplugin.py +++ b/lib/model/smartdeviceplugin.py @@ -404,8 +404,7 @@ def run(self): self.set_suspend(by='run()') if self._connection.connected(): - # make sure to read again on restart - self._initial_value_read_done = False + # make sure this is called once at startup, even if resume_initial is not set self.read_initial_values() def stop(self): From f331c32edc48b9d50a17bc9c859754ca4aafaec8 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:51:56 +0200 Subject: [PATCH 7/7] sdp: fix --- lib/model/smartdeviceplugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model/smartdeviceplugin.py b/lib/model/smartdeviceplugin.py index 1b3620936c..ffa930b1b7 100644 --- a/lib/model/smartdeviceplugin.py +++ b/lib/model/smartdeviceplugin.py @@ -1153,7 +1153,6 @@ def _read_initial_values(self): for cmd in self._commands_initial: self.logger.debug(f'Sending initial command {cmd}') self.send_command(cmd) - self._initial_value_read_done = True self.logger.info('Initial read commands sent') if self._triggers_initial: self.logger.info('Starting initial read group triggers') @@ -1161,6 +1160,7 @@ def _read_initial_values(self): self.logger.debug(f'Triggering initial read group {grp}') self.read_all_commands(grp) self.logger.info('Initial read group triggers sent') + self._initial_value_read_done = True def _read_cyclic_values(self): """