Skip to content

Commit f9d95cc

Browse files
author
joerggollnick
committed
new service launch - flexible arguments for command, respond with mqtt publish
1 parent bcc8801 commit f9d95cc

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

HANDBOOK.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ _mqttwarn_ supports a number of services (listed alphabetically below):
321321
* [ionic](#ionic)
322322
* [azure_iot](#azure_iot)
323323
* [irccat](#irccat)
324+
* [launch](#launch)
324325
* [linuxnotify](#linuxnotify)
325326
* [log](#log)
326327
* mastodon (see [tootpaste](#tootpaste))
@@ -1285,6 +1286,30 @@ targets = {
12851286
Requires:
12861287
* gobject-introspection Python bindings
12871288
1289+
### `launch`
1290+
1291+
The `launch` target excutes the specified program and its arguments. It is similar
1292+
to `pipe` but it doesn't open a pipe to the program. It provides stdout as response
1293+
to configured queue.
1294+
Example use cases are f.e. IoT buttons which publish a message when they are pushed
1295+
and the excute an external program. It is also a clone of [mqtt-launcher](https://github.com/jpmens/mqtt-launcher).
1296+
1297+
```ini
1298+
[config:launch]
1299+
targets = {
1300+
# full_topic, topic[0], topic[1], args[0], .....
1301+
'touch' : [ None,0,0,'touch', '/tmp/executed' ],
1302+
'fritzctl' : [ None,0,0,'/usr/bin/fritzctl', 'temperature', "{args[0]}", "{args[1]}" ]
1303+
'backup' : ["response/{topic[1]}/{topic[2]}",0,0,'/usr/bin/sudo','/usr/sbin/dirvish','--vault', "{args[0]}" ],
1304+
}
1305+
```
1306+
1307+
To pass the published data (json args array) to the command, use `{args[0]}` and `{args[1]}` which then gets replaced. Message looks like `'{ "args" : ["' + temp + '","' + room + '"] }'` for `fritzctl`.
1308+
outgoing_topic is constructed by parts of incoming topic or as full_incoming topic.
1309+
1310+
Note, that for each message targeted to the `launch` service, a new process is
1311+
spawned (fork/exec), so it is quite "expensive".
1312+
12881313
### `log`
12891314
12901315
The `log` service allows us to use the logging system in use by _mqttwarn_

etc/mqttwarn.openrc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/sbin/openrc-run
2+
3+
command="/usr/bin/python /opt/mqttwarn/mqttwarn.py"
4+
command_args="${MQTTWARN_OPTIONS}"
5+
command_background=yes
6+
pidfile=/run/mqttwarn.pid
7+
directory=/opt/mqttwarn
8+
9+
name="mqttwarn"
10+
description="mqttwarn pluggable mqtt notification service"
11+
12+
depend() {
13+
need net
14+
}

mqttwarn/services/launch.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
__author__ = 'Tobias Brunner <tobias()tobru.ch>'
5+
__copyright__ = 'Copyright 2016 Tobias Brunner / 2021 Joerg Gollnick'
6+
__license__ = """Eclipse Public License - v 1.0 (http://www.eclipse.org/legal/epl-v10.html)"""
7+
8+
import subprocess
9+
import json
10+
from pipes import quote
11+
from six import string_types
12+
13+
def plugin(srv, item):
14+
15+
srv.logging.debug("*** MODULE=%s: service=%s, target=%s", __file__, item.service, item.target)
16+
17+
# same as for ssh - extract args from json item.message
18+
args=json.loads(item.message)["args"]
19+
20+
if type(args) is list and len(args) == 1:
21+
args=args[0]
22+
23+
if type(args) is list:
24+
args=tuple([ quote(v) for v in args ]) #escape the shell args
25+
elif type(args) is str or type(args) is unicode:
26+
args=(quote(args),)
27+
28+
topic=list(map( lambda x: quote(x), item.topic.split('/') ))
29+
outgoing_topic = item.addrs[0].format(full_topic=quote(item.topic),topic=topic)
30+
qos = item.addrs[1]
31+
retain = item.addrs[2]
32+
addrs = item.addrs[3:]
33+
# replace args[0], args[1] ...
34+
cmd = [i.format(args=args) for i in addrs]
35+
srv.logging.debug("*** MODULE=%s: service=%s, command=%s outgoing_topic=%s", __file__, item.service, str( cmd ),outgoing_topic)
36+
37+
try:
38+
res = subprocess.check_output(cmd, stdin=None, stderr=subprocess.STDOUT, shell=False, universal_newlines=True, cwd='/tmp')
39+
except Exception as e:
40+
srv.logging.warning("Cannot execute %s because %s" % (cmd, e))
41+
return False
42+
43+
if outgoing_topic is not None:
44+
outgoing_payload = res.rstrip('\n')
45+
if isinstance(outgoing_payload, string_types):
46+
outgoing_payload = bytearray(outgoing_payload, encoding='utf-8')
47+
try:
48+
srv.mqttc.publish(outgoing_topic, outgoing_payload, qos=qos, retain=retain)
49+
except Exception as e:
50+
srv.logging.warning("Cannot PUBlish response %s: %s" % (outgoing_topic, e))
51+
return False
52+
53+
return True

0 commit comments

Comments
 (0)