-
Notifications
You must be signed in to change notification settings - Fork 0
Walkthrough: Implementing Your Own Plugin
One of the first steps when executing the System_Watchdog is reading and interpreting the configuration file. The configuration file consists of different configuration sections, each of which use a configuration type to implement a watchdog functionality.
The type is used to try and identify a Python implementation file of the same name with the extension .py
in the subdirectory plugins/
. So if we have a configuration type fluxcompensator
then the file plugins/fluxcompensator.py
will be loaded and initialized.
When a file is initially loaded, all python commands that are not inside of functions are executed directly. We use this to initialize global variables (global to this configuration type) and then call the function check_prerequisites()
(at least if you use template.py
as your basis).
This function now verifies that all that is needed to execute this plugin successfully is available. This might be Python modules that are needed or an operating system or a processor architecture. If everything is as needed this function sets a global variable prerequisites_satisfied
to the value True
. Since this function is only executed once when loading the plugin potentially expensive operations to verify that the correct environment is provided can be placed here. It is important to understand, though, that these checks are on the plugin-level, not the level of a specific configuration (this comes later).
prerequisites_satisfied = False
def check_prerequisites() -> None:
global prerequisites_satisfied
# Example for checking the availability of the
# Paho MQTT package
try:
import paho.mqtt.subscribe as subscribe
prerequisites_satisfied = True
logging.debug("Optional MQTT package found.")
except ImportError or ModuleNotFoundError:
logging.debug("No MQTT implementation found. Use 'pip3 install paho-mqtt' to install.")
logging.debug("Continuing without MQTT support.")
pass
prerequisites_satisfied = True
...
###############################################################
# End of file
check_prerequisites()
When the plugin file has been loaded successfully the function register()
is called. This function has to return a dictionary that contains the functions that are called during the lifecycle of the configuration. In the minimal case this dictionary contains only a function for checking that everything is ok. In the maximum case this dictionary contains functions for preparing every configuration, for checking, repairing and for the fallback action.
def register(config_type: str, g_conf: Dict[str, Any]) -> Dict[str, Callable]:
global config_name, general_config
config_name = config_type
general_config = g_conf
logging.debug("registering implementation for '%s'", config_name)
return {
PREP: prepare_configuration,
CHECK: check_configuration,
REPAIR: repair,
FALLBACK: fallback,
}
Whenever a new configuration is loaded the implementation's function prepare_configuration()
is called if it is registered. In this function we can convert values, check for configuration-specific prerequisites and do anything needed to guarantee that the configuration is defined correctly. If this function returns True
, then the configuration is seen as correct, if False
is returned, then the execution for this configuration is aborted. For instance, if you have a credentials file that contains user and password needed for your configuration, then this is the place to load it.
def prepare_configuration(section: str, config: ConfigParser) -> None:
if not prerequisites_satisfied:
logging.warning("%s: Prerequisites not satisfied." % section)
return False
logging.debug("Prepare configuration %s" % config_name)
return True
Now the normal life of the configuration begins. In regular intervals (the sleep time
intervals) the check function is called. If it returns 0, everything is well. If it returns another value, then depending on that value, time is added toward the timeout (see the template files for details). The check function is executed after another interval until the timeout is reached.
If the timeout is reached for the first time, then the repair action is executed. If you have registered a function for this, then the function is called. Otherwise, the contents of the repair
option is used as a shell command and executed. The timeout is reset to 0.
If the timeout is reached for the second time, then the fallback action is executed. Again, if you have registered a function for this, then the function is called. Otherwise, the contents of the fallback
option is used as a shell command and executed. The timeout is reset to 0 and the timeout count is reset to 0 as well (meaning that at the next timeout the repair command is tried again).
In the plugins/
subdirectory you find two template files, template.py and template_min.py. Both are fully functional without doing anything. They allow you to experiment with the flow of the execution and how the architecture works.
The file template.py
contains the full implementations of functions for the maximum flexibility, the file template_min.py
contains a minimal implementation that provides only the check
functionality. Actually most of the existing plugins are based on this second and simpler template.
Instead of the dictionary that contains the different callback functions implementing the parts of the lifecycle of our configuration, you can also simply return one function that is then run as a thread of its own. In that case you are fully responsible for the whole lifecycle, but on the other hand, have the full control over every aspect of the execution. If you want to do this, contact me.