The following problem was reported on the IOTstack Discord channel:
Today I rebuild my IOTstack from scratch using the Pibuild process and after restoring my nodered flows I noticed that my serials and GPIO are not connecting anymore.
I don't use node-red-node-pi-gpiod
myself but I have been able to reproduce the problem.
This gist walks through the problem-solving approach I used to enable Node-RED flows to communicate with the Raspberry Pi's GPIO pins. Some or all of what follows may be useful if you are chasing your own problems with getting Node-RED flows to talk to your Raspberry Pi's GPIO pins.
-
4GB Raspberry Pi 4 Model B Rev 1.1 running Debian GNU/Linux 11 (bullseye) as full 64-bit OS.
-
System built from scratch on 2023-02-12:
- using PiBuilder
- starting from
2022-09-22-raspios-bullseye-arm64.img.xz
Node-RED flows running in a container can't access the Raspberry Pi's GPIO pins directly. Instead, the node-red-node-pi-gpiod
package depends on pigpiod
running outside container-space.
This is one of the default nodes for IOTstack so it's usually present. The easiest way to confirm this is to go to the Node-RED GUI and search the Nodes palette for "gpiod". There should be two nodes (an in and an out node). If you can't find those nodes, you will have to:
-
Edit:
~/IOTstack/services/nodered/Dockerfile
and add
node-red-node-pi-gpiod
to the list. -
Re-build your local Node-RED container:
$ cd ~/IOTstack $ docker-compose up --build -d nodered $ docker system prune -f
-
Check that the two nodes have been added to the GUI palette.
Outside container-space, the pigpiod
daemon needs to be running.
Is it?
$ ps -ax | grep pigpiod
770391 pts/1 S+ 0:00 grep pigpiod
No. Why not? Is it enabled?
$ sudo systemctl is-enabled pigpiod
disabled
No. Enable it and start the service.
$ sudo systemctl enable pigpiod
Created symlink /etc/systemd/system/multi-user.target.wants/pigpiod.service → /lib/systemd/system/pigpiod.service.
$ sudo systemctl start pigpiod
Have we made any progress?
$ sudo systemctl status pigpiod
● pigpiod.service - Daemon required to control GPIO pins via pigpio
Loaded: loaded (/lib/systemd/system/pigpiod.service; enabled; vendor preset: enabled)
Active: failed (Result: exit-code) since Mon 2023-02-13 10:31:39 AEDT; 2s ago
Process: 435983 ExecStart=/usr/bin/pigpiod -l (code=exited, status=0/SUCCESS)
Main PID: 435984 (code=exited, status=1/FAILURE)
CPU: 60ms
Feb 13 10:31:39 demo systemd[1]: pigpiod.service: Main process exited, code=exited, status=1/FAILURE
Feb 13 10:31:39 demo systemd[1]: pigpiod.service: Failed with result 'exit-code'.
No. The daemon is exiting with status code 1 but there's nothing useful in those log messages to help explain why.
Let's back-track. First, tell systemctl
not to interfere (so we don't get confused).
$ sudo systemctl disable pigpiod
Removed /etc/systemd/system/multi-user.target.wants/pigpiod.service.
The output from the status
command reveals that pigpiod
is launched like this:
/usr/bin/pigpiod -l
Let's try that by hand (adding the -g
flag to keep the process in the foreground so error messages are delivered to the console):
$ sudo /usr/bin/pigpiod -g -l
2023-02-13 10:35:06 initInitialise: bind to port 8888 failed (Cannot assign requested address)
Can't initialise pigpio library
Weird. It can't bind to port 8888. Is something using that port?
$ sudo netstat -tulpn | grep 8888
$
Silence means the answer is no. What's that -l
flag actually doing? According to the man page:
-l Disable remote socket interface. Default enabled
So, the daemon is being told not to bind to any socket yet it still seems to be trying to do that.
If you think about it, a Node-RED flow running in a container must be able to communicate with pigpiod
running outside the container. To do that it's going to need a known port, and 8888 is as good as anything else. Therefore, it would seem to be counterproductive to tell pigpiod
not to bind to any port.
In other words, why the
-l
option has been added, what it is actually meant to be doing, and whether it is working or not, are all side issues.
We should lose the -l
option and try again:
$ sudo pigpiod -g
No error, no exit. This is Unix so those are good signs!
Terminate with control+C.
Retry without -g
so daemon starts in the background:
$ sudo pigpiod
Has the daemon spawned correctly?
$ ps -ax | grep pigpiod
773590 ? SLsl 0:01 pigpiod
773659 pts/1 S+ 0:00 grep pigpiod
Yes! Has it grabbed port 8888?
$ sudo netstat -tulpn | grep 8888
tcp6 0 0 :::8888 :::* LISTEN 773590/pigpiod
Yes! So the magic incantation we need is simply to lose the -l
flag.
Clobber the daemon we just spawned.
$ sudo killall pigpiod
Now, let's make that permanent.
$ sudo systemctl edit --full pigpiod
Displays:
[Unit]
Description=Daemon required to control GPIO pins via pigpio
[Service]
ExecStart=/usr/bin/pigpiod -l
ExecStop=/bin/systemctl kill pigpiod
Type=forking
[Install]
WantedBy=multi-user.target
Edit so the ExecStart
line to remove the -l
so it reads as follows:
ExecStart=/usr/bin/pigpiod
Save your work:
- Save the results by pressing control+O (upper-case letter O).
- Press return to accept the proposed filename.
- Exit the editor by pressing control+X (upper-case letter X).
Check your work:
$ cat /etc/systemd/system/pigpiod.service
The output should show your edited version. If the file is not there or doesn't show what you expect, go back and start over from systemctl edit --full
.
Enable the service:
$ sudo systemctl enable pigpiod
Created symlink /etc/systemd/system/multi-user.target.wants/pigpiod.service → /etc/systemd/system/pigpiod.service.
Although it's not strictly necessary, it's a good idea to reboot to make sure the pigpiod
will come up each time the system starts.
$ sudo reboot
Check if the daemon started correctly at boot time.
$ sudo systemctl status pigpiod
● pigpiod.service - Daemon required to control GPIO pins via pigpio
Loaded: loaded (/etc/systemd/system/pigpiod.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2023-02-13 16:17:06 AEDT; 18s ago
Main PID: 422 (pigpiod)
Tasks: 4 (limit: 4163)
Memory: 732.0K
CPU: 1.682s
CGroup: /system.slice/pigpiod.service
└─422 /usr/bin/pigpiod
Cooking with induction!
-
Copy the following JSON text to your clipboard.
[ { "id": "28742340c7816b25", "type": "function", "z": "9977c0c29ffada80", "name": "toggle pin", "func": "var pin = true\n\nfor (var i = 0; i < 16; i++) {\n\n node.send([null, { payload: pin }]);\n pin = !pin;\n\n}\n\nreturn null;\n", "outputs": 2, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 220, "y": 140, "wires": [ [], [ "712ef53b7f7a6754" ] ], "outputLabels": [ "on loop exit", "on event" ] }, { "id": "ebb0c904523e5a87", "type": "inject", "z": "9977c0c29ffada80", "name": "start", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 130, "y": 80, "wires": [ [ "28742340c7816b25" ] ] }, { "id": "712ef53b7f7a6754", "type": "pi-gpiod out", "z": "9977c0c29ffada80", "name": "GPIO 21", "host": "172.17.0.1", "port": 8888, "pin": "21", "set": true, "level": "0", "out": "out", "sermin": "1000", "sermax": "2000", "freq": "800", "x": 320, "y": 200, "wires": [] } ]
-
In the Node-RED GUI, go to the main menu ("≡" icon) and choose "Import".
-
Paste the contents of the clipboard into the area in the middle of the window.
-
Change the "Import to" option to "new flow".
-
Click "Import".
-
Position the selected nodes wherever you like in the canvas and click to release.
-
Click the Deploy button.
The result should look like:
Node-RED Test Flow |
---|
Re-check your work if you do not see "OK" below the "GPIO 21" node. The most likely cause is pigpiod
is not running.
Clicking the "start" button causes the "toggle pin" function node to iterate 16 times, toggling GPIO pin 21 on and off as fast as the loop can execute.
Notes:
- There's nothing magical about GPIO 21. I chose it for the practical reason that it's directly adjacent to a Ground pin, so it makes the connection to the logic analyzer slightly easier.
- Compared with ESP32/8266 or Arduinos, a Raspberry Pi is fairly expensive. I'm happy to plug-and-play with the former but I always power-off a Pi before I connect/disconnect anything to/from its pin headers. Please consider doing the same if you're following along at home.
My logic analyzer shows the result of running the flow:
Initialised LOW. 16 transitions, finishing LOW. Notice the cycle time of roughly 1ms per iteration. Not particularly fast.
After enabling pigpiod
, my test Pi developed a tendency to hang on the way down after being told to reboot. The precursor conditions seemed to be:
-
IOTstack containers running.
-
pigpiod
stopped and started by hand since the last reboot using various combinations of:$ sudo systemctl stop pigpiod $ sudo systemctl disable pigpiod $ sudo systemctl enable pigpiod $ sudo systemctl start pigpiod
-
Reboot.
Taking the stack down before rebooting seemed to cure the problem.
Just allowing the system to boot normally (so pigpiod
was started at boot time) and then not interfering with pigpiod
also cured the problem.
In normal operation, a user is unlikely to be interfering with pigpiod
so, based on what I'm seeing, this is unlikely to be a problem in practice. But it is still something to be aware of as a possibility if you are running pigpiod
and experience hangs during reboot.
- The pigpio library
- GitHub pigpio repository
- Node-RED add-on node
node-red-node-pi-gpiod