Skip to content

Instantly share code, notes, and snippets.

@Paraphraser
Last active February 15, 2023 04:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Paraphraser/bae3d41b60343608a256e1ce62153ff7 to your computer and use it in GitHub Desktop.
Save Paraphraser/bae3d41b60343608a256e1ce62153ff7 to your computer and use it in GitHub Desktop.
IOTstack + Node-RED + pigpiod

IOTstack + Node-RED + pigpiod

Problem Statement

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.

Test Environment

  • 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

Analysis

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.

the node-red-node-pi-gpiod add-on node

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:

  1. Edit:

    ~/IOTstack/services/nodered/Dockerfile
    

    and add node-red-node-pi-gpiod to the list.

  2. Re-build your local Node-RED container:

    $ cd ~/IOTstack
    $ docker-compose up --build -d nodered
    $ docker system prune -f
    
  3. Check that the two nodes have been added to the GUI palette.

the pigpiod daemon

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

The Fix

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:

  1. Save the results by pressing control+O (upper-case letter O).
  2. Press return to accept the proposed filename.
  3. 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!

Practical Test

  1. 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": []
        }
    ]
    
  2. In the Node-RED GUI, go to the main menu ("≡" icon) and choose "Import".

  3. Paste the contents of the clipboard into the area in the middle of the window.

  4. Change the "Import to" option to "new flow".

  5. Click "Import".

  6. Position the selected nodes wherever you like in the canvas and click to release.

  7. Click the Deploy button.

The result should look like:

Node-RED Test Flow
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:

  1. 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.
  2. 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:

Logic analyzer output

Initialised LOW. 16 transitions, finishing LOW. Notice the cycle time of roughly 1ms per iteration. Not particularly fast.

One more thing…

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:

  1. IOTstack containers running.

  2. 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
    
  3. 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.

References

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