Player/Stage Drivers

A Player driver, used for abstracting the communications between your physical robot and your client code, is a series of functions contained in an inherited base class. A driver implements standard methods, to communicate with proxies, listed in the Player documentation. A proxy is a Player-defined standard communication for a given object, such as sonar or vehicle movement. Some proxies and drivers provide complex functionality, such as local path planning and vision. To deploy a driver, build the driver code into a special library (showed below) and create a paired *.cfg file. Launch Player with the only parameter as your *.cfg file name, then launch your special client code, which connects through the Player server, then interfaces through the standard proxies to your driver.



Review the Player/Stage Overview for a good basis of this technology. Many drivers already exist, and your needs might have already been implemented. See the Player drivers list for current implementations.

Driver Class Overview
 This section is still based on Player 2.1, Player 3.0 uses the ThreadedDriver core class, and some of the names of overloaded functions have changed

The base class that all drivers must inherit from to implement the Player functionality is, simply enough, the Player core "Driver" class. This Player class is the core of the driver level abstraction as seen in the above graphic, that takes Player server commands such as data query and robot instructions (these are given through proxies) and actually implements these, running actions or returning data. It is up to you, the developer, to choose the best method of implementation.

Each driver instance runs on its own unique thread, letting the developer choose the time and order in which certain commands are parsed. The driver must manage the data coming to and from both the lower-level robot and the higher-level Player server. Since it is the Player server's job to manage the top-level commands, a driver rarely needs to implement any type of complex logic.

All drivers are compiled into shared libraries, and are only loaded at run-time by the Player server. Each driver is paired with a configure (*.cfg) file that shows which proxies the related driver provides functionality for.

The below lists the functions that may be overloaded by the inheriting custom driver class, and their related purpose.

Source(s): Drivers solutions


 * Driver(...)
 * Constructor, providing configure file parsing
 * ~Driver 
 * Destructor, release data/resources/connections
 * Setup
 * Initializes the driver data; Different from the constructor since a driver may be stopped/started multiple times
 * Shutdown
 * Destructs the driver data; Different from the destructor since a driver may be stopped/started multiple times
 * Main
 * Threaded main loop, core driver function
 * Publish(...)
 * Publishes driver data through one of the implemented proxies
 * ProcessMessages(...)
 * A function that processes messages directed towards your driver's interface(s)
 * Update
 * (Advanced feature) If the driver is not threaded, this function is called during every refresh
 * MainQuit
 * Cleanup/release function for the Main threaded main loop function

Run-Time Driver Cycle
At run-time, a driver goes through a simple cycle: First, the custom driver class is instanced by the Player server, which calls both the class constructor and then then Setup function. If the driver goes through the setup routines without error, the thread sits idle until another device tries to subscribe to it (unless the alwayson option is set to true). Once a subscription is made, Player starts the main thread, which calls the Main function. This function, due to the nature of threads, must manage its own loop and resource consumption. Having a "while(true) { /* code */ }" loop inside of Main is valid, but may consume too many resources as it constantly loops until stopped. A better approach is to call a sleep or delay function at the end of the Main loop, with an appropriate delay time (a few milliseconds should suffice, depending on how often the driver needs to update.) Some robots may need faster or slower updates, but reducing the amount of sleep time also reduces the amount of system resources other threads and the system may use. Also note that instead of placing code in the Main function, you can overload the Update function and place a single Update call within the Main function, so that both a threaded and non-threaded run-time work appropriately.



Within this Main thread, you should update the driver by processing any new messages waiting in the driver's message queue. To do this, call the ProcessMessages function. This will call your driver's ProcessMessage(...) function with each message waiting in the driver's queue as the input argument. Your driver should be able to process messages (commands, requests, etc.) appropriate for the functionality of the driver. For example, if your driver is controlling a motor controller, you might program the ability to handle a POSITION2D_CMD_VEL message. In ProcessMessage(...), you should first test for the message type. If this message type is not a message you've chosen to handle, return a -1 to represent an error. Once the message type has been determined, you may call your own parsing function, which checks the message sub-type (via the static Message::MatchMessage(...) function), and determine what to do. Once the messages have been parsed, you may now update your driver internals (such as manually query vehicle speed, direction, GPS) and return any new data via the Publish(...) function. The main loop will then continue normally unless an exit flag has been posted.

Once the driver is closed, the MainQuit function is called, which shuts down the driver and puts it in an idle state. When the server is shut down, the destructor is called, which cleans up any remaining resources.

Using Client Libraries
Client Libraries are a set of libraries that allow you to interact with the Player server from an external program, written in C, C++, Python, or Ruby. The client libraries communicate with the server by creating Proxies to desired interfaces. The libplayerc++ Proxies are C++ classes defined in the client library that provide methods to pass commands and/or query data to and from the Player server. In physical terms a proxy represents an interface provided by a specific component, device, or algorithm on your robot such as a sonar, sonar array, laser rangefinder, path planner, or motor controller.

Providing Interfaces
Drivers do all of their communication through the interfaces they provide and require. For each interface your driver provides, a unique player_devaddr_t variable must exist in your driver's class. This variable is a type of identifier for your unique component and robot. In your class constructor, when you parse your configuration file, it is important to check whether or not your driver is told to provide this interface. Some drivers provide optional interfaces, which do not load if the interface is not specified in the "provides" section of the driver's configuration file. To find out if your driver is being told to provide an interface, your driver should call ReadDeviceAddr(...) function, which is a member of the configuration file to find which interface address the driver must provide. If the configuration file supports the requested interface, you must then register this now correctly initialized unique key with Player by calling the AddInterface(...) function derived from the driver's base class. If any error takes place during the registration, use the SetError(int errCode) function, which will quit Player and post the related error code message.

Your newly registered interface functionality will enable you to receive and process messages sent to your interface from other drivers and clients. This is done by parsing messages coming through the Driver class function ProcessMessage(...). Each interface supports several types of message, such as get/set instructions. You must manually check the message header type to determine whether the message should be handled. Once the message type is determined, it should be processed accordingly, and the ProcessMessage function should return(0). If an error occurred during processing, or a message isn't handled by your driver, a negative value must be returned.

Since the two major command types are get data and set data commands, the methods for processing are relatively easy. For both types, each sub-type must be checked, meaning the exact interface type we are trying to parse. This can be done using the Message::MatchMessage(...) static function. Compare the header data, with the type, and sub-type (browse through the related proxies, and find the defined constants) to correctly determine the exact actions to take. In general, for get commands, you will have to post-back data using the Driver class' Publish(...) function. For set commands, you will need to take the source data pointer, cast to the appropriate data type structure, and access the correct variables. It is up to you, as the designer, to manage when the command will be executed. Certain interface types, and design needs, may allow you to publish your own data at your own discretion within the driver since the Player process will manage the data accordingly.

When the driver is released and calling it's own destructor functions, you can call the Unsubscribe(...) function to release the unique handler on any contained proxies.

Sample Code
Plese see Writing a Player Plugin for more information about Player driver code.

Compiler Settings
 This code was written with Player 2.1 plugins, Player 3.0 drivers can be compiled in this way, or by using CMake

Since Player driver code must be compiled using special options and into a library with no external references, the GNU C/C++ compiler options may seem complicated. Here is a sample make file with a walk through of the options

Note that since this is a makefile, correct tabbing is important

The first two lines are constants that defined the source files (Driver.cpp and Utilities.cpp). In some projects you may only need one single code file, but in larger projects you must link all external code to keep the library fully encapsulated without any external references. Many of the following parameters are detailed in the GNU compiler documentation. (This link is for GCC version 4.3.0)

For the build all tag, we make sure we build on any updates to the Driver/Utilities code base. The first line we call g++, our GNU C++ compiler, and we pass the parameters -Wall -Wextra -fpic -g3. These are to give us all warnings generated by our code, which is a good programming practice. The next parameter, -fpic, generates position independent code which is needed to build the driver as a shared library. The next parameter `pkg-config --cflags playercore` links our playercore library installation with this code. The -c option generates object files (prevents linking) which is then manipulated in the following line.

On this final line, we will attempt to link the generated objects into a shared library. The previously mentioned warning tags are included, followed by the -nostartfiles tag. This tag prevents standard system start-up files to be linked, creating a more fully-encapsulated library. The -shared tag creates a shared library output (Which is why the -fpic parameter is useful). The -rdynamic tag allows back traces from the program. The final parameter -o is simply the output file name, and is followed by the object files this command is linking.

Configuration File
A configuration file, in terms of Player drivers, is a text file with the *.cfg extension that defines the supported drivers for a system, and details for each driver. A driver defines its name, what interfaces it may support, as well as any configuration that might be needed by the driver to run properly. The standard syntax for a configuration file is as follows: