This gist answers a Discord question. It explains my approach to collecting CPU temperatures by sending the data as an MQTT payload.
I like to think of this approach as working with the MING (Mosquitto, InfluxDB, Node-RED, Grafana) paradigm rather than fighting against it (eg using approaches like SSH calling out of the Node-RED container).
The approach is not tied to the Raspberry Pi that is running IOTstack. If you have several Raspberry Pis, they can all log their temperatures to the Raspberry Pi running IOTstack using exactly the same mechanism.
The concept can also be extended to other types of computer. I'm using it to collect CPU temperatures for macOS systems too.
Be very careful about copying and pasting text from this gist. Unless you take precautions, Windows will add its CR+LF line-endings and those will cause problems.
-
Assumed path name:
~/.local/bin/publish_rpi_temperature
-
Script content:
#!/usr/bin/env bash mkdir -p ~/Logs mosquitto_pub -h mosquitto.mydomain.com -t "/mytopic/computer/pi" -m "{\ \"host\": \"$HOSTNAME\", \ \"temp\": $(vcgencmd measure_temp | cut -c 6-9)\ }"
The script assumes the Mosquitto clients are installed:
$ sudo apt install mosquitto-clients
You will need to customise:
- The target host "mosquitto.mydomain.com" using the fully-qualified domain name, or host name, or multicast DNS name, or IP address of the Raspberry Pi running IOTstack.
- The topic "/mytopic/computer/pi". Although I did not need to use it, I originally added the "/pi" suffix to the topic string in case I needed to know the type of computer that had generated the payload. My Raspberry Pi's use "/pi"; my macOS machines "/mac".
This script is specific to the Raspberry Pi. To port the script to a different type of host you will need to:
- find an alternative for
vcgencmd
; and - make sure that the HOSTNAME environment variable contains a sensible value (eg on macOS, it contains the fully-qualified domain name rather than the host name).
The MQTT messages sent by this script look like this:
/mytopic/computer/mac {"host": "bauxite", "temp": 46.5}
/mytopic/computer/mac {"host": "magnetite", "temp": 28.8}
/mytopic/computer/mac {"host": "gravel", "temp": 35.2}
/mytopic/computer/mac {"host": "marble", "temp": 34.2}
/mytopic/computer/pi {"host": "octopi", "temp": 41.3}
/mytopic/computer/pi {"host": "new-dev", "temp": 43.3}
/mytopic/computer/pi {"host": "iot-hub", "temp": 46.2}
/mytopic/computer/pi {"host": "sec-dev", "temp": 45.2}
$ mkdir -p ~/Logs
The collection script does not normally produce output unless there is an error condition. If the script does not appear to be working, the place to start looking is:
$ tail ~/Logs/publish_rpi_temperature.log
-
Preamble (should be common to all crontab files):
SHELL=/bin/bash HOME=/home/pi PATH=/home/pi/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
-
Event trigger:
# report system temperature every 5 minutes */5 * * * * publish_rpi_temperature >>./Logs/publish_rpi_temperature.log 2>&1
The InfluxDB database needs to be initialised before you can write to it. In this example, the database name is "computer".
$ docker exec -it influxdb influx
> create database computer
> exit
$
Within the "computer" database, each host computer logging temperatures generates its own series:
temperature,system=«hostname»
where "«hostname»" corresponds with the "host" key in the MQTT payload.
A Node-RED three-node flow handles the messages:
-
an MQTT-in node subscribing to the appropriate topic. In this example the topic is "/mytopic/computer/+" where "+" is a single-level wildcard.
-
a Change node to prepare the payload for insertion into InfluxDB:
[ { "temp": payload.temp },{ "system": payload.host } ]
In words:
- temperature is added as a field (data);
- the name of the host reporting the temperature is added as a tag (metadata)
-
an InfluxDB-out node inserts the prepared payload into the database named "computer".
Given the incoming MQTT message:
/mytopic/computer/pi {"host": "iot-hub", "temp": 46.2}
The overall effect of the flow is:
> USE computer
> INSERT temperature,system='iot-hub' temp=46.2
> exit
You should be able to copy the JSON content below and use the "Import" command in the main menu (three horizontal bars "≡" at the top, right of the Node-Red window) to create a new flow containing the three nodes.
Depending on how your system is set up, the flow may need some tinkering before it works.
[
{
"id": "cc5ffba5.fdc7c8",
"type": "tab",
"label": "CPU Temperatures",
"disabled": false,
"info": ""
},
{
"id": "796d8452.225294",
"type": "change",
"z": "cc5ffba5.fdc7c8",
"name": "prepare fields",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "[\t {\t \"temp\": payload.temp\t },{\t \"system\": payload.host\t }\t]\t",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 340,
"y": 100,
"wires": [
[
"d27128f3.4050c8"
]
]
},
{
"id": "d27128f3.4050c8",
"type": "influxdb out",
"z": "cc5ffba5.fdc7c8",
"influxdb": "626e8bc5.4c702c",
"name": "write to InfluxDB",
"measurement": "temperature",
"precision": "",
"retentionPolicy": "",
"database": "",
"retentionPolicyV18Flux": "",
"org": "",
"bucket": "",
"x": 570,
"y": 100,
"wires": []
},
{
"id": "39543010.e0258",
"type": "mqtt in",
"z": "cc5ffba5.fdc7c8",
"name": "/mytopic/computer/+",
"topic": "/mytopic/computer/+",
"qos": "2",
"datatype": "json",
"broker": "c5d29fb5.89907",
"x": 130,
"y": 100,
"wires": [
[
"796d8452.225294"
]
]
},
{
"id": "626e8bc5.4c702c",
"type": "influxdb",
"hostname": "influxdb",
"port": "8086",
"protocol": "http",
"database": "computer",
"name": "",
"usetls": false,
"tls": "",
"influxdbVersion": "1.x"
},
{
"id": "c5d29fb5.89907",
"type": "mqtt-broker",
"name": "Docker MQTT",
"broker": "mosquitto",
"port": "1883",
"clientid": "",
"usetls": false,
"compatmode": false,
"protocolVersion": 4,
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": ""
}
]
$ docker exec -it influxdb influx -precision=rfc3339
Connected to http://localhost:8086 version 1.8.9
InfluxDB shell version: 1.8.9
> use computer
Using database computer
> show series
key
---
temperature,system=bauxite
temperature,system=gravel
temperature,system=iot-hub
temperature,system=magnetite
temperature,system=marble
temperature,system=new-dev
temperature,system=octopi
temperature,system=sec-dev
temperature,system=sjgair
> select * from temperature where time > now() - 10m tz('Australia/Sydney')
name: temperature
time system temp
---- ------ ----
2021-08-22T23:40:00.271972208+10:00 marble 35.2
2021-08-22T23:40:00.674021388+10:00 magnetite 29.6
2021-08-22T23:40:00.937395236+10:00 gravel 36.6
2021-08-22T23:40:01.065496792+10:00 bauxite 46
2021-08-22T23:40:01.220100822+10:00 sec-dev 46.7
2021-08-22T23:40:01.87568606+10:00 iot-hub 47.7
2021-08-22T23:40:01.892511832+10:00 octopi 40.8
2021-08-22T23:40:02.021640681+10:00 new-dev 42.8
2021-08-22T23:45:00.210631999+10:00 magnetite 28.9
2021-08-22T23:45:00.453033759+10:00 gravel 35.6
2021-08-22T23:45:00.545580449+10:00 bauxite 46.4
2021-08-22T23:45:00.840558319+10:00 marble 35.6
2021-08-22T23:45:01.133290607+10:00 new-dev 43.3
2021-08-22T23:45:01.319572925+10:00 sec-dev 46.2
2021-08-22T23:45:01.977801647+10:00 iot-hub 46.7
2021-08-22T23:45:01.99638355+10:00 octopi 41.3
> exit
$
Shows a mixture of Raspberry Pi and macOS CPU temperatures.
The "$" is the system prompt. When you see something like:
it means "type the command
ls fred.png
and press return. Putting the "$" in front helps separate commands from responses, as in:Occasionally you will also see "#" being used as the system prompt. It usually means you are logged-in as the root user. You will often see this pattern if you use the
docker exec
command to run a shell inside a container.To answer your question, yes you do need to edit the command. Let's start with the basic syntax:
You replace «host» with some reference to the host running your Mosquitto broker. It can be:
Only you will know what will actually work in your environment.
For the «topic» string, you need to pick something that is appropriate to your needs. You'll find heaps of guidance on the web. I use two topics:
but that's only because I send slightly different payloads depending on the platform - all the CPU temperature readings still wind up in the same database.
You'll also see advice on the web about not starting a topic string with a "/" but, basically, it doesn't matter. You just need something that a subscriber (like Node-RED) can use so that the traffic winds up in the right place.
If it helps, try imagining that you have a whole bunch of computers sending their temperatures, plus a whole bunch of sensors reporting temperature, humidity, air pressure, wind speed, plus sensors on doors that send alerts when they are opened, light switches that send status updates as you turn them on and off, and so on. Now imagine you have several Node-RED flows. One handles computer temperatures, another all the weather data, one for the doors, another for the lights. You need a simple mechanism for getting the payload (the JSON string passed in the
-m
option) to the right flow. That's what topics are. It's also not just Node-RED. If you have smart light switches, you might want to send remote "turn on" commands so each switch will want to subscribe to its own topic - so the switch in the dining room doesn't turn on when you're trying to turn on the kitchen lights.In short, topic strings are filters and the answer is "whatever works in your situation".
The other thing I noticed in what you wrote was this:
I "get" what you are trying to do and I understand how you can arrive at that as a possible answer, but it reveals a number of misunderstandings so I'll try to unpack those.
I'll start with the port number. The default is 1883 so you don't actually need to specify it but, if you did want to pass the port number explicitly, you do it like this:
How do I know it's the
-p
option? You can get help from either (or both) of the following commands:Next, the actual protocol being used is "mqtt". That is a tcp-based protocol but "tcp" isn't a synonym. Again, "mqtt" is the default so you don't need to provide it. However, if you really did want to specify it explicitly, you would have to make wholesale changes to the entire command:
See how the
-h
,-p
and-t
options have all disappeared with everything being wrapped into a URL.The last point is about "mosquitto". In the IOTstack context (and Docker contexts generally), the names of containers should probably be treated as reserved words. For example, from the perspective of the Node-RED container, the Mosquitto container is a host named "mosquitto". That's why you can set up an MQTT-in node to refer to a server named "mosquitto" on port 1883. Docker manages all that for you.
On a totally separate host (Pi, Mac, Windows), you could definitely set up a hostname-to-IP address mapping associating the name "mosquitto" with the IP address of the host running the Mosquitto broker. That will work too.
But, where I start to get twitchy is trying to do that on the host that is running Mosquitto in a docker container. Presumably you're as interested in the CPU temp of the Pi running IOTstack as you are of any other computer - yes?
Inside container-space, Docker maps the container name "mosquitto" to the IP address of the container (as I said above).
Outside container-space, the correct IP address for the mapping would either be the Raspberry Pi's IP address or the loopback address 127.0.0.1.
What I don't know is what happens if you have the same name defined by both docker and the Raspberry Pi's
/etc/hosts
. It might work. Or it might break Node-RED.I'm not saying "mosquitto" is definitely wrong because I have not tested it to see what happens. I'm just saying "be cautious" and think back to this discussion if you get any weird behaviour.
Hope this helps.