What would be the proper way to add a power button for BalenaOS

I figured it out! I now have a button that will turn off a raspberry pi AND wake it from the same pins (5 & 6) using no power and only two wires. I wanna make this into a repository and share it for people who need this button functionality. I may have some stuff in here that is extra (extra packages that may not be needed) so I’m going to reine this a bit but this all works just fine. Any suggestions or comments about things that could be better would be greatly appreciated.

One big issue was trying to use dbus-python but that has been depreciated and is VERY difficult to work with.

Dockerfile

FROM balenalib/%%BALENA_MACHINE_NAME%%-debian-python:3.7.4

# Enable systemd init system
ENV INITSYSTEM off

# Set the working directory
WORKDIR /usr/src/app

RUN install_packages git dbus gnome-common

RUN apt-get update
RUN apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0

# Upgrade pip
RUN pip install --upgrade pip
COPY requirements.txt .
RUN pip install --user -r requirements.txt --no-cache-dir --disable-pip-version-check \
                --index-url https://www.piwheels.org/simple

# Copy everything into the container
COPY . ./
#Make sure scripts in .local are usable:
ENV PATH=/root/.local/bin:$PATH
ENV DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket
# Start application
CMD ["python", "button.py"]

requirements.txt

RPi.Gpio
pydbus
Pycairo
PyGObject

button.py

#!/usr/bin/env python3
#!
import RPi.GPIO as GPIO
import time
import pydbus
import gi

# Set GPIO mode: GPIO.BCM or GPIO.BOARD
GPIO.setmode(GPIO.BOARD)

# Set pin 5 an an input, and enable the internal pull-up resistor
GPIO.setup(5, GPIO.IN, pull_up_down=GPIO.PUD_UP)

oldButtonState1 = True

while True:
    buttonState1 = GPIO.input(5)

    if buttonState1 != oldButtonState1 and buttonState1 == False :
        bus = pydbus.SystemBus()
        logind = bus.get('.login1')['.Manager']
        logind.PowerOff(True)

    oldButtonState1 = buttonState1

time.sleep(1)

Links that helped me find the answers:

https://wiki.python.org/moin/DbusExamples

https://pygobject.readthedocs.io/en/latest/getting_started.html

Glad you finally made it!

From the python code you posted above, a very quick suggestion is to move the line

bus = pydbus.SystemBus()

out from the while loop (place it before the loop), so you initialize thee connection only once.

Let us know when you have the repo to share the solution!

1 Like

Good call! I’ll post the github here after the holiday.

Thank you @KingstonSteele for your help. I struggled with that too, but in the end, I preferred using the API to do so, as it has less dependencies:
(you need to enable API access with: io.balena.features.supervisor-api: 1)

docker-compose.yml

...
balena-button:
    build: ./balena-button
    restart: on-failure
    network_mode: host
    privileged: true
    labels:
      io.balena.features.supervisor-api: 1
...

Dockerfile.template

FROM balenalib/%%BALENA_MACHINE_NAME%%-debian:buster-run

WORKDIR /usr/src/app

COPY . ./

RUN install_packages gcc python3 python3-dev python3-pip

RUN pip3 install --no-cache-dir -r requirements.txt

ENV UDEV=1

CMD ["python3","-u","button.py"]

requirements.txt

RPi.Gpio
requests

button.py

#!/usr/bin/env python3

import os
import RPi.GPIO as GPIO
import sys
import time
import signal
import requests

pin_btn = 17
pin_led = 22

api_url = os.environ.get('BALENA_SUPERVISOR_ADDRESS')
api_tkn = os.environ.get('BALENA_SUPERVISOR_API_KEY')

def signal_handler(signal, frame):
    GPIO.output(pin_led, GPIO.LOW)
    GPIO.cleanup()
    sys.exit(0)

def button_callback(channel):
    print("Shutdown Triggered")
    requests.post(api_url + "/v1/shutdown?apikey=" + api_tkn, headers={'Content-Type': 'application/json'})

def main():
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(pin_led, GPIO.OUT)
    GPIO.setup(pin_btn, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    GPIO.output(pin_led, GPIO.HIGH)
    GPIO.add_event_detect(pin_btn, GPIO.RISING, button_callback, bouncetime=100)

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    signal.pause()

    GPIO.output(pin_led, GPIO.LOW)
    GPIO.cleanup()
    sys.exit(0)

if __name__ == "__main__":
    main()

There’s a very simple solution

Wire a push button between GPIO3 (pin 5) and GROUND (pin6).
By default (and you cannot change this) pushing it will boot up the pi if it’s off.

Then add gpio-shutdown to your dtoverlay (either using balena cloud or config.txt).
It will initiate a clean shutdown process when the button is pressed and the pi in on.
By default it will be on GPIO3 as well, which makes it handy, but you can change the pin if you like.

Voilà you got your ON/OFF button without writing a line of code :slight_smile:

Notes :

  • the shutdown is the same as if you typed shutdown in a terminal or called the shutdown using dbus or the supervisor
  • when the shutdown process is finished the act led will blink 10 times to inform that it properly shutdown
  • the board is in a low power mode, but still consume power
  • in the same idea, 5v and 3.3v pins from the 40pins header will continue to provide power to any peripheral connected

if you need a deeper shut down, you need to use more complex circuitry and have a look at the gpio-poweroff dto which would turn a GPIO HIGH when the soft shutdown is done (up to you to use that to actually turn off power)

More infos and docs : firmware/README at 9f4983548584d4f70e6eec5270125de93a081483 · raspberrypi/firmware · GitHub