skip to Main Content

I’m using BalenaOS and building a docker container with some python that senses when a button is pressed and then is supposed to send this command:

dbus-send --system 
    --print-reply=literal 
    --dest=org.freedesktop.login1 
    /org/freedesktop/login1 
   "org.freedesktop.login1.Manager.PowerOff" boolean:true

I can open a terminal inside this container, run this command, and it DOES work exactly as intended. However, pressing the button only results in this in error messages (shown at bottom of post)

Here is my setup:

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 ["bash", "start.sh"]

requirements.txt

RPi.Gpio
dbus-python

start.sh

#!/usr/bin/env bash

## connect to the host's system bus from the application container
export DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket

EDIT

I abandoned dbus-python for pydbus
It seems to yield a better result and seems to simplify the problem but still spits an error. More info on the difference between the two can be found here https://wiki.python.org/moin/DbusExamples

Here is the edited python

button.py

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()

    oldButtonState1 = buttonState1

time.sleep(1)

The output is now:

21.12.19 11:33:53 (-0800)  button      logind.PowerOff()
21.12.19 11:33:53 (-0800)  button    File "/root/.local/lib/python3.7/site-packages/pydbus/proxy_method.py", line 62, in __call__
21.12.19 11:33:53 (-0800)  button      raise TypeError(self.__qualname__ + " missing {} required positional argument(s)".format(-argdiff))
21.12.19 11:33:53 (-0800)  button  TypeError: org.freedesktop.login1.Manager.PowerOff missing 1 required positional argument(s)
21.12.19 11:33:59 (-0800)  button  button.py:12: RuntimeWarning: A physical pull up resistor is fitted on this channel!
21.12.19 11:33:59 (-0800)  button    GPIO.setup(5, GPIO.IN, pull_up_down=GPIO.PUD_UP)

If I had to guess I’d say I’m doing something wrong in the python script. Not utilizing dbus correctly.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to stovfl

    The issue was:

    1. Using a dbus-python, but what SHOULD be used is the pydbus library. dbus-python is depreciated.
    2. My interface.PowerOff() was missing True. It should be interface.PowerOff(True)

    The correct dockerfile is:

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

    My requirements.txt

    RPi.Gpio
    pydbus
    Pycairo
    PyGObject
    

    The correct python script is

    #!/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://fhackts.wordpress.com/2019/08/08/shutting-down-or-rebooting-over-dbus-programmatically-from-a-non-root-user/

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


  2. Raspbian bullseye solution:

    Sudo Edit /usr/share/polkit-1/actions/org.freedesktop.login1.policy to change the required security settings in https://www.freedesktop.org/software/systemd/man/org.freedesktop.login1.html#Security e.g. to allow "poweroff"

    ...
    <action id="org.freedesktop.login1.power-off">
      <description gettext-domain="systemd">Power off the system</description>
      <message gettext-domain="systemd">Authentication is required to power off the system.</message>
      <defaults>
        <allow_any>yes</allow_any>
        <allow_inactive>yes</allow_inactive>
        <allow_active>yes</allow_active>
      </defaults>
      <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.set-wall-message</annotate>
    </action>
        
    <action id="org.freedesktop.login1.power-off-multiple-sessions">
      <description gettext-domain="systemd">Power off the system while other users are logged in</description>
      <message gettext-domain="systemd">Authentication is required to power off the system while other users are logged in.</message>
      <defaults>
        <allow_any>yes</allow_any>
        <allow_inactive>yes</allow_inactive>
        <allow_active>yes</allow_active>
      </defaults>
      <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.power-off</annotate>
    </action>
    ...
    

    Python:

    import dbus
    bus = dbus.SystemBus()
    obj = bus.get_object('org.freedesktop.login1', '/org/freedesktop/login1')
    iface = dbus.Interface(obj, 'org.freedesktop.login1.Manager')
    iface.PowerOff(True)
    

    It’s not ideal for a multi-user system to allow unchallenged access to poweroff but works fine for my purpose of shutting down a robot with a remote control key

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search