| actions | ||
| data | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.example.yaml | ||
| Readme.md | ||
MQTT Relay
A simple service that can be used to run scripts and publish MQTT messages when others are received.
Configuring the service itself
The service is configured via a yaml file.
See config.example.yaml for all configuration options.
Configuring actions
Actions define what happens when a MQTT message is received. They are managed by simply placing files
in the actions directory. The service will only subscribe to topics for which at least one action is defined.
In general, there are two types of actions:
- Scripts can be used to run whatever you want
- They can also trigger sending new MQTT messages in response.
- Mappings can be used if you only want to send MQTT messages in response.
- There is a simple mechanism to add conditions based on the message payload.
You can find examples for both in the actions directory. These examples are also "active" if you run the service
locally with cargo run.
Scripts
To link a script to a MQTT topic, simply place an executable file in a directory matching the topic.
For the topic examples/topic, create the file actions/examples/topic.sh (or any other file extension).
Note that if the file is not executable, the service will ignore it and not subscribe to the corresponding topic, unless it is a yaml file (see mappings).
An example script could look like this:
#!/usr/bin/env bash
current_hour=$(date +%H)
if (( current_hour >= 12 )); then
echo "examples/response" >&3
echo "It's past noon" >&3
else
echo "examples/response" >&3
echo "It's not yet noon" >&3
fi
There are a few rules for how scripts should behave:
- The topic will be in the first argument (
$1) - The payload will be in stdin (you can use
input=$(cat)to get it)- The payload might be multiple lines
- Ignoring stdin is no problem
- Anything printed to stdout is considered to be an info. It will be logged as a
debugmessage - Anything printed to stderr is considered an error message. It will be logged as a
warning - Printing to fd3 (file descriptor 3) tells the service to send a MQTT message
- The first line defines the topic
- The second line defines the payload
- If you don't want to include a payload, send another newline
- If you don't, the message will not be sent
- Sending multiple messages is allowed
- Longer running scripts are allowed, but you should make sure that the script does end
- The service will create a background thread to read the scripts outputs, so never ending scripts are a memory leak
- Note that stdout and stderr are only read once the script is finished
- Symlinks are followed
- Technically, you can use any executable, not just scripts
Mappings
If you just want to send a new message once another has been received, you can use a mapping file. Mapping files can be placed just like scripts, but they do not need to be executable.
This first example file will result in the service subscribing to the MQTT topic my/topic (based on
its directory and file name). When a message on the topic is received, the service will publish a message
to other/topic with the given payload.
# ./actions/my/topic.yaml
messages:
- topic: "other/topic"
message: |-
{ "data": "just a string" }
To send multiple messages when a single one is received:
# ./actions/my/topic.yaml
messages:
- topic: "other/topic1"
message: |-
{ "info": "first message" }
- topic: "other/topic2"
message: |-
{ "info": "second message" }
If you need to listen to multiple topics, create multiple files.
This example will subscribe to my/first/topic and my/second/topic:
# ./actions/my/first/topic.yaml
messages:
- topic: "other/topic1"
message: |-
{ "info": "first message" }
# ./actions/my/second/topic.yaml
messages:
- topic: "other/topic1"
message: |-
{ "info": "second message" }
You can attach conditions to messages. The service will evaluate the condition using jq and
only send the messages if it evaluates to "true".
In the following example, the message will only be sent if the received message includes a
JSON that contains {"hello": "world"}:
# ./actions/my/topic.yaml
conditions:
my_condition_name: .hello == "world"
messages:
- condition: "my_condition_name"
topic: "other/topic"
message: |-
{ "data": "just a string" }
Multiple actions for the same topic
You can have multiple actions for the same topic. They will all be run in parallel.
This can be done by adding a + to the end of the file / directory name (before the extension).
The + itself and everything after it will be ignored.
For example, all these files would be tied to the topic my/topic:
./actions/my/topic.sh./actions/my/topic.yaml./actions/my/topic+.sh./actions/my/topic+a_comment.yaml./actions/my+another_dir/topic.sh
Disabling actions
Files and directories starting with a . are ignored.
This way, you can disable actions by renaming them.