Skip to content

Instantly share code, notes, and snippets.

@Paraphraser
Created August 22, 2021 14:05
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Paraphraser/bd4eb1ebd0a7d94bd50111efba7bfbe2 to your computer and use it in GitHub Desktop.
Save Paraphraser/bd4eb1ebd0a7d94bd50111efba7bfbe2 to your computer and use it in GitHub Desktop.
IOTstack Tutorial: Logging CPU Temperatures

IOTstack Tutorial: Logging CPU Temperatures

introduction

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.

if you are a Windows user…

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.

collection script

  • 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}

scaffolding

$ 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

cron job

  • 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

influx database

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.

three-node flow

three-node flow

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": ""
    }
]

example data

$ 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
$

grafana visualisation

Shows a mixture of Raspberry Pi and macOS CPU temperatures.

CPU temperature chart

@larduino
Copy link

### I got lost at collection script

### **I did the following on my raspberry pi **

#!/usr/bin/env bash

**I tried to go to the usr/bin/env but I get

root@PiBullseye:/usr/bin# cd env
bash: cd: env: Not a directory
root@PiBullseye:/usr/bin#**
### Can you refer me to somewhere to learn how to do what you are talking about in this step ?
( I already copied the Node Red Flow and made the Influx database you showed )
Thanks

@Paraphraser
Copy link
Author

I will break it down.

The # at the start means "this is a comment" so, in principle, the rest of the line does not matter.

The combination of #! is a special kind of comment which is treated as a hint to the calling shell as to how it should launch the script.

Depending on who you listen to, #! is pronounced as one of "shbang" (the "sh" sound in "shoosh"), "shabang" or "shebang". I first heard it as "she-bang" in the early 1980s but, these days, that can sound quite sexist so I recommend "shbang".

Traditionally, you would see something like this at the start of any shell script:

#!/bin/sh

where sh could be sh or or bash or zsh or ksh or any other kind of shell (there are an awful lot of shells).

Now, let's make two assumptions:

  1. The shell you are actually running (the thing giving you the "$" prompt and where you are typing commands) is bash. That's the default on a Raspberry Pi.
  2. You type the name of a script (let's call it fred.sh) in a way that causes bash to execute it.

If you don't have a #! hint then bash (the running shell) assumes the script should be executed with a copy of itself.

The problem is that all shells differ in their precise features. You might, therefore, create a script which depends on it being run by sh. It might fail or do the wrong thing if it is run by bash. This is where the #! hint comes in. If fred.sh starts with:

#!/bin/sh

then it doesn't matter whether the shell you are actually running is sh or bash or anything else. When the shell you are running starts fred.sh it will always start it with sh.

All clear so far?

But then comes another problem. What happens if you have multiple versions of a particular shell installed? This can happen for any number of reasons. You might be testing a new version of a shell. Or, if you are a macOS user who favours bash over zsh, the version of bash Apple ships by default is 3.2.57 while up-to-date bash (which you get from HomeBrew) is around 5.1.8.

I read somewhere that this has something to do with Apple not liking the bash licence agreement but I don't know that for a fact and I don't know any of the details.

I'm a Mac person but I want up-to-date bash so I wind up with both versions installed:

$ which -a bash
/usr/local/bin/bash
/bin/bash

On macOS, it's difficult-to-impossible to change anything in /bin because it's a protected directory.

Do you see where this is heading? Suppose my fred.sh script starts with:

#!/bin/bash

That's going to tell the calling shell (which is actually /usr/local/bin/bash) to use /bin/bash which is, of course, the obsolete version so it might not work if I use features in fred.sh that are only present in the up-to-date version of bash.

And that's where this syntax comes in:

#!/usr/bin/env bash

The env program looks at the bash argument and says "go find the version of bash which is actually in use", then use that version to run the fred.sh script. So, the practical effect on my Mac is the same as if the script had started with:

#!/usr/local/bin/bash

The reason for using the env bash syntax rather than hard-coding /usr/local/bin/bash is to make the script portable. As long as there's a bash installed on the target system (eg Raspberry Pi), env will find it and use it.

Multiple versions of shells being installed on a system, or multiple potential installation locations depending on the platform (macOS vs Linux vs something else), and automatically figuring out which is the correct shell to use when starting a script, is a very common problem. The solution of /usr/bin/env plus an argument of the shell name to use to run the script is a pattern that all shell scripts are supposed to adopt. Granted, not every example you find on the web will use this convention - but they should.

Make sense?


With all that in mind, /usr/bin/env is a file, not a directory:

pi@sec-dev:~$ ls -l /usr/bin/env
-rwxr-xr-x 1 root root 35168 Sep 22  2020 /usr/bin/env

It's a compiled binary program:

pi@sec-dev:~$ file /usr/bin/env
/usr/bin/env: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=7b483edc03dc14e7f9d5f1761405375f975ca62a, for GNU/Linux 3.2.0, stripped

If you want to know more about how it works:

$ man env

If you are not familiar with Unix text editors, you could try this:

  1. Make sure the target directory exists:

    $ mkdir -p ~/.local/bin
    
  2. Make that directory your working directory:

    $ cd ~/.local/bin
    
  3. Start this command (which will appear to do nothing - don't panic):

    $ cat >publish_rpi_temperature
    
  4. Select everything in the box below and copy it to your clipboard:

    #!/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)\
    }"
    
  5. Switch back to the terminal where that cat command from step 3 is still waiting and paste the contents of the clipboard.

  6. Hold down the control key, then press the d key. Control+D means "end input". The terminal "$" prompt should come back.

  7. Check your work by listing the file you just created:

    $ cat publish_rpi_temperature
    

    The output should be the same as the box above. If it isn't, repeat from step 3.

    Also note the subtle difference between the cat command here and the one in step 3. Step 3 has the > so it says "read from the keyboard and write to the file named publish_rpi_temperature". The command here in step 7 doesn't have the > so it says "read from the file named publish_rpi_temperature and write to the console".

  8. Make the script executable:

    $ chmod +x publish_rpi_temperature
    

After that, it should work properly.

@larduino
Copy link

wow - thanks for explaining that - I will have to read the first part three or four times to really get all the nuances of what you explained . But I just did the bottom part of what you wrote on how to install it and it all went according to plan.
I am sure your info saved me many many hours of trying to figure it out on my own looking up stuff.
Thanks again - I will go on now and see if I get some success with the rest of the temps to Grafana part of it.

@larduino
Copy link

larduino commented Dec 26, 2021

I customized ( see below) with the mosquitto address I am using works in telegraf.conf for a solar monitoring program
Do I also need to customize the mytopic part ?

#!/usr/bin/env bash

mkdir -p ~/Logs

mosquitto_pub -h tcp://mosquitto:1883 -t "/mytopic/computer/pi" -m "{
"host": "$HOSTNAME",
"temp": $(vcgencmd measure_temp | cut -c 6-9)
}"

I get this error too
pi@PiBullseye:~ $ $ tail /Logs/publish_rpi_temperature.log
-bash: $: command not found
pi@PiBullseye:
$ ls
2021-12-05-132440_1920x1080_scrot.png Documents Logs Public
Bookshelf Downloads Music Templates
Desktop IOTstack Pictures Videos

I will try again tomorrow , my 1 AM brain shutdown is happening ! ( no idea why it struck out lines above - I didn't do that )

@Paraphraser
Copy link
Author

Paraphraser commented Dec 26, 2021

The "$" is the system prompt. When you see something like:

$ ls fred.png

it means "type the command ls fred.png and press return. Putting the "$" in front helps separate commands from responses, as in:

$ ls fred.png
ls: cannot access 'fred.png': No such file or directory

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:

mosquitto_pub -h «host» -t "«topic»"

You replace «host» with some reference to the host running your Mosquitto broker. It can be:

  • A fully-qualified domain name like "pibullseye.domain.com" but that assumes you have a local Domain Name Server which is able to resolve "pibullseye.domain.com" to an IP address; or
  • A multicast domain name like "pibullseye.local"; or
  • the IP address of the host running your Mosquitto broker.

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:

/house/computer/pi
/house/computer/mac

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:

mosquitto_pub -h tcp://mosquitto:1883 

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:

mosquitto_pub -h «host» -p 1883 -t "«topic»"

How do I know it's the -p option? You can get help from either (or both) of the following commands:

$ mosquitto_pub --help
$ man mosquitto_pub

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:

mosquitto_pub -L mqtt://«host»:1883/«topic»

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.

@larduino
Copy link

Thanks for the info . I am trying to understand what you wrote and to make this work.
I might not get it solved tonight though.

@larduino
Copy link

I am studying the info above and starting to understand it.
It is going to take some more time to put everything together and get it working .
I haven't given up on it yet. There is a lot to learn .

@larduino
Copy link

larduino commented Dec 29, 2021

I spent some time studying - I had to learn crontab and other things. For some reason I could never get everything to work. But in the process of looking things up I did find a way to get the pi temperature using node red and send it to Grafana . I may revisit your method but for now it is nice to just get something working instead of hours of errors. This is the flow I used - I also have one that also gets all the pi cpu , memory usage, and disc load using same method.
pitemp
[ { "id": "e8af9858e50f8afd", "type": "tab", "label": "Raspberry Pi Temperature ", "disabled": false, "info": "", "env": [] }, { "id": "f9c23d23.c8c24", "type": "exec", "z": "e8af9858e50f8afd", "command": "vcgencmd measure_temp", "addpay": false, "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "name": "Raspberry CPU Temperature", "x": 380, "y": 220, "wires": [ [ "a0ad4644.0efbf8" ], [], [] ] }, { "id": "a0ad4644.0efbf8", "type": "function", "z": "e8af9858e50f8afd", "name": "Extract CPU Temp", "func": "str = msg.payload\nmsg.payload = str.substring(5,9);\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 630, "y": 220, "wires": [ [ "d028027e7fa8023d", "8317134.cff18f" ] ] }, { "id": "a97d78fc.40c268", "type": "inject", "z": "e8af9858e50f8afd", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payloadType": "date", "x": 150, "y": 220, "wires": [ [ "f9c23d23.c8c24" ] ] }, { "id": "8317134.cff18f", "type": "debug", "z": "e8af9858e50f8afd", "name": "", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "x": 870, "y": 280, "wires": [] }, { "id": "d028027e7fa8023d", "type": "influxdb out", "z": "e8af9858e50f8afd", "influxdb": "31eb6050bebfe505", "name": "", "measurement": "pitemp", "precision": "", "retentionPolicy": "", "database": "database", "precisionV18FluxV20": "ms", "retentionPolicyV18Flux": "", "org": "organisation", "bucket": "bucket", "x": 900, "y": 220, "wires": [] }, { "id": "31eb6050bebfe505", "type": "influxdb", "hostname": "127.0.0.1", "port": "8086", "protocol": "http", "database": "pitemp", "name": "influxdb", "usetls": false, "tls": "", "influxdbVersion": "1.x", "url": "http://localhost:8086", "rejectUnauthorized": true } ]

@Paraphraser
Copy link
Author

I'm glad you got it working. Well done! It's a steep learning-curve at the beginning isn't it? I'm a few years ahead of you but I'd still only put myself at maybe 2.5 on a 1..10 scale of understanding all this stuff. Every win is a major victory!!

But I did notice one thing, which is near the end of your JSON:

"influxdb", "hostname": "127.0.0.1", "port": "8086" … "url": "http://localhost:8086" …

That will only work if Node-RED is running in host mode because that's the only time Node-RED can use 127.0.0.1 or localhost to refer to InfluxDB running in a container. In that situation, 127.0.0.1 and localhost mean "this Raspberry Pi".

If Node-RED is running in non-host mode (which is the normal case) 127.0.0.1 and localhost mean "this Node-RED container".

One of the benefits of using Docker is the ability to avoid port conflicts. You lose that ability when you start using host-mode for everything.

I'm not saying running in host-mode is "wrong" or that you should change it. Just keep this in mind for the future because it's one of those things that can turn around and bite you.

@larduino
Copy link

Yes - a lot to learn . I think i do pretty good for a retired guy doing it for a hobby. I have the mosquitto , grafana , portainer, telegraf, and influxdb all set up using IOTStack. I just used the Node Red that came installed with the Raspberry Pi. I mainly use the pi to monitor my solar system . There is a docker program that connects to my solar controllers and converts the modbus data from those into MQTT. From there it goes to mosquitto and then directly to telegraf which sends it to influxdb and Grafana . I use my Windows computer to look at the Grafana running on the Pi. That part works very well and is stable and will start up automatically if I restart the pi. I want to learn how to use Node Red so that instead of just monitoring the solar data - I can start using it to control things. So every bit I learn helps towards that goal. I haven't actually used or monitored the Pi Temps in grafana - I just got it to talk so it wouldn't give me errors. But it is good to know what you said about port conflicts. I will pay attention to that. Maybe I should set up Node Red using IOTStack . Last time I tried that I don't think it installed properly - but maybe that is fixed now - it was awhile ago I did that. Here is picture of my Solar grafana screen .
grafana 1

@Paraphraser
Copy link
Author

Great dashboard! I think you do too. What else is retirement for if not to play with all these gizmos. Never enough hours in the day.

I tend to aim for more compact and less busy dashboards because my spouse grizzles if things don't fit on her iPhone screen. I mean, I built all this for me but it somehow became a kind of community property with r-u-l-e-s. Gotta work 100% of the time. Gotta fit on small screens...

If you are using Node-RED "just as it comes from IOTstack" then it is running in non-host mode. A quick way to check is to run docker ps and look at the ports column. This command cuts it down to just the networking info:

$ docker ps --format "table {{.Names}}\t{{.Ports}}"

Here's an example on a system which has a mixture of containers running in host and non-host mode:

NAMES          PORTS
dhcpd          
subversion     
syslog-ng      
bind9          0.0.0.0:53->53/tcp, 0.0.0.0:953->953/tcp, 0.0.0.0:53->53/udp
portainer-ce   0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp, 9443/tcp

See how there's nothing in the Ports column for dhcpd, subversion and syslog-ng? That means they are running in host mode.

The other containers (bind9 and portainer-ce) have something in the Ports column so they are in non-host mode.

It's not actually quite that simple - what is? - because a container that exposes no ports or hasn't been mapped won't show up in the Ports column even if it is running in non-host mode. But it's close enough for our purposes.

On my more standard IOTstack RPi, the same command gives:

NAMES          PORTS
wireguard      0.0.0.0:51720->51820/udp
pihole         0.0.0.0:53->53/udp, 0.0.0.0:53->53/tcp, 0.0.0.0:67->67/udp, 0.0.0.0:8089->80/tcp
grafana        0.0.0.0:3000->3000/tcp
influxdb       0.0.0.0:8086->8086/tcp
nodered        0.0.0.0:1880->1880/tcp
portainer-ce   0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp, 9443/tcp
mosquitto      0.0.0.0:1883->1883/tcp

They all have something in the ports column so they are all running in non-host mode. If you run the same command on your system and you see the 1880:1880 mapping for Node-RED, it's running in non-host mode. And that means 127.0.0.1 and locahost mean "this container". And that, in turn, means that the flow isn't actually able to send data to InfluxDB. There are several ways around the problem. Instead of either "localhost" or "127.0.0.1", you can use:

  1. The container name "influxdb".
  2. The IP address of one of the Raspberry Pi's interfaces (Ethernet or WiFi).
  3. The domain name you've given to the Raspberry Pi (which will map to one of its IP addresses). This assumes you have a local DNS server.

The "container name" approach is the best. Docker manages the mapping between the names of all the running containers and their dynamically-allocated IP addresses on the internal bridged network. It's like an /etc/hosts file. The "internal bridged network" behaves exactly like a layer two switch. Broadcast traffic is flooded but unicast traffic goes container-to-container.

If you use option 2 or 3 (which are really just option 2) then the traffic is routed out of container-space, to the Raspberry Pi, and then back into container-space again. The traffic undergoes network address translation (NAT) in both directions and because TCP is acknowledged traffic, you've got "payload" and "ack" traffic flying in and out of container-space. It's quite inefficient. Option 1 is much better.

I put together a chart some time back that explains some of this:

docker-networking-quick-reference

Basically, if all of MING (Mosquitto, Influx, Node-RED, Grafana) is in non-host mode then:

  1. Node-RED flows subscribe to the Mosquitto broker as mosquitto:1883.
  2. Node-RED flows forward data to InfluxDB as influxdb:8686.
  3. Grafana charts fetch data from InfluxDB as influxdb:8086.

A mix of Macs and Pis (that's why I wanted a solution that send the temps via MQTT - multiple hosts):

Screen Shot 2021-12-30 at 15 44 40

Various sensors around the place:

Screen Shot 2021-12-30 at 15 45 18

With only a day to go, I don't think we're going to crack 1m of rainfall this year. So close...

Screen Shot 2021-12-30 at 16 07 53

What the solar system and grid are up to. Note the incredibly shitty power we get here. We are supposed to be on a 230V standard. In the lower chart, the green trace (left hand Y axis) should always be in the shaded-green zone. It should only ever have occasional excursions into the shaded-blue zone until whatever problem is causing that is brought under control. It should never be in the shaded-red zone. And yet... Also note that one would expect voltage to be normally distributed. It ain't. It's got a positive skew. Clearly the Poles and Wires people have sufficient control to stop voltage sagging too low, yet faff all in the way of control to stop it from surging too high. I'm afraid I don't believe it. Australia went onto the 230V standard in 2003. "We need time to adapt" is the industry mantra. 18 years not enough? I often think of this when each morning brings news of yet more COVID rules to which mere mortals have to adapt before they've had breakfast. Bah!

Screen Shot 2021-12-30 at 15 45 46

@larduino
Copy link

Thanks for explaining all the host and non - host stuff. I didn't get that a bit before and it was very confusing. I did the print of my docker containers and they are all in non host except for my two solar docker programs which are in host . My node red does not show up at all since it is not in docker and from your chart that means it is in host mode .
That is great you can monitor your power. All my power here is from inverter but I want to monitor it so I recently ordered this
https://www.aliexpress.com/item/33045826345.html?spm=a2g0o.9042311.0.0.decf4c4dMTBqXh
as well as similar one that monitors DC ( for my direct dc solar system)
Hopefully I can figure out the modbus stuff for it - I know others seem to have documented that so I will see if I can follow along their efforts.
pi ports

@larduino
Copy link

larduino commented Jan 3, 2022

I have been reading and studying a lot about all things you have covered in this .
Right now I am trying to get a Sonoff POW MQTT to Influx.
I know I get the data - I was able to display it on Node Red dashboard
But I am not able to get it into Influx because I don't think I have formatted it correctly though I just inserted Change module and that helped somewhat though there is no data in it.
If you have time could you take a quick look and advice ?
Thanks - flow and photo attached
flow [ { "id": "0abb9dc2d326cf1b", "type": "tab", "label": "Flow 5", "disabled": false, "info": "", "env": [] }, { "id": "4f68e1c7.43cce", "type": "mqtt in", "z": "0abb9dc2d326cf1b", "name": "", "topic": "tele/sonoffpow/#", "qos": "0", "datatype": "auto", "broker": "e40a6e76.c9f3d", "nl": false, "rap": false, "inputs": 0, "x": 100, "y": 200, "wires": [ [ "df01d05e.099b" ] ] }, { "id": "27b3a4db.fab30c", "type": "debug", "z": "0abb9dc2d326cf1b", "name": "change debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 500, "y": 420, "wires": [] }, { "id": "af9e30fb.3217b", "type": "function", "z": "0abb9dc2d326cf1b", "name": "", "func": "\nvar msg1 = {};\nvar msg2 = {};\nvar msg3 = {};\nvar msg4 = {};\nvar msg5 = {};\nvar msg6 = {};\nmsg1.payload = msg.payload.ENERGY.Voltage;\nmsg1.topic = \"Voltage\";\nmsg2.payload = msg.payload.ENERGY.Current;\nmsg2.topic = \"Current\";\nmsg3.payload = msg.payload.ENERGY.Factor;\nmsg3.topic = \"Power Factor\";\nmsg4.payload = msg.payload.ENERGY.Power;\nmsg4.topic = \"Power\";\nmsg5.payload = msg.payload.ENERGY.ApparentPower;\nmsg5.topic = \"Apparent Power\";\nmsg6.payload = msg.payload.ENERGY.ReactivePower ;\nmsg6.topic = \"Reactive Power\";\n\nreturn [[msg1, msg2,msg3,msg4, msg5,msg6]];\n\n", "outputs": 6, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 620, "y": 180, "wires": [ [ "783944b.71e4abc", "f0c8f2a939ba4b80", "d11de45c580d7093" ], [ "f5fdef7.f91a11", "f0c8f2a939ba4b80" ], [ "ca7d21bb4b2f3815", "f0c8f2a939ba4b80" ], [ "95dcfceed20e7d08", "f0c8f2a939ba4b80" ], [ "945812e5afd9f9aa", "f0c8f2a939ba4b80" ], [ "691a5578db6c7ea9", "f0c8f2a939ba4b80" ] ] }, { "id": "783944b.71e4abc", "type": "ui_chart", "z": "0abb9dc2d326cf1b", "name": "Voltage", "group": "8f214c59.22bec", "order": 1, "width": 0, "height": 0, "label": "Voltage", "chartType": "line", "legend": "false", "xformat": "HH:mm", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "604800", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#eb2d2f", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 840, "y": 80, "wires": [ [] ] }, { "id": "f5fdef7.f91a11", "type": "ui_chart", "z": "0abb9dc2d326cf1b", "name": "Current", "group": "8f214c59.22bec", "order": 3, "width": 0, "height": 0, "label": "Current", "chartType": "line", "legend": "false", "xformat": "HH:mm", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "604800", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#51df44", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 840, "y": 120, "wires": [ [] ] }, { "id": "df01d05e.099b", "type": "json", "z": "0abb9dc2d326cf1b", "name": "JSON", "property": "payload", "action": "", "pretty": false, "x": 290, "y": 200, "wires": [ [ "2886fa70283df190", "fdc5662e2657f640" ] ] }, { "id": "d11de45c580d7093", "type": "debug", "z": "0abb9dc2d326cf1b", "name": "function debug ", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 880, "y": 440, "wires": [] }, { "id": "ca7d21bb4b2f3815", "type": "ui_chart", "z": "0abb9dc2d326cf1b", "name": "Power Factor", "group": "8f214c59.22bec", "order": 4, "width": 0, "height": 0, "label": "Power Factor", "chartType": "line", "legend": "false", "xformat": "HH:mm", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "604800", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#51df44", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 850, "y": 160, "wires": [ [] ] }, { "id": "691a5578db6c7ea9", "type": "ui_text", "z": "0abb9dc2d326cf1b", "group": "8f214c59.22bec", "order": 6, "width": 0, "height": 0, "name": "Reactive Power", "label": "VariAmps Reactive Power", "format": "{{msg.payload}}", "layout": "row-center", "className": "", "x": 860, "y": 280, "wires": [], "icon": "node-red/alert.svg" }, { "id": "945812e5afd9f9aa", "type": "ui_text", "z": "0abb9dc2d326cf1b", "group": "8f214c59.22bec", "order": 7, "width": 0, "height": 0, "name": "Apparent Power", "label": "VariAmps Apparent Power ", "format": "{{msg.payload}}", "layout": "row-center", "className": "", "x": 860, "y": 240, "wires": [], "icon": "node-red/alert.svg" }, { "id": "95dcfceed20e7d08", "type": "ui_gauge", "z": "0abb9dc2d326cf1b", "name": "Power in Watts ", "group": "8f214c59.22bec", "order": 8, "width": 0, "height": 0, "gtype": "gage", "title": " Power / Watts", "label": "Watts ", "format": "{{value}}", "min": 0, "max": 10, "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "className": "", "x": 860, "y": 200, "wires": [] }, { "id": "f0c8f2a939ba4b80", "type": "influxdb out", "z": "0abb9dc2d326cf1b", "influxdb": "31eb6050bebfe505", "name": "Sonoff POW", "measurement": "AC Power ", "precision": "", "retentionPolicy": "", "database": "database", "precisionV18FluxV20": "ms", "retentionPolicyV18Flux": "", "org": "organisation", "bucket": "bucket", "x": 870, "y": 360, "wires": [] }, { "id": "2886fa70283df190", "type": "change", "z": "0abb9dc2d326cf1b", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": " [\t { \"ENERGY\": payload.ENERGY },\t { \"SENSOR\": payload.SENSOR } \t]", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 440, "y": 220, "wires": [ [ "27b3a4db.fab30c", "af9e30fb.3217b" ] ] }, { "id": "fdc5662e2657f640", "type": "debug", "z": "0abb9dc2d326cf1b", "name": "json debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 450, "y": 140, "wires": [] }, { "id": "e40a6e76.c9f3d", "type": "mqtt-broker", "name": "", "broker": "127.0.0.1", "port": "1883", "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willPayload": "", "willMsg": {}, "sessionExpiry": "" }, { "id": "8f214c59.22bec", "type": "ui_group", "name": "Sonoff POW", "tab": "6b25e5f4.b44ecc", "order": 1, "disp": true, "width": "6", "collapse": false, "className": "" }, { "id": "31eb6050bebfe505", "type": "influxdb", "hostname": "localhost", "port": "8086", "protocol": "http", "database": "sonoffpow", "name": "influxdb", "usetls": false, "tls": "", "influxdbVersion": "1.x", "url": "http://localhost:8086", "rejectUnauthorized": true }, { "id": "6b25e5f4.b44ecc", "type": "ui_tab", "name": "Tasmota Control", "icon": "dashboard", "disabled": false, "hidden": false } ]
node red pow

@Paraphraser
Copy link
Author

I think I have some idea why it isn't working but it will be simpler if you can collect some raw data for me, please.

Please run these commands on the Raspberry Pi that is running your IOTstack:

$ docker ps
$ mosquitto_sub -v -h 127.0.0.1 -t "tele/sonoffpow/#" -F "%I %t %p"

I assume you've run the first command before so you'll be aware of what its output looks like. If not, it just lists the Docker containers that are running and terminates.

The second command will "hang" waiting for something to happen but, when the sonoffpow speaks, you should get output.

Here's an example where I'm using an equivalent command to record data being sent by a Hiking electricity meter:

2022-01-04T11:39:29+1100 /merle/HikingMonitor {"v":248.80,"f":50.01,"a":0.00,"e":512.94,"eMask":0,"upTime":54018,"heap":41872}
2022-01-04T11:39:39+1100 /merle/HikingMonitor {"v":248.80,"f":50.00,"a":0.00,"e":512.94,"eMask":0,"upTime":54028,"heap":41568}

You can see that I get a timestamp, the topic, and the JSON payload. That's what I need you to collect for me. Once you have a few observations, you can press control+c to end the command.

Then use copy and paste to collect the output from both commands and paste it into your reply.

Why? Well, the output from the docker ps will help me assess whether you are communicating correctly with Mosquitto. The output from the second command will help me simulate your data. I can use a mosquitto_pub to send the same topic and payload to my instance and then see what turns up in your flow.

@larduino
Copy link

larduino commented Jan 4, 2022

here is the data from the pi
If this is a bad format I can attach the data another way .

pi@PiBullseye:~ $ docker ps CONTAINER ID IMAGE COMMAND CREATED ST ATUS PORTS NAMES 478014720f93 iotstack_telegraf "/entrypoint.sh tele…" 2 days ago Up 2 days 0.0.0.0:8092->8092/udp, 0.0.0.0:8125->8125/udp, 0.0.0.0:8094 ->8094/tcp telegraf fb9eefa0161d classicdiy/classicmqtt "python3 classic_mqt…" 2 days ago Up 2 days classic_mqtt f9660dece843 influxdb:1.8 "/entrypoint.sh infl…" 2 days ago Up 2 days 0.0.0.0:8086->8086/tcp influxdb 243ebd58f5f2 portainer/portainer-ce "/portainer" 2 days ago Up 2 days 0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp, 9443/tcp portainer-ce 528f6705bf73 iotstack_mosquitto "/docker-entrypoint.…" 2 days ago U 2 days (healthy) 0.0.0.0:1883->1883/tcp mosquitto c1af8a2db5e9 grafana/grafana "/run.sh" 2 days ago Up 2 days 0.0.0.0:3000->3000/tcp grafana 3530baa9fb82 classicdiy/classicmqtt "python3 classic_mqt…" 2 days ago Up 2 days

2022-01-03T19:14:49-0600 tele/sonoffpow/STATE {"Time":"2022-01-04T02:14:49","Uptime":"0T00:02:09","UptimeSec":129,"Heap":27,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":1,"POWER":"ON","Wifi":{"AP":1,"SSId":"Tomato24","BSSId":"44:94:FC:93:54:B5","Channel":9,"Mode":"11n","RSSI":98,"Signal":-51,"LinkCount":1,"Downtime":"0T00:00:03"}} 2022-01-03T19:14:49-0600 tele/sonoffpow/SENSOR {"Time":"2022-01-04T02:14:49","ENERGY":{"TotalStartTime":"2022-01-01T02:51:28","Total":0.612,"Yesterday":0.027,"Today":0.001,"Period": 0,"Power":23,"ApparentPower":42,"ReactivePower":35,"Factor":0.56,"Voltage":120,"Current":0.351}} 2022-01-03T19:15:19-0600 tele/sonoffpow/STATE {"Time":"2022-01-04T02:15:19","Uptime":"0T00:02:39","UptimeSec":159,"Heap":27,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":1,"POWER":"ON","Wifi":{"AP":1,"SSId":"Tomato24","BSSId":"44:94:FC:93:54:B5","Channel":9,"Mode":"11n","RSSI":90,"Signal":-55,"LinkCount":1,"Downtime":"0T00:00:03"}} 2022-01-03T19:15:19-0600 tele/sonoffpow/SENSOR {"Time":"2022-01-04T02:15:19","ENERGY":{"TotalStartTime":"2022-01-01T02:51:28","Total":0.613,"Yesterday":0.027,"Today":0.001,"Period": 0,"Power":23,"ApparentPower":42,"ReactivePower":36,"Factor":0.54,"Voltage":120,"Current":0.353}} 2022-01-03T19:15:49-0600 tele/sonoffpow/STATE {"Time":"2022-01-04T02:15:49","Uptime":"0T00:03:09","UptimeSec":189,"Heap":27,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":1,"POWER":"ON","Wifi":{"AP":1,"SSId":"Tomato24","BSSId":"44:94:FC:93:54:B5","Channel":9,"Mode":"11n","RSSI":96,"Signal":-52,"LinkCount":1,"Downtime":"0T00:00:03"}} 2022-01-03T19:15:49-0600 tele/sonoffpow/SENSOR {"Time":"2022-01-04T02:15:49","ENERGY":{"TotalStartTime":"2022-01-01T02:51:28","Total":0.613,"Yesterday":0.027,"Today":0.001,"Period": 0,"Power":25,"ApparentPower":42,"ReactivePower":34,"Factor":0.59,"Voltage":120,"Current":0.350}} 2022-01-03T19:16:19-0600 tele/sonoffpow/STATE {"Time":"2022-01-04T02:16:19","Uptime":"0T00:03:39","UptimeSec":219,"Heap":27,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":1,"POWER":"ON","Wifi":{"AP":1,"SSId":"Tomato24","BSSId":"44:94:FC:93:54:B5","Channel":9,"Mode":"11n","RSSI":98,"Signal":-51,"LinkCount":1,"Downtime":"0T00:00:03"}} 2022-01-03T19:16:19-0600 tele/sonoffpow/SENSOR {"Time":"2022-01-04T02:16:19","ENERGY":{"TotalStartTime":"2022-01-01T02:51:28","Total":0.613,"Yesterday":0.027,"Today":0.001,"Period": 0,"Power":25,"ApparentPower":43,"ReactivePower":35,"Factor":0.57,"Voltage":120,"Current":0.360}}

@Paraphraser
Copy link
Author

That first part can't be right. Here's a tip. When you paste actual screen output, surround it with lines containing triple back-ticks, like this:

```
here is my raw screen output
```

How that actually appears is:

here is my raw screen output

Now, here's what I see when I reconstruct the output from docker ps so that it isn't all run together:

pi@PiBullseye:~ $ docker ps
CONTAINER ID IMAGE                    COMMAND                  CREATED     STATUS               PORTS                                                                    NAMES
478014720f93 iotstack_telegraf        "/entrypoint.sh tele…"   2 days ago  Up 2 days            0.0.0.0:8092->8092/udp, 0.0.0.0:8125->8125/udp, 0.0.0.0:8094 ->8094/tcp  telegraf
fb9eefa0161d classicdiy/classicmqtt   "python3 classic_mqt…"   2 days ago  Up 2 days                                                                                     classic_mqtt
f9660dece843 influxdb:1.8             "/entrypoint.sh infl…"   2 days ago  Up 2 days            0.0.0.0:8086->8086/tcp                                                   influxdb
243ebd58f5f2 portainer/portainer-ce   "/portainer"             2 days ago  Up 2 days            0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp, 9443/tcp                 portainer-ce
528f6705bf73 iotstack_mosquitto       "/docker-entrypoint.…"   2 days ago  Up 2 days (healthy)  0.0.0.0:1883->1883/tcp                                                   mosquitto
c1af8a2db5e9 grafana/grafana          "/run.sh"                2 days ago  Up 2 days            0.0.0.0:3000->3000/tcp                                                   grafana
3530baa9fb82 classicdiy/classicmqtt   "python3 classic_mqt…"   2 days ago  Up 2 days

See the problem? classicdiy/classicmqtt is mentioned twice and Node-RED is nowhere to be seen. Can you run the docker ps again, please. If it's easier, just take a screen capture of the terminal window and paste it as an image.

Meanwhile, I'll take a look at the other stuff.

@larduino
Copy link

larduino commented Jan 4, 2022

yeah I tried to put the docker stuff in notepad and make it better but still didn't work right.
There are supposed to be two Classic DIY - one each for the two different Classic solar controllers I have . Those work fine.
The node red is not listed in docker because I am running the Node Red that came already installed on the Raspberry Pi.
Is there a benefit to removing the Pi installed Node Red and then installing Node Red via IOTStack docker ? I can certainly do that.

docker ps

@Paraphraser
Copy link
Author

Have a look at the following. I had to make a few assumptions so please keep these notes in mind.

  1. Because of the incomplete docker ps I could not tell whether Node-RED is running in host mode or non-host mode. I'm running in non-host mode so I changed the 127.0.0.1:1883 to mosquitto:1883, and the 127.0.0.1:8086 to influxdb:8086. If your Node-RED is running in host mode, you will have to change those back.

  2. I'm assuming this is a Sonoff Power Switch. I further assume it's possible you'll have more than one of those. I further assume that, given the topic pattern tele/sonoffpow/SENSOR, one of those fields is meant to indicate which Sonoff Power Switch is sending the telemetry. For the purposes of this example, I assumed "sonoffpow" but if "tele" means something like "television" and that's meant to be the discriminator between different Sonoff Power Switches, can I suggest that you consider changing the ordering so it's sonoffpow/tele/SENSOR and sonoffpow/tele/STATE.

  3. You need to split SENSOR and STATE somehow and the easiest way to do what is two separate subscribers - you'll see that I've done that. If you don't do it at the subscriber, you need another node to vector on the message type so you don't lose/gain anything either way.

  4. Also, a good "trick" is to change MQTT-in nodes so they deliver parsed JSON output. That way you don't need a separate JSON node.

  5. Another good trick is to see what Node-RED does if you leave "Name" fields empty. Often, Node-RED gives you a good summary of what's going on. You'll see that in the MQTT-in nodes. It's Node-RED that's providing the topic string on the canvas and that's exactly what you usually want.

  6. You need to be very careful with spaces. I found a bug in your InfluxDB-out node caused by a trailing space. Once I deleted it, the insert worked.

  7. You'll see I lean towards camel-case field and tag names in Influx. Obviously that's up to you. If you want to capitalise everything, go for it.

  8. Whenever you start working with Influx, you need to create the database by hand. That's true with Influx 1.8 but I don't know if it's different for Influx 2.x:

    $ docker exec -it influxdb influx -precision=rfc3339
    > create database sonofpow
    > exit
    $
    

The flow:

[
    {
        "id": "0abb9dc2d326cf1b",
        "type": "tab",
        "label": "Flow 5",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "4f68e1c7.43cce",
        "type": "mqtt in",
        "z": "0abb9dc2d326cf1b",
        "name": "",
        "topic": "tele/+/SENSOR",
        "qos": "0",
        "datatype": "json",
        "broker": "e40a6e76.c9f3d",
        "nl": false,
        "rap": false,
        "rh": "0",
        "inputs": 0,
        "x": 120,
        "y": 380,
        "wires": [
            [
                "fdc5662e2657f640",
                "2886fa70283df190",
                "af9e30fb.3217b"
            ]
        ]
    },
    {
        "id": "27b3a4db.fab30c",
        "type": "debug",
        "z": "0abb9dc2d326cf1b",
        "name": "",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 510,
        "y": 460,
        "wires": []
    },
    {
        "id": "af9e30fb.3217b",
        "type": "function",
        "z": "0abb9dc2d326cf1b",
        "name": "",
        "func": "\nvar msg1 = {};\nvar msg2 = {};\nvar msg3 = {};\nvar msg4 = {};\nvar msg5 = {};\nvar msg6 = {};\nmsg1.payload = msg.payload.ENERGY.Voltage;\nmsg1.topic = \"Voltage\";\nmsg2.payload = msg.payload.ENERGY.Current;\nmsg2.topic = \"Current\";\nmsg3.payload = msg.payload.ENERGY.Factor;\nmsg3.topic = \"Power Factor\";\nmsg4.payload = msg.payload.ENERGY.Power;\nmsg4.topic = \"Power\";\nmsg5.payload = msg.payload.ENERGY.ApparentPower;\nmsg5.topic = \"Apparent Power\";\nmsg6.payload = msg.payload.ENERGY.ReactivePower ;\nmsg6.topic = \"Reactive Power\";\n\nreturn [[msg1,msg2,msg3,msg4,msg5,msg6]];",
        "outputs": 6,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 420,
        "y": 240,
        "wires": [
            [
                "783944b.71e4abc",
                "d11de45c580d7093"
            ],
            [
                "f5fdef7.f91a11"
            ],
            [
                "ca7d21bb4b2f3815"
            ],
            [
                "95dcfceed20e7d08"
            ],
            [
                "945812e5afd9f9aa"
            ],
            [
                "691a5578db6c7ea9"
            ]
        ]
    },
    {
        "id": "783944b.71e4abc",
        "type": "ui_chart",
        "z": "0abb9dc2d326cf1b",
        "name": "Voltage",
        "group": "8f214c59.22bec",
        "order": 1,
        "width": 0,
        "height": 0,
        "label": "Voltage",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "604800",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#eb2d2f",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 980,
        "y": 60,
        "wires": [
            []
        ]
    },
    {
        "id": "f5fdef7.f91a11",
        "type": "ui_chart",
        "z": "0abb9dc2d326cf1b",
        "name": "Current",
        "group": "8f214c59.22bec",
        "order": 3,
        "width": 0,
        "height": 0,
        "label": "Current",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "604800",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#51df44",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 980,
        "y": 100,
        "wires": [
            []
        ]
    },
    {
        "id": "d11de45c580d7093",
        "type": "debug",
        "z": "0abb9dc2d326cf1b",
        "name": "",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 610,
        "y": 60,
        "wires": []
    },
    {
        "id": "ca7d21bb4b2f3815",
        "type": "ui_chart",
        "z": "0abb9dc2d326cf1b",
        "name": "Power Factor",
        "group": "8f214c59.22bec",
        "order": 4,
        "width": 0,
        "height": 0,
        "label": "Power Factor",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "604800",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#51df44",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 1000,
        "y": 140,
        "wires": [
            []
        ]
    },
    {
        "id": "691a5578db6c7ea9",
        "type": "ui_text",
        "z": "0abb9dc2d326cf1b",
        "group": "8f214c59.22bec",
        "order": 6,
        "width": 0,
        "height": 0,
        "name": "Reactive Power",
        "label": "VariAmps Reactive Power",
        "format": "{{msg.payload}}",
        "layout": "row-center",
        "className": "",
        "x": 1000,
        "y": 260,
        "wires": [],
        "icon": "node-red/alert.svg"
    },
    {
        "id": "945812e5afd9f9aa",
        "type": "ui_text",
        "z": "0abb9dc2d326cf1b",
        "group": "8f214c59.22bec",
        "order": 7,
        "width": 0,
        "height": 0,
        "name": "Apparent Power",
        "label": "VariAmps Apparent Power ",
        "format": "{{msg.payload}}",
        "layout": "row-center",
        "className": "",
        "x": 1010,
        "y": 220,
        "wires": [],
        "icon": "node-red/alert.svg"
    },
    {
        "id": "95dcfceed20e7d08",
        "type": "ui_gauge",
        "z": "0abb9dc2d326cf1b",
        "name": "Power in Watts ",
        "group": "8f214c59.22bec",
        "order": 8,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": " Power / Watts",
        "label": "Watts ",
        "format": "{{value}}",
        "min": 0,
        "max": 10,
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "className": "",
        "x": 1000,
        "y": 180,
        "wires": []
    },
    {
        "id": "2886fa70283df190",
        "type": "change",
        "z": "0abb9dc2d326cf1b",
        "name": "prepare influx insert",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "[\t    {\t        \"total\": msg.payload.ENERGY.Total,\t        \"yesterday\": msg.payload.ENERGY.Yesterday,\t        \"today\": msg.payload.ENERGY.Total,\t        \"period\": msg.payload.ENERGY.Period,\t        \"power\": msg.payload.ENERGY.Power,\t        \"apparentPower\": msg.payload.ENERGY.ApparentPower,\t        \"reactivePower\": msg.payload.ENERGY.ReactivePower,\t        \"powerFactor\": msg.payload.ENERGY.Factor,\t        \"voltage\": msg.payload.ENERGY.Voltage,\t        \"current\": msg.payload.ENERGY.Current\t    },{\t        \"sensor\": $split(msg.topic,'/')[1]\t    }\t]\t",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 460,
        "y": 380,
        "wires": [
            [
                "27b3a4db.fab30c",
                "fb488532bff55ae6"
            ]
        ]
    },
    {
        "id": "fdc5662e2657f640",
        "type": "debug",
        "z": "0abb9dc2d326cf1b",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 190,
        "y": 460,
        "wires": []
    },
    {
        "id": "f1bf29a0246742ee",
        "type": "mqtt in",
        "z": "0abb9dc2d326cf1b",
        "name": "",
        "topic": "tele/+/STATE",
        "qos": "0",
        "datatype": "json",
        "broker": "e40a6e76.c9f3d",
        "nl": false,
        "rap": false,
        "rh": "0",
        "inputs": 0,
        "x": 110,
        "y": 540,
        "wires": [
            [
                "fdc5662e2657f640",
                "dadbbd78ef636630"
            ]
        ]
    },
    {
        "id": "fb488532bff55ae6",
        "type": "influxdb out",
        "z": "0abb9dc2d326cf1b",
        "influxdb": "31eb6050bebfe505",
        "name": "insert acpower",
        "measurement": "acpower",
        "precision": "",
        "retentionPolicy": "",
        "database": "database",
        "precisionV18FluxV20": "ms",
        "retentionPolicyV18Flux": "",
        "org": "organisation",
        "bucket": "bucket",
        "x": 720,
        "y": 380,
        "wires": []
    },
    {
        "id": "dadbbd78ef636630",
        "type": "change",
        "z": "0abb9dc2d326cf1b",
        "name": "prepare influx insert",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "[\t    {\t        \"uptime\": msg.payload.UptimeSec,\t        \"heap\": msg.payload.Heap,\t        \"sleepMode\": msg.payload.SleepMode,\t        \"sleep\": msg.payload.Sleep,\t        \"loadAvg\": msg.payload.LoadAvg,\t        \"mqttCount\": msg.payload.MqttCount,\t        \"power\": msg.payload.POWER\t    },{\t        \"sensor\": $split(msg.topic,'/')[1]\t    }\t]\t",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 460,
        "y": 540,
        "wires": [
            [
                "b8c9cbadac0773e8",
                "27b3a4db.fab30c"
            ]
        ]
    },
    {
        "id": "b8c9cbadac0773e8",
        "type": "influxdb out",
        "z": "0abb9dc2d326cf1b",
        "influxdb": "31eb6050bebfe505",
        "name": "insert status",
        "measurement": "status",
        "precision": "",
        "retentionPolicy": "",
        "database": "database",
        "precisionV18FluxV20": "ms",
        "retentionPolicyV18Flux": "",
        "org": "organisation",
        "bucket": "bucket",
        "x": 710,
        "y": 540,
        "wires": []
    },
    {
        "id": "e40a6e76.c9f3d",
        "type": "mqtt-broker",
        "name": "",
        "broker": "mosquitto",
        "port": "1883",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthRetain": "false",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeRetain": "false",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willRetain": "false",
        "willPayload": "",
        "willMsg": {},
        "sessionExpiry": ""
    },
    {
        "id": "8f214c59.22bec",
        "type": "ui_group",
        "name": "Sonoff POW",
        "tab": "6b25e5f4.b44ecc",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false,
        "className": ""
    },
    {
        "id": "31eb6050bebfe505",
        "type": "influxdb",
        "hostname": "influxdb",
        "port": "8086",
        "protocol": "http",
        "database": "sonoffpow",
        "name": "",
        "usetls": false,
        "tls": "",
        "influxdbVersion": "1.x",
        "url": "http://localhost:8086",
        "rejectUnauthorized": true
    },
    {
        "id": "6b25e5f4.b44ecc",
        "type": "ui_tab",
        "name": "Tasmota Control",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]

Sample data that made it to Influx:

$ docker exec -it influxdb influx -precision=rfc3339

> use sonoffpow
Using database sonoffpow

> show series
key
---
acpower,sensor=sonoffpow
status,sensor=sonoffpow

> select * from acpower
name: acpower
time                           apparentPower current period power powerFactor reactivePower sensor    today total voltage yesterday
----                           ------------- ------- ------ ----- ----------- ------------- ------    ----- ----- ------- ---------
2022-01-04T03:06:55.199690005Z 42            0.351   0      23    0.56        35            sonoffpow 0.612 0.612 120     0.027
2022-01-04T03:19:18.518436829Z 42            0.351   0      23    0.56        35            sonoffpow 0.612 0.612 120     0.027
2022-01-04T03:20:45.937055418Z 42            0.351   0      23    0.56        35            sonoffpow 0.612 0.612 120     0.027
2022-01-04T03:20:46.963298527Z 42            0.353   0      23    0.54        36            sonoffpow 0.613 0.613 120     0.027
2022-01-04T03:20:48.006427967Z 42            0.35    0      25    0.59        34            sonoffpow 0.613 0.613 120     0.027
2022-01-04T03:20:48.999652464Z 43            0.36    0      25    0.57        35            sonoffpow 0.613 0.613 120     0.027

> select * from status
name: status
time                           heap loadAvg mqttCount power sensor    sleep sleepMode uptime
----                           ---- ------- --------- ----- ------    ----- --------- ------
2022-01-04T03:18:52.568022751Z 27   19      1         ON    sonoffpow 50    Dynamic   129

> exit
$

And, finally:

Screen Shot 2022-01-04 at 14 44 45

@Paraphraser
Copy link
Author

Didn't know Node-RED came pre-installed. It's up to you. These days, I'm pretty much prefer a container over any other approach for just about anything.

@larduino
Copy link

larduino commented Jan 4, 2022

I had the Sonoff POW from a couple years ago - never got it working with EspEasy firmware.
The other day I got the Sonoff POW out and flashed it with Tasmota and it works !
It has the regular display screen with all the power data and also the on off toggle.
Then the Tasmota has setup screens for MQTT and other things - really nice setup.
I have already been using it with plugs installed on it to test various AC circuits and see what is going on with loads. Pretty nice for $10. I just ordered two more of their revised POW R2 which I guess is a bit better .
Regarding the Node Red flow I have - it is a hodge podge of downloading the flows of other people and then trying to modify them to work.
And I know what you mean about getting corrupted flows - some people have some posted and I can't get them to load and spent time trying to figure out syntax errors, etc.
So now I am going to spend some time studying the flow which you kindly made and shared with me . I do appreciate it . After spending days reading up on this and that what you shared should make some sense to me - I hope !
Here are screen shots from the Tasmota webpage installed in the Sonoff POW.
tasmoda

tasmota

@Paraphraser
Copy link
Author

🥳 - graphics!

Aside from the standard benefits of containerisation (someone else does all the maintenance work, easy to pin to earlier versions if an update breaks something, or test new versions in a controlled manner before committing to them, "no surprises" because two processes try to futz with the same part of your OS - they can't because containerisation stops them), you should think about the efficiency of traffic flows. A device like the sonoff speaks. Its MQTT traffic arrives at the RPi where Docker is listening on port 1883. Docker uses Network Address Translation (NAT) to forward the packet to the container's port 1883. The entire TCP exchange in each direction goes through NAT and the packets are routed (Layer Three) in and out of container space. If NodeRed and Influx are both running as containers like Mosquitto then all the remaining traffic interactions occur within containerspace so it's like a Layer Two switch - unicast traffic is point-to-point across the internal subnet. If NodeRed runs outside container space then its subscriptions to Mosquitto and its "insert" instructions to Influx all have to undergo bi-directional NAT and be routed in and out of containerspace. It works but ... yech.

Also, NodeRed graphics are a good way to get started but I think you'll quickly grow tired of their limitations. I bet the question you'll be asking in a few days is "why don't the measurements shown in the graphs persist if I restart NodeRed?" Because they don't. You can make NodeRed do it but it's quite ugly.

Grafana, on the other hand, has all this down pat. One of the things I really like is having a running dashboard of last week's voltage being updated every 10 seconds. If I notice spike I can just rubber-band the area and zoom in on the period in question without a second thought. The histogram associated with the graph of voltage by time just follows along giving me the distribution. Quite neat, really. It's a great app.

I suppose the best advice I can give you is to not waste too much time trying to make NodeRed do graphics. You have devices. They send telemetry to Mosquitto - it's the concentrator. Node-Red is the traffic cop saying where the data should be stored in Influx. Grafana is the display engine, ferreting data out of influx tables and combining it in interesting ways. Node-Red is good at things like alerts but Grafana has those too so it's a matter of taste and features. Influx is also good at chucking data away or aggregating it in useful ways. Example, do I really need 10-second voltage data from five years ago or would the maximum voltage in each minute, five minutes, 30 minutes, hour, etc be more appropriate? Influx can handle that. Each component in the MING stack is well worth the learning curve.

@Paraphraser
Copy link
Author

In "full topic" it's not clear where %prefix% is coming from but it does seem reasonably clear that Tasmota expects the second part of the topic string to be the device discriminator. That's why it defaults to "tasmota" followed by what is probably derived from the WiFi MAC address of the ESP8266 chip. The point is to think about what you want to call these things. If you get it "right" (which is entirely dependent on your needs and goals) that flow will automatically handle new sonoffs correctly. They'll just show up as new series in the database.

@larduino
Copy link

larduino commented Jan 4, 2022

I totally understand what you are saying about Node Red Graphics compared to Grafana. As you may remember I have a very nice Grafana dashboard displaying my solar system data and it works great and as you say I can look backwards and zoom in. The graphics I had on Node Red example were ones I downloaded from someone else and it was helpful in me learning some things- since it didn't work I figured out the problems in the code in it.
I will uninstall the built in node red ( or leave it there ) and I will use IOTStack to install container Node Red. I really like using Portainer to see what is going on - makes it easy to look at logs, get in via the console, restart , etc.
The flow you made is running perfectly with no errors .I am studying your code in the change node and the effect it had on the output . I haven't set up the influx database for it yet and tried it in Grafana - that may have to be tomorrow. I may try to get to sleep tonight before 2 AM .
Thanks again - i really appreciate that you share your knowledge .

@Paraphraser
Copy link
Author

I chat with so many people on GitHub and Discord that I can't remember who has shown me what. That often leads me to make poor assumptions. Sorry. I'm glad that the flow is working for you.

@larduino
Copy link

larduino commented Jan 6, 2022

I removed the Node Red that came on Pi, then used IOTStack to load container Node Red. Everything worked out fine . I was also able to get the Influxdb part working and linked up to Grafana .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment