UPS monitor - back end - proof of concept
- There exists a mechanism (unspecified) for acquiring status information from an Uninterruptible Power Supply;
- That mechanism is available to Node-RED; and
- There exists a Node-RED flow (unspecified) that is capable of interpreting the UPS status information and making decisions about when devices should power down and in what order.
The goal is to show that decisions made by Node-RED can be distributed via MQTT, so that client systems can react appropriately.
Two hosts are involved in this proof of concept:
A "coordinator". Assumed to be a Raspberry Pi running IOTstack with at least the following containers:
- Mosquitto - the MQTT broker role
- Node-RED - the MQTT publisher role
The "test device". Assumed to be another Raspberry Pi running a:
- bash script - the MQTT subscriber role
- There is no dependence on Node-RED. Any device capable of calling
mosquitto_pubcould be the publisher.
- There is no requirement for the Mosquitto and Node-RED containers to be running on the same Raspberry Pi. It is simply the most likely arrangement.
- There is no requirement for the "coordinator" and "test device" to be different machines. It is perfectly possible (and probably desirable) for Node-RED to be able to send a "shut down" signal to the machine it is running on.
It is just simpler to keep things straight in your mind if you assume two hosts, with coordinator and test device roles.
Your test device will need the Mosquitto clients (
mosquitto_pub). On Raspbian, you install these with:
$ sudo apt update $ sudo apt install -y mosquitto_clients
On macOS, use HomeBrew and run:
$ brew update $ brew install mosquitto
- This installs the Mosquitto broker as well but does not activate it. Just ignore the messages telling you how to activate it.
For Windows, see mosquitto.org/download.
Are you a Windows user?
The scripts shown here should be created on your Raspberry Pi. Please do not make the mistake of selecting the text, copying it into a text editor on your Windows machine, saving the file, and then moving the file to your Raspberry Pi. Unless you take precautions, Windows will add its
0x0d 0x0a (CR+LF) line endings and those will stop things from working properly on your Raspberry Pi.
Script – power_monitor.sh
This script needs to be installed on the "test device". If you have five test devices, it needs to be installed on each one.
Replace the right hand side of the "BROKER" variable with the domain name or IP address of your coordinator (ie the Raspberry Pi running the Mosquitto container under IOTstack).
Assumed installation location:
~/.local/bindoes not exist, create it, then logout and login so it gets added to your PATH.
#!/usr/bin/env bash # set parameters SCRIPT=$(basename "$0") TOPIC="grid/begone" BROKER="iot-hub.local" # is this script running in the foreground or background? if [ "$(tty)" = "not a tty" ] ; then # background! redirect all output to log LOGFILE="$HOME/Logs/$SCRIPT.log" mkdir -p $(dirname "$LOGFILE") touch "$LOGFILE" exec >> "$LOGFILE" exec 2>> "$LOGFILE" # assume started from crontab @reboot and sleep to # give the network time to start up and stabilise sleep 15 fi echo "$(date) $SCRIPT: Listening for $TOPIC from $BROKER" # infinite loop while [ 0 ] ; do # wait (indefinitely) for exactly one message or an error condition LEVEL=$(mosquitto_sub -C 1 -v -h "$BROKER" -t "$TOPIC" -F "%p") EXITCODE=$? # exit code zero means "message received" if [ $EXITCODE -eq 0 ] ; then # vector on event case "$LEVEL" in "1" ) echo "$(date) $SCRIPT: responding to level 1 power-down message" ;; "2" ) echo "$(date) $SCRIPT: responding to level 2 power-down message" sudo shutdown -h +1 "$SCRIPT scheduling shutdown in one minute" ;; *) echo "$(date) $SCRIPT: Unexpected power-down level = $LEVEL" ;; esac else echo "$(date) $SCRIPT: non-zero return code $EXITCODE - will retry" sleep 15 fi done echo "$(date) $SCRIPT: Unexpected exit from infinite loop"
The script subscribes to the "grid/begone" topic. It expects a message payload consisting of a single digit:
1simply echoes a message to the log
2echoes a message to the log and then initiates a shutdown of the machine on which it is running.
Any other number results in an error message appearing in the log.
Unless explicitly killed, the script loops indefinitely so it should ride through transient outages like Mosquitto container restarts.
The first three lines in the following should be in every
crontab. The key ingredient for this project is the last line. It fires off the script at reboot time.
SHELL=/bin/bash HOME=/home/pi PATH=/home/pi/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin @reboot power_monitor.sh
If you don't have a crontab already, just get those lines into a file (eg
mycrontab.txt) then load that file as your crontab by:
$ crontab mycrontab.txt
Google is your friend if you need more help than that.
A reboot is required to cause
cron to activate
$ sudo reboot
After the machine comes up, wait 30 seconds or so then make sure the following log file has been created:
You should expect to see at least:
«date & time» power_monitor.sh: Listening for grid/begone from «BROKER»
If you don't see that, check your work.
- The script does not write to the log immediately. Because the script is started during a reboot, it needs to wait for the network to be ready. It does that by sleeping for 15 seconds. Don't be in too much of a hurry.
Add a new empty flow and give it an appropriate name (eg "UPS Responder").
Drag the following nodes onto the canvas from the palette:
- Two Inject nodes,
- One debug node
- One MQTT out node
Wire them up as per the following:
The first Inject node should be set:
- Name: Trigger Level 1
- msg.payload =  1
- msg.topic = [az] grid/begone
The second Inject node should be set:
- Name: Trigger Level 2
- msg.payload =  2
- msg.topic = [az] grid/begone
The MQTT Out node should point to "mosquitto" (the container name) on port 1883. See mqtt-in-node if you don't know how to do that. MQTT in and out nodes are configured identically. Give it a name like "Publish" or "grid/begone".
It is a good idea to set the Debug node to output the "complete message object" so that you get to see both the topic and message payload in the debug window.
On the test device, you can place a "watch" on the log like this:
$ tail -f tail -f Logs/power_monitor.sh.log
When you click the "Trigger Level 1" button on the coordinator, Node-RED will "publish" the equivalent of:
$ mosquitto_pub -t "grid/begone" -m "1"
power_monitor.shscript (the "subscriber") running on the test device will receive that message from the broker (the Mosquitto container) and write the following to the log:
«date & time» power_monitor.sh: responding to level 1 power-down message
When you click the "Trigger Level 2" button on the coordinator, Node-RED will initiate the equivalent of this:
$ mosquitto_pub -t "grid/begone" -m "2"
power_monitor.shscript running on the test device will receive that message and:
write the following to the log:
«date & time» power_monitor.sh: responding to level 2 power-down message
initiate a system shutdown, scheduled for one minute in the future.
Connect to the test device:
$ ssh test-dev.local
Place a watch on the log:
pi@test-dev:~ $ tail -f Logs/power_monitor.sh.log Wed 02 Jun 2021 11:22:48 PM AEST power_monitor.sh: Listening for grid/begone from iot-hub.local
Click the "Trigger Level 1" button in the Node-RED flow. Node-RED debug output is:
02/06/2021, 11:23:53 pmnode: 78bedf81.87f74 grid/begone : msg : Object object _msgid: "c745484a.aec318" payload: 1 topic: "grid/begone"
Test device log response is:
Wed 02 Jun 2021 11:23:53 PM AEST power_monitor.sh: responding to level 1 power-down message
Click the "Trigger Level 2" button in the Node-RED flow. Node-RED debug output is:
02/06/2021, 11:25:37 pmnode: 78bedf81.87f74 grid/begone : msg : Object object _msgid: "beaa1894.accae8" payload: 2 topic: "grid/begone"
Test device response is:
Wed 02 Jun 2021 11:25:37 PM AEST power_monitor.sh: responding to level 2 power-down message Broadcast message from root@test-dev (Wed 2021-06-02 23:25:37 AEST): power_monitor.sh scheduling shutdown in one minute The system is going down for poweroff at Wed 2021-06-02 23:26:37 AEST! Shutdown scheduled for Wed 2021-06-02 23:26:37 AEST, use 'shutdown -c' to cancel. Broadcast message from root@test-dev (Wed 2021-06-02 23:26:37 AEST): power_monitor.sh scheduling shutdown in one minute The system is going down for poweroff NOW! Connection to test-dev.local closed by remote host. Connection to test-dev.local closed. [Wed Jun 02 23:26:37:~] $
Extending the script
The example script only handles two messages but it is written as a
casestatement to make it easy to add more levels.
The script can easily do things like shut down IOTstack, as in:
docker-compose -f "$HOME/IOTstack/docker-compose.yml" down
The script is not limited to running commands on the local machine. Suppose you have a device (eg your home router) which is not capable of running a script like
power_monitor.shbut is capable of receiving shutdown instructions via
ssh. A Raspberry Pi running
power_monitor.shcould easily relay such instructions.
The script could be extended to send acknowledgements back to Node-RED by using
In the current design, Node-RED publishes to the "grid/begone" topic without using the retain flag. This implies that any subscriber that is in the process of rebooting or not otherwise listening may not hear the message. Adding the retain flag would address this weakness but some care would need to be taken to prevent stale "shutdown" messages from preventing reboots once the triggering UPS condition was resolved.
Extending the concept
This is a proof of concept. Although the
power_monitor.sh script "works", it is not particularly elegant and its error-handling is unsophisticated. It also depends on the availability of
More elegant and sophisticated solutions with better error-handling could involve techniques such as are described at Python MQTT publish and subscribe.
Take the example above of a home router. While I can connect to my own home router via
ssh, the most I can do is cause it to reboot. It has no concept of shutdown or power-off so, during a mains power outage, it's going to sit there draining power from the UPS.
The solution, of course, is a smart switch, flashed with firmware that subscribes to "grid/begone" and removes power from the router. Contrived scenario? Perhaps. The idea is to help you understand MQTT's role as an enabling technology.
Here's another idea. Suppose you add a "level 0". All it does is publish the equivalent of "I've heard you" via MQTT. Now you have a mechanism by which Node-RED can test that all the devices that should be listening are indeed listening.