Getting started with D-Bus using Python and systemd
In this post I’ll take a look at how to get started with D-Bus using Python and systemd.
D-Bus is a system that allows two or more programs to communicate with each other. An application or service can expose various objects using D-Bus. Each object can have multiple methods and properties which are then accessible for other applications via D-Bus.
Some applications also expose signals. Programs can subscribe to those signals so that they get notified if an event occurs. One common use case for this is, for example, Bluetooth: Invoke some custom logic if a device is connected. (bluez exposes the “device connected signal” via D-Bus)
In this post I’ll use D-Bus to communicate with systemd to see if the crate service is running.
To use D-Bus in python there are a couple of libraries available. There is dbus-python which is a binding for libdbus (the reference implementation of D-Bus). The dbus-python library isn’t very pythonic and not available on pypi so in order to install it, it is necessary to use the distributions package manager or to install it from source.
Under Archlinux (the only distribution that matters, really!) it can be installed using pacman:
pacman -S python-dbus
Once installed we can open up a python shell and get started:
from dbus import SystemBus, SessionBus
D-Bus has two “channels” one system channel for system services (like systemd!) and system events (like new bluetooth device detected) and channels that are bound to the login session. The later are mostly used by regular applications.
For now we’ll use the SystemBus as we want to interact with systemd:
= SystemBus()
bus = bus.get_object('org.freedesktop.systemd1',
systemd '/org/freedesktop/systemd1')
The first argument org.freekdestop.systemd1
is the
well-known-name of the systemd bus. Think of it as kind of a domain name
for an application or service. The second argument
/org/freedesktop/systemd1
is an object-path. It is the path
to an object that is exposed on the bus.
So we’ve got:
SystemBus
-> 'org.freedesktop.systemd1' # The well-known-name to identify a bus
-> '/org/freedesktop/systemd1' # object-path to identify an object on the bus
The objects that are exposed by systemd are documented on the systemd site.
D-Bus separates objects and the description of their functionality.
So for now our systemd
instance is just a proxy that
doesn’t really know what it can do.
The description of the functionality is an Interface
.
One object can implement one or more interfaces.
In the case of systemd and the /org/freedesktop/systemd1
object it supports (besides others) the
org.freedesktop.systemd1.Manager
interface:
from dbus import Interface
= Interface(systemd, dbus_interface='org.freedesktop.systemd1.Manager') manager
So now we have:
SystemBus
-> 'org.freedesktop.systemd1' # The well-known-name to identify a bus
-> '/org/freedesktop/systemd1' # object-path to identify an object on the bus
implements
'org.freedesktop.systemd1.Manager' # Manager defines various methods
* LoadUnit(name)
* StartUnit(name, mode)
* ...
There are a lot more methods in the Manager interface. Take a look at the systemd documentation if you’re interested.
Here we basically told the systemd
proxy object what it
can do by telling it his interface, which is identified by a name. With
this manager
interface we can now finally call methods that
are exposed by the systemd dbus-api:
= manager.LoadUnit('crate.service') crate_unit
The LoadUnit
call returned an object-path to the
crate.service
unit that is also available on the systemd
bus. Using this object-path it is possible to retrieve a proxy object
for the crate service:
= bus.get_object('org.freedesktop.systemd1', str(crate_unit)) crate_proxy
Note that I used the same well-known-name as before (which identifies systemd) but a different object-path because this time I don’t want to get the root systemd object but rather the crate service object.
Just like before it is necessary to tell the object what it is
capable of. For Units systemd specifies the
org.freedesktop.systemd1.Unit
interface:
= Interface(crate_proxy, dbus_interface='org.freedesktop.systemd1.Unit') crate
The .systemd1.Unit
interface defines some methods to
interact with the unit and a lot of properties. Unfortunately while the
properties are defined in the org.freedesktop.systemd1.Unit
interface there is no method defined in it to access them. Instead there
is a generic org.freedesktop.DBus.Properties
interface that
has to be used to access them.
In order to get the state of the crate service we can use our
crate_proxy
object using the Properties
interface:
'org.freedesktop.systemd1.Unit', # interface that defines the property
crate_proxy.Get('ActiveState', # name of the property
='org.freedesktop.DBus.Properties') # interface that defines `Get` dbus_interface
The same thing could also have been accomplished if we created an
interface instance using the crate_proxy
and the
DBus.Properties
interface and then called Get
on that instance:
= Interface(crate_proxy,
crate_properties ='org.freedesktop.DBus.Properties')
dbus_interface'org.freedesktop.systemd1.Unit', 'ActiveState') crate_properties.Get(
So to wrap up our Bus-Tree:
SystemBus
-> 'org.freedesktop.systemd1' # The well-known-name to identify a bus
-> '/org/freedesktop/systemd1' # object-path to identify an object on the bus
implements
'org.freedesktop.systemd1.Manager'
...
# the object-path we got using LoadUnit('crate.service') on the manager
-> '/org/freedesktop/systemd1/unit/crate_2eservice'
implements
'org.freedesktop.systemd1.Unit'
* Start(mode)
* ...
'org.freedesktop.DBus.Properties'
* Get(propertyName)
* GetAll()
* ...
....
So far we’ve covered pretty much all the basics. Working with
dbus-python
feels a tad tedious so it might be worth
checking out pydbus which
seems to be a bit more pythonic. Pydbus is available on pypi but it
requires PyGI which is not available on pypi and needs to be installed
beforehand.
Using pydbus the example from above would look a little less frightening:
from pydbus import SystemBus
= SystemBus()
bus = bus.get('org.freedesktop.systemd1')
systemd = systemd.LoadUnit('crate.service')
crate_unit = bus.get('.systemd1', crate_unit[0])
crate 'org.freedesktop.systemd1.Unit', 'ActiveState') crate.Get(