To warn, alert, or notify.
This program subscribes to any number of MQTT topics (which may include wildcards) and publishes received payloads to one or more notification services, including support for notifying more than one distinct service for the same message.
For example, you may wish to notify via e-mail and to Pushover of an alarm published as text to the MQTT topic home/monitoring/+.
Support for the following services is available:
- files (output to files on the file system)
- MQTT. Yes, outgoing MQTT, e.g. as a republisher to same or different broker
- Pushover.net
- SMTP (e-mail)
- Prowl
- Redis PUB
- SQLite3
- XBMC
- Mac OS X notification center
Notifications are transmitted to the appropriate service via plugins. We provide plugins for the above list of services, and you can easily add your own.
In addition to passing the payload received via MQTT to a service, mqttwarn allows you do do the following:
- Transform payloads on a per/topic basis. For example, you know you'll be receiving JSON, but you want to warn with a nicely formatted message.
- For certain services, you can change the title (or subject) of the outgoing message.
Consider the following JSON payload published to the MQTT broker:
mosquitto_pub -t 'osx/json' -m '{"fruit":"banana", "price": 63, "tst" : "1391779336"}'Using the formatmap we can configure mqttwarn to transform that JSON into a different outgoing message which is the text that is actually notified. Part of said formatmap looks like this in the configuration file, and basically specifies that messages published to osx/json should be transformed as on the right-hand side.
formatmap = {
'osx/json' : "I'll have a {fruit} if it costs {price}",
}The result is:
You associate MQTT topic branches to applications in the configuration file (copy mqttwarn.conf.sample to mqttwarn.conf for use). In other words, you can accomplish, say, following mappings:
- PUBs to
owntracks/jane/iphoneshould be notified via Pushover to John's phone - PUBs to
openhab/temperatureshould be Tweeted - PUBs to
home/monitoring/alert/+should notify Twitter, Mail, and Prowl
See details in the config sample for how to configure this script.
The path to the configuration file (which must be valid Python) is obtained from the MQTTWARNCONF environment variable which defaults to mqttwarn.conf in the current directory.
Service plugins are configured in the main mqttwarn.conf file. Each service has two mandatory _dict_s:
service_configdefines things like connection settings for a service, even though many don't need that. Even so, the dict must be defined (e.g. toNone).service_targetsdefines the notification "targets" we use to associate an incoming MQTT topic with the output (i.e. notificiation) to a service.
We term the array for each target an "address list" for the particular service. These may be path names (in the case of the file service), topic names (for outgoing mqtt publishes), hostname/port number combinations for xbmc, etc.
The file service can be used for logging incoming topics, archiving, etc. Each message is written to a path specified in the targets list. Note that files are opened for appending and then closed on each notification.
Supposing we wish to archive all incoming messages to the branch arch/# to a file /data/arch, we could configure the following:
file_config = {
'append_newline' : True,
}
file_targets = {
'log-me' : ['/data/arch'],
}
topicmap = {
'arch/#' : ['file:log-me'],
}The mqtt service fires off a publish on a topic, creating a new connection to the configured broker for each message.
Consider the following configuration snippets:
topicmap = {
'in/a1' : ['mqtt:o1', 'mqtt:o2'],
}
mqtt_targets = {
'o1' : [ 'out/food' ],
'o2' : [ 'out/fruit/{fruit}' ],
}
formatmap = {
'in/a1' : u'Since when does a {fruit} cost {price}?',
}The topicmap specifies we should subscribe to in/a1 and republish to two MQTT targets.
The second target (mqtt:o2) has a topic branch with a variable in it which is to be
interpolated ({fruit}).
These are the results for appropriate publishes:
$ mosquitto_pub -t 'in/a1' -m '{"fruit":"pineapple", "price": 131, "tst" : "1391779336"}'
in/a1 {"fruit":"pineapple", "price": 131, "tst" : "1391779336"}
out/food Since when does a pineapple cost 131?
out/fruit/pineapple Since when does a pineapple cost 131?
$ mosquitto_pub -t 'in/a1' -m 'temperature: 12'
in/a1 temperature: 12
out/food temperature: 12
out/fruit/{fruit} temperature: 12
In the first case, the JSON payload was decoded and the fruit variable could be interpolated into the topic name of the outgoing publish, whereas the latter shows the outgoing topic branch without interpolated values, because they simply didn't exist in the original incoming payload.
This service publishes a message to the broker mqttwarn is connected to. (To
publish a message to a different broker, see mqtt.)
Each target requires a topic name, the desired qos and a retain flag.
mqttpub_config = None # This service requires no configuration
mqttpub_targets = {
# topic qos retain
'mout1' : [ 'mout/1', 0, False ],
}- Requires Mac ;-) and pync which uses the binary terminal-notifier created by Eloy Durán. Note: upon first launch,
pyncwill download and extracthttps://github.com/downloads/alloy/terminal-notifier/terminal-notifier_1.4.2.zipinto a directoryvendor/.
This service is for Prowl. Each target requires an application key and an application name.
prowl_config = None # This service requires no configuration
prowl_targets = {
# application key # app name
'pjpm' : [ 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'SuperAPP' ],
}- Requires prowlpy
This service is for Pushover, an app for iOS and Android. In order to receive pushover notifications you need what is called a user key and one or more application keys which you configure in the targets definition:
pushover_config = None # This service requires no configuration
pushover_targets = {
'nagios' : ['userkey1', 'appkey1'],
'alerts' : ['userkey2', 'appkey2'],
'tracking' : ['userkey1', 'appkey2'],
'extraphone' : ['userkey2', 'appkey3'],
}This defines four targets (nagios, alerts, etc.) which are directed to the
configured user key and app key combinations. This in turn enables you to
notify, say, one or more of your devices as well as one for your spouse.
- Requires: a pushover.net account
The redispub plugin publishes to a Redis channel.
redispub_config = {
'host' : 'localhost',
'port' : 6379,
}
redispub_targets = {
'r1' : [ 'channel-1' ],
}- Requires Python redis-py
The sqlite plugin creates the a table in the database file specified in the targets,
and creates a schema with a single column called payload of type TEXT. mqttwarn
commits messages routed to such a target immediately.
sqlite_config = None # This plugin requires no configuration
sqlite_targets = {
#path #tablename
'demotable' : [ '/tmp/m.db', 'mqttwarn' ],
}The smtp service basically implements an MQTT to SMTP gateway which needs
configuration.
smtp_config = {
'server' : 'localhost:25',
'sender' : "MQTTwarn <jpm@localhost>",
'username' : None,
'password' : None,
'starttls' : False,
}
smtp_targets = {
'localj' : [ 'jpm@localhost' ],
'special' : [ 'ben@gmail', 'suzie@example.net' ],
}Targets may contain more than one recipient, in which case all specified recipients get the message.
Notification of one or more Twitter accounts requires setting up an application at apps.twitter.com. For each Twitter account, you need four (4) bits which are named as shown below.
Upon configuring this service's targets, make sure the four (4) elements of the list are in the order specified!
twitter_config = None # This service requires no configuration
twitter_targets = {
'janejol' : [ 'vvvvvvvvvvvvvvvvvvvvvv', # consumer_key
'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww', # consumer_secret
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', # access_token_key
'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz' # access_token_secret
],
}This service allows for on-screen notification popups on XBMC instances. Each target requires the address and port of the XBMC instance (:).
xbmc_config = None # This service requires no configuration
xbmc_targets = {
'living_room' : '192.168.1.40:8080',
'bedroom' : '192.168.1.41:8080'
}Creating new plugins is rather easy, and I recommend you take the file plugin
and start from that.
Plugins are invoked with two arguments (srv and item). srv is an object
with some helper functions, and item a dict which contains information on the message
which is to be handled by the plugin. item contains the following elements:
item = {
'service' : 'string', # name of handling service (`twitter`, `file`, ..)
'target' : 'string', # name of target (`o1`, `janejol`) in service
'config' : dict, # None or dict from SERVICE_config {}
'topic' : 'string', # incoming topic branch name
'payload' : <payload> # raw message payload
'message' : 'string', # formatted message (if no format string then = payload)
'addrs' : <list>, # list of addresses from SERVICE_targets
'fmt' : None, # possible format string from formatmap{}
'data' : None, # dict with transformation data
'title' : None, # possible title from titlemap{}
'priority' : None, # possible priority from prioritymap{}
}mqttwarn can transform an incoming message before passing it to a plugin service.
On each message, mqttwarn attempts to decode the incoming payload from JSON. If
this is possible, a dict with transformation data is made available to the
service plugins as item.data.
This transformation data is initially set up with some built-in values, in addition
to those decoded from the incoming JSON payload. The following built-in variables
are defined in item.data:
{
'topic' : topic name
'_dtepoch' : epoch time # 1392628581
'_dtiso'] : ISO date (UTC) # 2014-02-17T10:38:43.910691Z
'_dthhmm' : timestamp HH:MM (local) # 10:16
'_dthhmmss' : timestamp HH:MM:SS (local) # 10:16:21
}Any of these values can be used in formatmap{} to create custom outgoing
messages.
formatmap = {
'some/topic' : "I'll have a {fruit} if it costs {price} at {_dthhmm}",
}Consider the following configuration snippet in addition to the configuration
of the mqtt service shown above:
def lookup_data(data):
if type(data) == dict and 'fruit' in data:
return "Ananas"
return None
formatmap = {
# 'in/a1' : u'Since when does a {fruit} cost {price}?',
'in/a1' : lookup_data,
}We've replaced the formatmap entry for the topic by a function which you
define withing the mqttwarn.conf configuration file. These functions
are invoked with decoded JSON data passed to them. They must return
a string which replaces the outgoing message:
in/a1 {"fruit":"pineapple", "price": 131, "tst" : "1391779336"}
out/food Ananas
out/fruit/pineapple Ananas
An MQTT topic branch name contains information you may want to use in transformations. As a rather extreme example, consider the OwnTracks program (the artist formerly known as MQTTitude).
When an OwnTracks device detects a change of a configured waypoint or geo-fence (a region monitoring a user can set up on the device), it emits a JSON payload which looks like this, on a topic name consisting of owntracks/_username_/_deviceid_:
owntracks/jane/phone -m '{"_type": "location", "lat": "52.4770352" .. "desc": "Home", "event": "leave"}'
In order to be able to obtain the username (jane) and her device name (phone) for use
in transformations (see previous section), we would ideally want to parse the MQTT topic name and add that to the item data our plugins obtain. Yes, we can.
An optional topicdatamap in our configuration file, defines the name of a function we provide, also in the configuration file, which accomplishes that.
topicdatamap = {
'owntracks/jane/phone' : tdata,
}This specifies that when a message for the defined topic owntracks/jane/phone is processed, our function tdata() should be invoked to parse that. (As usual, topic names may contain MQTT wildcards.)
The function we define to do that is:
def tdata(topic):
if type(topic) == str:
try:
# owntracks/username/device
parts = topic.split('/')
username = parts[1]
deviceid = parts[2]
except:
deviceid = 'unknown'
username = 'unknown'
return dict(username=username, device=deviceid)
return NoneThe returned dict is merged into the transformation data, i.e. it is made available to plugins and to transformation rules (formatmap). If we then create the following rule
formatmap = {
'owntracks/jane/phone' : u'{username}: {event} => {desc}',
}the above PUBlish will be transformed into
jane: leave => Home
A notification can be filtered (or supressed) using a custom function.
An optional filtermap in our configuration file, defines the name of a function we provide, also in the configuration file, which accomplishes that.
filtermap = {
'owntracks/jane/phone' : owntracks_filter,
}This specifies that when a message for the defined topic owntracks/jane/phone is processed, our function owntracks_filter() should be invoked to parse that. The filter function should return True if the message should be suppressed, or False if the message should be processed. (As usual, topic names may contain MQTT wildcards.)
The function we define to do that is:
def owntracks_filter(topic, message):
return message.find('event') == -1This filter will suppress any messages that do not contain the event token.
You'll need at least the following components:
- Python 2.x (tested with 2.6 and 2.7)
- An MQTT broker (e.g. Mosquitto)
- The Paho Python module:
pip install paho-mqtt
- Clone this repository into a fresh directory.
- Copy
mqttwarn.conf.sampletomqttwarn.confand edit to your taste - Install the prerequisite Python modules for the services you want to use
- Launch
mqttwarn.py
I recommend you use Supervisor for running this.





