Writing Servers

Russell Taylor edited this page Mar 30, 2015 · 3 revisions

The VRPN library is compiled both on the client side and the server side. The library includes the entire communication class (vrpn_Connection) and the base classes for the various devices (trackers, buttons, sound devices, force devices, etc).

There are separate #include files for each of the device types in the library. Currently, these include vrpn_Connection.h, vrpn_Tracker.h, vrpn_Button.h, and vrpn_ForceDevice.h. These files are used both on the client and server side, although both may derive specialized classes from the base device classes, or may even define their own device classes.

All device classes rely on the vrpn_Connection class to pass messages between the server and application. A vrpn_Device base class will know how to encode and decode its state or state updates in messages that are passed between a vrpn_Device_Remote class instance that the application creates and a vrpn_Device_Specific class that is written to handle the control of a specific device. For example, when an application communicates with a Polhemus 3Space tracker server, it creates a vrpn_Tracker_Remote instance and talks to a server that is using a vrpn_Tracker_3Space instance (both of which are derived from vrpn_Tracker).

Generic Server

There is a generic server, whose source code lives in server_src/vrpn.C. There is a Makefile and a Visual Studio project file to compile it into vrpn_server. This server reads instructions about which devices to open from a configuration file (default is vrpn.cfg in the directory where it is run, the -f flag lets you specify another file). This configuration file contains one line for each device that is to be opened. A complete sample configuration file with example declarations for each type of server object that can be run by the generic server is included in the source code as vrpn.cfg). Example lines for this file to configure trackers are:

vrpn_Tracker_Fastrak          Tracker0    /dev/ttyS0     115200
vrpn_Tracker_Dyna             Tracker0 1   /dev/ttyS0    19200
vrpn_Tracker_Flock           Tracker0 4   /dev/ttyC4    115200
vrpn_Tracker_Flock_Parallel  Tracker0 4  /dev/ttyC4    115200 /dev/ttyC0 /dev/ttyC1 /dev/ttyC2 /dev/ttyC3

Each of the lines lists the type of tracker, followed by parameters specific to that type. The first argument to each is the name of the device as it will be known to VRPN (in this case Tracker0, you would put different names if the trackers were all going on the same server; you might want a more descriptive name anyway). If this server is run on a machine named foobar, you would open the tracker as "Tracker0@foobar" in each case. The next argument for all but vrpn_Tracker_Fastrak is the number of sensors (the Fastrak driver opens all available sensors). The next argument is the name of the serial port to open, then the baud rate for that port. The vrpn_Flock_Parallel driver is made for the case where each sensor on the driver has its own serial connection; the port names for the sensor connections follow the baud rate. The controller is plugged into the initial port (/dev/ttyC4 in the example) for control signals; just as in the case where all reports come from the same port.

Building your own server

The name of all objects all begin with vrpn_. The base class for the object (from which both client and server objects are derived) will look something like vrpn_Tracker or vrpn_Button. Client objects should have the name _Remote at the end. Server objects have names specific to the device or class of devices that they serve (for example, _Fastrak for a certain kind of tracker). Objects to be used by the server (and which drive specific devices) will therefore look like vrpn_Tracker_3Space or vrpn_Tracker_Fastrak. The base class and _Remote device must be defined in the header file that is part of the library (vrpn_Tracker.h, for example). Specific server classes derived from the base class can be in separate header and source files, since they do not need to be compiled except on the architecture and machine where they run. This is the case for the 3Space tracker driver.

The base class for an object should handle the registration of message types and sender names with the connection that the object uses. It should also contain all routines for packing and unpacking the messages that are sent across the connection. This keeps all of the encoding and decoding in one class, and ensures that all derived classes use the same methods to do this.

A VRPN server will create a vrpn_Connection in a listening state and then create one of more device-specific server objects, each connected to this connection. It then cycles through each object, calling the mainloop() methods on the device objects and then on the connection object (to send messages generated by the local objects and to receive messages from the remote objects.

Example servers

A complicated server is the generic server that is found in server_src/vrpn.C. It makes use of the vrpn_Generic_Server_Object class that can be found in the server library. It knows how to parse a configuration file and start devices as described in that file. Your own server can make use of this same class to create devices as desired.

An example of a server and client running in the same process can be found in client_and_server.C in the server directory. Another example that verifies that you can delete remote objects can be found in test_vrpn.C in the same directory.

A simple example server is shown here. This server creates a tracker and button object, then runs forever.

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include "vrpn_Connection.h"
#include "vrpn_Button.h"
#include "vrpn_Tracker.h"
#include "vrpn_Tracker_Fastrak.h"

main (unsigned argc, char *argv[])
{
    vrpn_Connection *connection = vrpn_create_server_connection();
    vrpn_Tracker    *tracker;
    vrpn_Button *button;

    //Open a 3Space tracker
    tracker = new vrpn_Tracker_Fastrak("Tracker0", connection, "/dev/ttyS1", 19200);

    //Open a Python Button box on parallel port 1
    button = new vrpn_Button_Python("Button0", connection, 1);

    // Loop forever calling the mainloop()s for all devices and the connection
    while (true) {
        // Let all the buttons generate reports
        button->mainloop();

        // Let all the trackers generate reports
        tracker->mainloop();

        // Send and receive all messages
        connection->mainloop();
    }
}