Skip to content

Commit

Permalink
Merge pull request #662 from Morg42/sdp-1
Browse files Browse the repository at this point in the history
sdp: add/improve suspend feature
  • Loading branch information
Morg42 authored Aug 1, 2024
2 parents 7321d60 + 948e24b commit 7b8fa7c
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 21 deletions.
14 changes: 13 additions & 1 deletion dev/sample_smartdevice_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions dev/sample_smartdevice_plugin/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 12 additions & 0 deletions dev/sample_smartdevice_standalone_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down
14 changes: 14 additions & 0 deletions dev/sample_smartdevice_standalone_plugin/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions lib/model/sdp/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 3 additions & 1 deletion lib/model/sdp/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
67 changes: 48 additions & 19 deletions lib/model/smartdeviceplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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):
"""
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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
Expand All @@ -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):
"""
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -1124,14 +1153,14 @@ 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')
for grp in self._triggers_initial:
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):
"""
Expand Down

0 comments on commit 7b8fa7c

Please sign in to comment.