Notification Delivery Plugins¶
Notification delivery plugins are used by the notification system to send a notification to some other system or device. They are the transport that allows the event to be notified to that other system or device.
Notification delivery plugins may be written in C or C++ and have a very simple interface. The plugin mechanism and a subset of the API is common between all types of plugins including filters. This documentation is based on the MQTT notification delivery source code. The MQTT delivery plugin sends MQTT messages to a configurable MQTT topic when a notification is triggered and cleared.
Configuration¶
Notification Delivery plugins use the same configuration mechanism as the rest of FogLAMP, using a JSON document to describe the configuration parameters. As with any other plugin the structure is defined by the plugin and retrieve by the plugin_info entry point. This is then matched with the database content to pass the configured values to the plugin_init entry point.
Notification Delivery Plugin API¶
The notification delivery plugin API consists of a small number of C function entry points, these are called in a strict order and based on the same set of common API entry points for all FogLAMP plugins.
Plugin Information¶
The plugin_info entry point is the first entry point that is called in a notification delivery plugin and returns the plugin information structure. This is the exact same call that every FogLAMP plugin must support and is used to determine the type of the plugin and the configuration category defaults for the plugin.
A typical implementation of plugin_info would merely return a pointer to a static PLUGIN_INFORMATION structure.
PLUGIN_INFORMATION *plugin_info()
{
return &info;
}
Plugin Initialise¶
The second call that is made to the plugin is the plugin_init call, that is used to retrieve a handle on the plugin instance and to configure the plugin.
PLUGIN_HANDLE plugin_init(ConfigCategory* config)
{
MQTT *mqtt = new MQTT(config);
return (PLUGIN_HANDLE)mqtt;
}
The config parameter is the configuration category with the user supplied values inserted, these values are used to configure the behavior of the plugin. In the case of our MQTT example we use this to call the constructor of our MQTT class.
/**
* Construct a MQTT notification plugin
*
* @param category The configuration of the plugin
*/
MQTT::MQTT(ConfigCategory *category)
{
if (category->itemExists("broker"))
m_broker = category->getValue("broker");
if (category->itemExists("topic"))
m_topic = category->getValue("topic");
if (category->itemExists("trigger_payload"))
m_trigger = category->getValue("trigger_payload");
if (category->itemExists("clear_payload"))
m_clear = category->getValue("clear_payload");
}
This constructor merely stores values out of the configuration category as private member variables of the MQTT class.
We return the pointer to our MQTT class as the handle for the plugin. This allows subsequent calls to the plugin to reference the instance created by the plugin_init call.
Plugin Delivery¶
This is the API call made whenever the plugin needs to send a triggered or cleared notification state. It may be called multiple times within the lifetime of a plugin.
bool plugin_deliver(PLUGIN_HANDLE handle,
const std::string& deliveryName,
const std::string& notificationName,
const std::string& triggerReason,
const std::string& message)
{
MQTT *mqtt = (MQTT *)handle;
return mqtt->notify(notificationName, triggerReason, message);
}
The delivery call is passed the handle, which gives us the MQTT class instance on this case, the name of the notification, a trigger reason, which is a JSON document and a message. The trigger reason JSON document contains information about why the delivery call was made, including the triggered or cleared status, the timestamp of the reading that caused the notification to trigger and the name of the asset or assets involved in the notification rule that triggered this delivery event.
{
"reason": "triggered",
"asset": ["sinusoid"],
"timestamp": "2020-11-18 11:52:33.960530+00:00"
}
The return from the plugin_deliver entry point is a boolean that indicates if the delivery succeeded or not.
In the case of our MQTT example we call the notify method of the class, this then interacts with the MQTT broker.
/**
* Send a notification via MQTT broker
*
* @param notificationName The name of this notification
* @param triggerReason Why the notification is being sent
* @param message The message to send
*/
bool MQTT::notify(const string& notificationName, const string& triggerReason, const string& message)
{
string payload = m_trigger;
MQTTClient client;
lock_guard<mutex> guard(m_mutex);
// Parse the JSON that represents the reason data
Document doc;
doc.Parse(triggerReason.c_str());
if (!doc.HasParseError() && doc.HasMember("reason"))
{
if (!strcmp(doc["reason"].GetString(), "cleared"))
payload = m_clear;
}
// Connect to the MQTT broker
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
MQTTClient_message pubmsg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
int rc;
if ((rc = MQTTClient_create(&client, m_broker.c_str(), CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS)
{
Logger::getLogger()->error("Failed to create client, return code %d\n", rc);
return false;
}
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
Logger::getLogger()->error("Failed to connect, return code %d\n", rc);
return false;
}
// Construct the payload
pubmsg.payload = (void *)payload.c_str();
pubmsg.payloadlen = payload.length();
pubmsg.qos = 1;
pubmsg.retained = 0;
// Publish the message
if ((rc = MQTTClient_publishMessage(client, m_topic.c_str(), &pubmsg, &token)) != MQTTCLIENT_SUCCESS)
{
Logger::getLogger()->error("Failed to publish message, return code %d\n", rc);
return false;
}
// Wait for completion and disconnect
rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)
Logger::getLogger()->error("Failed to disconnect, return code %d\n", rc);
MQTTClient_destroy(&client);
return true;
}
Plugin Reconfigure¶
As with other plugin types the notification delivery plugin may be reconfigured during its lifetime. When a reconfiguration operation occurs the plugin_reconfigure method will be called with the new configuration for the plugin.
void plugin_reconfigure(PLUGIN_HANDLE *handle, const std::string& newConfig)
{
MQTT *mqtt = (MQTT *)handle;
mqtt->reconfigure(newConfig);
return;
}
In the case of our MQTT example we call the reconfigure method of our MQTT class. In this method the new values are copied into the local member variables of the instance.
/**
* Reconfigure the MQTT delivery plugin
*
* @param newConfig The new configuration
*/
void MQTT::reconfigure(const string& newConfig)
{
ConfigCategory category("new", newConfig);
lock_guard<mutex> guard(m_mutex);
m_broker = category.getValue("broker");
m_topic = category.getValue("topic");
m_trigger = category.getValue("trigger_payload");
m_clear = category.getValue("clear_payload");
}
The mutex is used here to prevent the plugin reconfiguration occurring when we are delivering a notification. The same mutex is held in the notify method of the MQTT class.
Plugin Shutdown¶
As with other plugins a shutdown call exists which may be used by the plugin to perform any cleanup that is required when the plugin is shut down.
void plugin_shutdown(PLUGIN_HANDLE *handle)
{
MQTT *mqtt = (MQTT *)handle;
delete mqtt;
}
In the case of our MQTT example we merely destroy the instance of the MQTT class and allow the destructor of that class to do any cleanup that is required. In the case of this example there is no cleanup required.