Powering off RPI4 gracefully using gpio-shutdown device tree overlay

Tags: #<Tag:0x00007f326e514170>

Hello everybody.

Recently I’ve started getting acquainted topic of home automation and, after deciding to not go with any proprietary solutions for obvious reasons, went with the Raspberry Pi 4 as the base computer and Zigbee XBee WRL-15128 S2C module, connected to the serial port GPIO pins. My first try was OpenHAB and I didn’t really have good experience with it - adding the devices was hit or miss and since I’ve bought bunch of Aqara devices without realizing they aren’t really following the ZCL specification, it was a miracle if I managed to merely turn on the wall plug from the web GUI. I’ve stumbled upon Home Assistant, decided to give it a try and in less than one hour I had all my devices connected into one network (thank you ZHA device handlers!), with Xbee module fully configured and automations created. After all the days I spent configuring OpenHAB, that really felt like a miracle. What’s more, the HassOS feels much more appropriate for embedded solution, being a lightweight Buildroot-built distro instead of Raspbian fork the openHABian is. And on top of that, instead of the whole stack of Java solutions like OSGi and Apache Karaf it uses my beloved Docker (I work as a DevOps and yes, I’m a Docker fanboy :stuck_out_tongue:)!

I’ve decided to go further with my DIY project of creating universal home automation gateway and the next step was creating a power button. Raspberry Pi devices have a system of device tree overlays which allows one to modify various aspect of the hardware and the ways it integrates with the operating system. There is a gpio-shutdown one and according to its documentation:

Name:   gpio-shutdown
Info:   Initiates a shutdown when GPIO pin changes. The given GPIO pin
        is configured as an input key that generates KEY_POWER events.
        This event is handled by systemd-logind by initiating a
        shutdown. Systemd versions older than 225 need an udev rule
        enable listening to the input device:

                ACTION!="REMOVE", SUBSYSTEM=="input", KERNEL=="event*", \
                        SUBSYSTEMS=="platform", DRIVERS=="gpio-keys", \
                        ATTRS{keys}=="116", TAG+="power-switch"

        This overlay only handles shutdown. After shutdown, the system
        can be powered up again by driving GPIO3 low. The default
        configuration uses GPIO3 with a pullup, so if you connect a
        button between GPIO3 and GND (pin 5 and 6 on the 40-pin header),
        you get a shutdown and power-up button.
Load:   dtoverlay=gpio-shutdown,<param>=<val>
Params: gpio_pin                GPIO pin to trigger on (default 3)

        active_low              When this is 1 (active low), a falling
                                edge generates a key down event and a
                                rising edge generates a key up event.
                                When this is 0 (active high), this is
                                reversed. The default is 1 (active low).

        gpio_pull               Desired pull-up/down state (off, down, up)
                                Default is "up".

                                Note that the default pin (GPIO3) has an
                                external pullup.

        debounce                Specify the debounce interval in milliseconds
                                (default 100)

I know HassOS uses systemd, but from what I checked, there is no sign of systemd-logind there. I’m not trying nor asking to add it - systemd is heavy enough for the embedded solutions and adding one of its components solely for supporting power button is a bit too much. So, I’ve checked the systemd-logind udev rule which is normally responsible for handling the KEY_POWER events - it can be found on the Raspbian:

ACTION=="remove", GOTO="power_switch_end"

SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_SWITCH}=="1", TAG+="power-switch"
SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_KEY}=="1", TAG+="power-switch"

LABEL="power_switch_end"

I’ve enabled gpio-shutdown overlay by adding dtoverlay=gpio-shutdown line to the config.txt file on the boot partition, created the following udev rule and placed it in /etc/udev/rules.d/99-gpio-power-switch.rules file in the HassOS system (the root system, not one of the Docker containers):

ACTION!="remove", SUBSYSTEM=="input", KERNEL=="event*", SUBSYSTEMS=="platform", DRIVERS=="gpio-keys", ATTRS{keys}=="116", RUN+="/bin/sh -c 'date >> /mnt/data/udev.test'"

As you can see, instead of calling /usr/sbin/shutdown I’m just writing the execution time to the text file. However, nothing is happening when I press the button - no event is logged when I’m running udevadm monitor and no new row is added to the text file. What’s more, the new row is always added on the system startup and after calling udevadm trigger - which means that if I call /usr/sbin/shutdown there, the system will be powered off immediately after startup. The button itself is working - since it connects the GPIO3 and GND pins, it’s able to wake up the RPI from the halt state, so I’m sure it’s connected correctly.

I didn’t try to wire up the shutdown button to Home Assistant and to be honest, I don’t want to - I’m aiming for the power button to be independent from the system software and be handled by the system itself, so that it works even when I stop the Home Assistant and/or the Docker daemon. The button seems to be visible to the system after adding the gpio-shutdown overlay (here is the result of executing udevadm info /dev/input/event0):

P: /devices/platform/soc/soc:shutdown_button/input/input0/event0
N: input/event0
L: 0
S: input/by-path/platform-soc:shutdown_button-event
E: DEVPATH=/devices/platform/soc/soc:shutdown_button/input/input0/event0
E: DEVNAME=/dev/input/event0
E: MAJOR=13
E: MINOR=64
E: SUBSYSTEM=input
E: USEC_INITIALIZED=1751688
E: ID_INPUT=1
E: ID_INPUT_KEY=1
E: ID_PATH=platform-soc:shutdown_button
E: ID_PATH_TAG=platform-soc_shutdown_button
E: DEVLINKS=/dev/input/by-path/platform-soc:shutdown_button-event

Home Assistant also seems to discover the button:

Did anybody try to achieve the same thing as I do with the gpio-shutdown overlay? Maybe you have any idea what I’m doing wrong here? This is my first attempt on playing with the udev rules and kernel events, so my attempts are based on more-or-less conscious attempts of glueing together stuff from the documentation, manpages and whatever I find on the internet. :grinning:

1 Like

Okay, so I did more research and after understanding it more I know the udev rules aren’t the place to execute scripts on button press - that would require the button device to appear/disappear upon press. :joy:

I found the updated documentation on the gpio-shutdown overlay, which now includes extra information about implementing the shutdown without systemd:

        Alternatively this event can be handled also on systems without
        systemd, just by traditional SysV init daemon. KEY_POWER event
        (keycode 116) needs to be mapped to KeyboardSignal on console
        and then kb::kbrequest inittab action which is triggered by
        KeyboardSignal from console can be configured to issue system
        shutdown. Steps for this configuration are:

            Add following lines to the /etc/console-setup/remap.inc file:

                # Key Power as special keypress
                keycode 116 = KeyboardSignal

            Then add following lines to /etc/inittab file:

                # Action on special keypress (Key Power)
                kb::kbrequest:/sbin/shutdown -t1 -a -h -P now

            And finally reload configuration by calling following commands:

                # dpkg-reconfigure console-setup
                # service console-setup reload
                # init q

The problem is that it’s not really init system agnostic - it requires objects which are not present in the systemd-based HassOS like /etc/inittab file or /etc/console-setup/ directory. The other directories I could think of (/etc/init.d, /etc/rc.d) are also non-existent in /etc directory of HassOS.

I’m wondering - what could be the impact of adding systemd-logind to the HassOS after all? According to its description it’s responsible for things like:

  • Keeping track of users and sessions, their processes and their idle state.
  • Generating and managing session IDs.
  • Providing polkit-based access for users for operations such as system shutdown or sleep
  • Implementing a shutdown/sleep inhibition logic for applications
  • Handling of power/sleep hardware keys
  • Multi-seat management
  • Session switch management
  • Device access management for users
  • Automatic spawning of text logins (gettys) on virtual console activation and user runtime directory management

I think having the standardized power key handling would be good addition to HassOS - I could even try to make a pull request for its Github repository which would add systemd-logind, since I worked with Buildroot before. However, the fact it does so many other things which are not vital for the HassOS operation and could potentially interfere with its current behavior (polkit) makes me wonder if there is a more lightweight solution, ideally such one that could be deployed with the CONFIG USB for the true out-of-box experience that doesn’t require tinkering with the system files too much. :grinning:

1 Like

I would also love a solution for GPIO power/sleep button support directly in HassOS. For now I will just install a hardware swith on the power supply but I really do not love that solution.

Cutting the power without invoking proper system shutdown doesn’t unmount the filesystems, which coupled with the fact that there might be some pending I/O operations could lead to their corruption. However, the power button coupled with a power switch is totally different story - the former one allows for clean shutdown and the latter one for cutting the power completely once it’s safe to do so. :grinning:

I gave it a try and compiled HassOS myself, with the systemd-logind enabled. After enabling gpio-shutdown overlay driving the GPIO3 low performs a graceful shutdown, just like on Raspbian. The impact on the system seems to be negligible - resource-wise the library takes around 220 KB of disk space and 1 MB of RAM. From the usage perspective, my HA-powered hub worked exactly the same with logind as it did without it (save for the working shutdown capability of course).

I created a pull request in the HassOS repository 3 months ago - it got accepted and will be available with the HassOS 5 release. :grinning:

If you want to give it try right now, you can download one of the development builds:

2 Likes

That is grand! I will then arrange a power button on my setup while I wait for this. Usually I shut it down from SSH or via the GUI but once in a while I get a hang situation and then a power button or power-cut is the only remedy. But again, this is good nes. Thanks for the good work @konpon96 !

1 Like

Hi! I switched from OpenHAB and so had GPIO3 shutdown button on my Raspberry Pi, Also to have USB-SSD boot I switched to 5.6 build. The overlay does not looks like active there (or probably need more setup).

The systemd-logind addition only allows the system to handle the power button presses. It’s up to device and its configuration to expose the power button as an input. The default HassOS overlay config doesn’t include gpio-shutdown overlay which is necessary for exposing GPIO3 pin as the power button input. You have to add the following line to the config.txt file on the boot partition:

dtoverlay=gpio-shutdown

The config.txt doesn’t seem to be overwritten by HassOS updates - I’ve modified it when I was still on version 4 of the system and after upgrade to stable version 5 the line is still there and my power button is working. Once you add the mentioned line to the config.txt file, on the next system startup it will react to shortening GPIO3 and GND by gracefully bringing RPi to the halt state in which it’s safe to unplug the power cord. Shortening the pins again will start RPi up from the wake state. I remember measuring the power consumption of RPi in the halt state and although I don’t remember the exact value, it was something really negligible like 0.5 W or less.