Migrating from Player 1.6 to Player 2.0

Including a guide to updating your drivers. More...

Including a guide to updating your drivers.

Much changed in Player between 1.6 and 2.0, from the fundamental message model to the nuts and bolts of message formats. This page aims to ease the transition by explaining what changed (and sometimes why it changed). At least initially, the focus here will be on providing driver maintainers with the information necessary to update their code.

Fundamentals

Two core aspects of Player have changed:

TCP server vs. robot programming framework

Player 1.6.x was many things:

Though we tried to keep these aspects of Player separate, they were really inextricably intertwined. While Player was useful to a lot of people, it couldn't easily be extended or reused.

Player 2.0 aims to clarify and compartmentalize its components in such a way that they can be extended, resused, and replaced, as the situation demands. At its core, Player 2.0 comprises 5 components:

Because it's just so darn useful, Player 2.0 still contains a TCP player server. As you can imagine, this server is a short C++ program that uses the above libraries to provide the ability to parse a configuration file, load/instantiate device drivers, and allow clients access to these devices via XDR-encoded messages over a TCP socket.

Use the player server if it's what you need, but don't hesitate to use the various components however you like. For example, currently in development are SWIG-generated Java bindings to libplayercore. Using these bindings, you can write a native Java program that instantiates and control device drivers. You might then plug that Java program into a Jini network and exchange Player messages as serialized Java objects, instead of XDR-encoded structs.

State-based vs. message-passing

From its inception through version 1.6.x, Player was a state-based system. In this model, each device is presumed to have some time-varying state, and the goal of Player is to provide the ability to read and (sometimes) write this state. The content of a device's state and whether it is mutable is defined by the interface(s) that the device supports. For example, a mobile robot that supports the position2d interface maintains as its state the robot's position and velocity. These data are reported in the data messages produced by the robot and can be changed by command messages sent to the robot. This model is conceptually simple but is inflexible and not always appropriate.

Player 2.0 employs a more general message-passing model. In this approach, each device can produce a certain set of messages, and it can consume a certain set of messages. These messages are still defined by the interface(s) that the device supports, but they no longer need to report the entire state of the device. For example, in addition to periodic updates on its current position and velocity, a mobile robot might send out a different message when it has reached a goal or when a motor current limit was exceeded.

Details

A variety of details have changed, including:

Message formats

Because of the techniques used to encode messages for network transmission, Player 1.6.x had frustrating limits on the data types that were allowed. Specifically, floating point values were not supported, so real numbers were represented as integers in fixed point.

Player 2.0 is far more flexible in this regard. Virtually all C data types are supported, as are nested message structures and fixed- and variable-length one-dimensional arrays. Multi-dimensional arrays are not supported.

Unless otherwise noted, all message fields are represented in MKS units.

Arrays

Some arrays in message structures are fixed-length and some are variable-length. An array 'foo' is variable-length if the message structure containing 'foo' also contains a uint32_t called 'foo_count' ('foo_count' must appear before 'foo' in the structure). Otherwise, it is fixed-length. Basically, small arrays (2 or 3 elements) are fixed-length and bigger ones are variable-length.

The name 'foo_count' is special. It tells you how many items are actually in the array 'foo'. This information is used by the XDR functions during (de)marshaling. As a result, you must always fill in this field when sending a message containing a variable-length array (and you should always consult this field when receiving such a message).

For example, when sending a camera image, put the data in 'image' and fill in 'image_count' with the actual size of the frame. Then only those bytes will be transferred. Another example: when asking for the list of available devices, you send a player_device_devlist, with the field 'devices_count' set to 0. The server responds with it filled in.

Message type namespace

Player 2.0 defines a 2-level namespace of messages. At the first level, there are 7 types:

Each message also has an interface-specific subtype. For example, the laser interface defines 2 data messages: one contains a scan, while the other contains both a scan and a pose. The subtype field of the message header allows the recipient to disambiguate the two. The subtype of a response message will be identical to the corresponding request message.

Dynamic libs

All libraries are now built using libtool in both static and dynamic versions (assuming your system supports building and loading shared libraries).

Low-level details (how to update your driver)

The amount of work required to update a Player 1.6.x driver to the Player 2.0 API varies greatly from driver to driver. Simple, single-interface drivers tend to be pretty easy to update, whereas complex, multi-interface drivers can be quite tedious. There is no tool for automatically updating driver code; writing such a tool would be very difficult indeed.

Because each driver can be structured differently and use different parts of the Driver API, I don't have step-by-step instructions for updating a driver. Instead, below is a list of tips, hints, and things to look out for. I find that a combination of systematically following the items on this list and compilation by attrition (i.e., fix each problem as the compiler finds and complains about it) does the job.

As an example, if your driver is called foo, and is built from foo.cc, then your Makefile.am might look like this:

noinst_LTLIBRARIES =
if INCLUDE_FOO
noinst_LTLIBRARIES += libfoo.la
endif
AM_CPPFLAGS = -Wall -I$(top_srcdir)
libfoo_la_SOURCES = foo.cc