Exposing a D-Bus Interface in Linux — Part 2

From basic concepts to code

Rodrigo Perazzo
CESAR Update
7 min readMar 29, 2018

--

This is the 2nd part of a series about D-Bus, so please take a look at the 1st one if you haven’t yet. This 2nd part was co-written by me and Thiago Cardoso.

Since D-Bus is an IPC / RPC mechanism, as explained on the first post, there are at least two processes involved: One that consumes APIs (aka client) and another one that provides those APIs (aka server).

We’re going to start by the server, which usually is a service that manages something on the system and exposes objects and interfaces to clients interact with. By the way, objects/interfaces are some of the keywords from D-Bus protocol that worths explaining before one can fully understand what it takes to implement server and client sides. Here they are:

Note: The following concepts explanation were taken from Avichal Pandey’s great post and then rewritten/organized differently or just complemented to better fitting this text.

API Definition

The following keywords are used to mention server available APIs and to reference them in code:

  • Object Path: Client uses the object path to reference an object exposed by the Service. For example, org/bluez/hci0 is the path of a remote object exposed by BlueZ representing a Bluetooth adapter.
  • Object: All objects exposed by the service implement Interfaces and have a unique object path.
  • Interface: The API itself (and its members). It should answer: “What could I use from this service?”. Interfaces names are namespace strings much like Java package names, for example, org.freedesktop.NetworkManager.

Interface Members

Interfaces are composed of three types of members: methods, properties, and signals — the typical members provided in other object-oriented languages, such as C#. Each member defines how clients are going to communicate with the exposed objects:

D-Bus method call message flow
  • Method: It is an example of a request-response model of communication and it is used for one-to-one communication. A client calls a method exposed by a service optionally passing inputs and receiving an output. It is always initiated by the client and can be used in a blocking or an async way.
D-Bus signal message flow
  • Signal: It is a notification that something of interest has happened. A signal is an example of a publish-subscribe model of communication and it is generally used for one-to-many type of communication. A client listens to a signal exposed by a service which at some point of time emits the signal. In contrast with methods, signals are a one-way form of communication, i.e. don’t have an output.
  • Property: A variable that an object exposes. Its value can be accessed and modified via a getter and a setter, respectively.

Defining an interface

At the KNoT platform (powered by CESAR) we have a daemon that manages devices communicating using nRF24 radios. This daemon exposes a D-Bus interface that allows other components to interact with these devices. In this example, we’ll use a subset of it.

There is an XML format for defining D-Bus interfaces. The XML file can be constructed by hand, but to avoid errors most frameworks or libraries provide means of generating this file by parsing the code. When the XML file is provided by the code, D-Bus specification says that the objects were introspected at runtime.

Interface name

The first step is to define a name for the interface. As we are representing a device exposed by the nRF daemon from the KNoT platform, let’s call it br.org.cesar.knot.nrf.Device1.

Note: The “1” at the end of interface name stands for the API version. For more information please check API versioning.

Properties

Our device has two properties: address and a flag telling whether it is paired or not with the gateway. The address will be represented as a string and the paired flag will be represented as a boolean.

We don’t want the user to change the address or to directly change the pairing, so these two properties are read-only, i.e. have only getters.

Methods

There are two operations that can be executed on our device: pair and forget. Pair will allow the device to communicate with the gateway and forget will do the inverse.

When pairing, we might want to specify a public-private key pair to protect the communication. This will be passed as a dictionary containing the PublicKey and PrivateKey keys.

Both methods don’t return a value, but if they did it would be something like that:

In case of error, a couple of errors messages can be returned. Error messages have names defined in the same way as interface names, but they are not included in interface XML, instead, a textual API documentation is provided.

Signals

When a device is paired or forgotten, we want to notify other interested parties. In this case, we’re going to use a pre-defined signal on D-Bus, by just adding an annotation on property:

But we could define a custom signal too as follows:

Now let’s see how this can be implemented. (Finally!)

Server’s code

For convenience, we’re going to write server’s code using Python and pydbus library, but a similar result can be achieved with Node.js and node-dbus (example) or with C and ELL (example).

For running our server’s code you will need to install pydbus library using the following command:pip install 'pydbus'. You may need also to (re)install some Python APIs for GObject Introspection and D-Bus (as discussed here):

Please create a file named server.py and begin defining a method main and an empty DeviceInterface class:

The code above obtains system bus with bus = SystemBus() and exposes an interface (empty for now) with name br.org.cesar.knot.nrf.Device1 by calling bus.publish(service_name, ("Device1", DeviceInterface()).

Another thing that above code does is setting up an event loop that is going to handle all source of events (including D-Bus ones) and then call the right callbacks for us:

Now, let’s properly define the interface and its methods:

The class begins with the introspection data (mentioned on the previous section) that represents interface XML and then implements methods, properties, and signal handling.

Above implementation is obviously just an example. The Pair method always set the paired property as True and emits PropertiesChanged after half of a second. The Forget method always returns the InProgress error. The full code can be found here.

Gaining system access for our service

The service needs its own <busconfig> file, as we did on first post, to get access to the system bus. Please download nrf24.conf file from KNoT’s GitHub, put it into /etc/dbus-1/system.d/ folder and reload the machine.

Run python server.py as root and no org.freedesktop.DBus.Error.AccessDenied error should be raised.

Sending Messages from Terminal

Now that the server is up and running, it is possible to send messages to him from another terminal without the need to write any client’s code.

The mdbus2 command helps us on checking if our interface was exposed right. Just pass the service name and object path and we should see something like this (The org.freedesktop.DBus entries were omitted):

Everything is good, let’s send some messages with the dbus-send command. Like dbus-monitor, we should indicate on which bus and for whom we want to send messages, so include --system option and specify destination with --dest option. It is also important to include --print-reply option to see what happened after our call.

Forget method call example:

Pairmethod call example:

Since Pair method doesn’t return a value and emits a signal after some time, using dbus-monitor on another terminal should show our emitted signal:

That’s it. Our server is ready to accept calls from clients. This part took more time (and words!) than I expected, so we will cover client’s side in a future post.

Special thanks to @cktakahasi who always raises some good points (and topics to cover in future) and Paulo Serra Filho who made himself available to test our code.

References

--

--

Rodrigo Perazzo
CESAR Update

Dad & Husband. SW / Android / Arduino / IoT Engineer @inovacao_cesar. In pursuit of good habits for life.