diff --git a/dev/sample_smartdevice_plugin/__init__.py b/dev/sample_smartdevice_plugin/__init__.py index 9cf03130a..2a4ba3b26 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 9d2f104ba..72836cb51 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 dabbe37b3..d1c4f28cc 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 9d2f104ba..72836cb51 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/connection.py b/lib/model/sdp/connection.py index ab584d3ff..9fa52abcf 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/sdp/globals.py b/lib/model/sdp/globals.py index 4247c96a1..bbb9bb52e 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 f2de968d3..ffa930b1b 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) @@ -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' @@ -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 @@ -204,6 +208,9 @@ def __init__(self, sh, logger=None, **kwargs): # init device + # allow other classes to access plugin + self._parameters['plugin'] = self + # possibly initialize additional (overwrite _set_device_defaults) self._set_device_defaults() @@ -321,8 +328,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 +343,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 +390,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 +404,8 @@ def run(self): self.set_suspend(by='run()') if self._connection.connected(): - self._read_initial_values() + # make sure this is called once at startup, even if resume_initial is not set + self.read_initial_values() def stop(self): """ @@ -394,8 +414,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): @@ -1112,6 +1131,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: + 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 @@ -1124,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') @@ -1132,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): """