Eat Burgers on the Short Bus

Every so often I get questions about D-Bus and I end up giving a mini-lesson on what D-Bus is and how it’s used.  It took me a while to wrap my head around D-Bus long ago so I don’t expect everyone to pick up D-Bus like Yo-Yo Ma sight-reading.  So this is D-Bus, simplified.

D-Bus is just an IPC mechanism, but it layers a few concepts on top of plain message-passing. It took me some time to understand how the D-Bus object model really works (long ago of course), so don’t worry about it you don’t completely understand how it all fits together yet.

  • service: a program that responds to requests from clients. Each service is identified by a “bus name” which clients use to find the service and send requests to it. The bus name usually looks like org.foobar.Baz. A program can claim more than one bus name; NM claims org.freedesktop.NetworkManager and org.freedesktop.NetworkManagerSystemSettings, each is a unique service which provides different functionality to clients.
  • object: a method of organizing distinct entities, just like programming languages have objects. Each object is uniquely identified by an “object path” (somewhat like an opaque pointer) that often looks like /org/foobar/Baz/235235. Each request sent to the service must be directed to a specific object. Many services have a base object with a well-known path that you use to bootstrap your communication with the service.
  • interface: each request belongs to an interface, which is simply a way of logically separating different functionality. The same way that object-oriented languages like Java or C++ or GLib define an “interface”; a specific API that completely different objects can implement, but the caller doesn’t need to know what type the object is, just what methods the interface defines. Interface names often look like D-Bus service names, but have no relation to them.
  • method call: a request for an operation or information that a client sends to the service; method calls are defined by an Interface and are sent to a service’s objects.
  • signal: a message broadcast from a service to any listening client.

Putting it All Together

Say you have a binary called “mcdonaldsd” that provides a D-Bus service called org.fastfood.McDonalds. Clients that want to talk to this service use the bus name org.fastfood.McDonalds to direct requests to mcdonaldsd.  mcdonaldsd provides a base object called /org/fastfood/McDonalds. This object implements the org.fastfood.McDonalds interface, which defines these method calls:

  • GetItems
  • Order

GetItems() returns an array of object paths representing all the things on the menu that you can order. So if you call it you’ll get something like this in return:

[ ‘/org/fastfood/McDonalds/Item/0’, ‘/org/fastfood/McDonalds/Item/1’ ]

Each of these returned object paths is a pointer to an object; mcdonaldsd probably even implements these as objects internally using Java or C++ or GObject or whatever. These objects are probably completely different (one may be a burger, one may be a drink, the other could be fries) but they all implement a common interface: org.fastfood.McDonalds.Item.

The org.fastfood.McDonalds.Item interface defines the following method calls:

  • GetName
  • GetType (returns either TYPE_BURGER, TYPE_DRINK, or TYPE_FRIES)
  • GetPrice
  • Consume

So even if you don’t know what exact type of object /org/fastfood/McDonalds/Item/0 is, you still can get a lot of information about it, enough to decide whether you want to order it or not.

Assume that item “0” is a “BigMac” and item “1” is “Coke”. These are clearly different objects, but each has a name, a calorie count, a price, and can be consumed.  Next, since each item is different (even though they all implement the common org.fastfood.McDonalds.Item interface) each item will implement other interfaces that define functionality specific to that type of item.

So item “0” (BigMac) implements the org.fastfood.McDonalds.Item.Burger interface which has the following methods:

  • Unwrap
  • AddMustard
  • RemovePickle (nobody likes those stupid limp pickles anyway)

And item “1” (Coke) implements the org.fastfood.McDonalds.Item.Drink interface which has the following methods:

  • PutLidOn
  • InsertStraw
  • RemoveStraw

Remember, since both objects *also* implement the base org.fastfood.McDonalds.Item interface, you can use the Consume() method to consume both items. But clearly, you don’t want to include the InsertStraw() method on the generic org.fastfood.McDonalds.Item interface, because all items implement that interface, and it would be pretty funny if you tried to call InsertStraw() on the BigMac object.  People would stare.

So interfaces are about logically separating method calls that have specific functionality, and thus any object that wants that functionality can implement that interface, instead of having every object type duplicate every call the interface defines.

So, with python-esque pseudocode:

# Get local proxy for the remove mcdonaldsd service
bus = get_service("org.fastfood.McDonalds")
mcds = bus.get_object("org.fastfood.McDonalds", "/org/fastfood/McDonalds")
burger_path = None
drink_path = None
# Lets read all the menu items
menu_items = mcds.GetItems()
for object_path in menu_items:
    item = bus.get_object("org.fastfood.McDonalds.Item", object_path)
    print "Item: %s price %s" % (item.GetName(), item.GetPrice())
    # Now let's figure out what we want to order; we'll order
    # the first burger we find and the first drink we find, but
    # only one of each.  We just had breakfast so we're not that
    # hungry.
    item_type = item.GetType()
    if item_type == TYPE_BURGER and burger is None:
        burger_path = object_path
    elif item_type == TYPE_DRINK and drink is None:
        drink_path = object_path
    # We've found a burger and drink on the menu, lets order them
    if burger_path and drink_path:
        break

# Did this place not get their latest deliveries or something?
if not burger_path or not drink_path:
    print "This restaurant doesn't have enough food for me."
    sys.exit(1)
food = mcds.Order([burger_path, drink_path])
if food.len() != 2:
    print "Oops, not enough money or something. Need to get a job."
    sys.exit(1)
# Yay, we got our order.  Now we take off the damn pickle.
burger = bus.get_object("org.fastfood.McDonalds.Item.Burger", burger_path)
burger.RemovePickle()
# And we're taking this to go, so we need a lid and straw for the drink
drink = bus.get_object("org.fastfood.McDonalds.Item.Drink", drink_path)
drink.InsertStraw()
try:
    drink.PutLidOn()
catch Exception, e:
    print "Oops, straw already inserted!"
# We were distracted by the smell of the burger and put the
# straw in before we put the lid on.  Oops.  Take the straw
# out, put the lid on, and then re-insert the straw
drink.RemoveStraw()
drink.PutLidOn()
drink.InsertStraw()
# All ready.  Now we can walk out, sit on the curb, and consume the
# burger and drink; note that even though burger_proxy and drink_proxy
# were created with D-Bus interfaces specific to their food type, we
# don't really need to create another interface just to call the
# generic Consume() method which both the burger and drink implement.
# Just give the method call the generic interface.
burger.Consume(dbus_interface="org.fastfood.McDonalds.Item")
drink.Consume(dbus_interface="org.fastfood.McDonalds.Item")

One thought on “Eat Burgers on the Short Bus”

  1. Your Python example code looks a bit wonky.

    Isn’t the first argument to Bus.get_object() the bus name rather than an interface name? So given the description you gave previously, these extra objects should belong to the same bus name as the “mcds” object.

    If you want to call methods on an object without specifying the interface, perhaps you wanted to use dbus.Interface(object, dbus_interface)?

Comments are closed.