Writing a device driver

Russell Taylor edited this page Apr 10, 2015 · 4 revisions

The root device class for each device type (vrpn_Tracker, etc.) handles the registration of message types and sender names used by all objects of that type. It also contains routines for packing and unpacking the messages that are sent between the client-side class (vrpn_Tracker_Remote) and the servers (vrpn_Tracker_Fastrak, etc.). This keeps all of the encoding and decoding in one class, and ensures that all derived classes use the same methods to do this. The device class and _Remote device are defined in the header file that is part of the library (vrpn_Tracker.h, for example), and their methods described in source files (vrpn_Tracker.C, 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 (the UNC Ceiling Tracker server, for example). Some servers (such as the example servers) are present in the library directly.

The easiest way to implement a new server is to copy the server that is most like the one you want, and modify it to suit. Towards this end, simple example server classes have been provided for several of the common devices within VRPN (see below).

Step-by-step example (using a HID-based tracker)

  1. Find a device that uses a communication mechanism like the one you want to use, so that you can use that server as an example. A simple example for this would be the vrpn_Tracker_SpacePoint class. (If there is not a driver for the same interface and the same class, you want to find a simple example of each -- perhaps one device that derives from serial and another from Button.)
  2. Copy the existing source files and rename the class and all includes and comments to your new device. In this example, you'd copy vrpn_Tracker_SpacePoint.h to vrpn_Tracker_MYDEVICE.h and vrpn_Tracker_SpacePoint.C to vrpn_Tracker_MYDEVICE.C. Then edit each file to change the class names to MYDEVICE. (Of course, "MYDEVICE" actually stands for the name of the device itself, for example "Wintracker"). If the vendor sells multiple similar devices, then it probably makes sense to have a base class that handles common functions and then derived classes for each device; in this case, you'd name the files after the vendor: vrpn_Tracker_VRSpace.C and have the Wintracker as a subclass.
  3. Add the device files into the build system. After this, you should be able to compile VRPN again (the new classes won't function with the new device, but there should not be any name collisions if you renamed things correctly).
    1. Edit CMakeLists.txt and add the .C and .h files. This is easily done by searching for the device name you copied from (vrpn_Tracker_SpacePoint in this case) and adding entries for your new device.
    2. Edit Makefile and do the same thing.
    3. Edit vrpn.vcproj and vrpndll.vcproj to make duplicate entries for the .C and .h files (this is more complicated because you have to copy the 20 lines associated with the .C file and the 4 lines associated with the .h file
  4. Modify the source code so that it handles your new device. For our example, if the new tracker sends reports automatically then you should only need to change the product/vendor ID and the on_data_received() function to parse the new reports correctly. If your new tracker does not include buttons, you'd also remove all references to vrpn_Button from the device to clean things up.
  5. Add an entry into the vrpn_Generic_server_object class by editing the .h and .C files. Again, the easiest thing to do is to search for the device you are copying from and duplicate it. This will involve adding a #include for the device and a setup_ function declaration in the header file and then defining a setup_ function and a check for when to call it in the .C file. Edit the setup_ function to parse whatever command-line arguments you will need to let the user configure the device.
  6. Finally, you update the vrpn.cfg file in the server_src directory to add an entry for your device. Again, you can copy the entry for the previous device and then change it to suit.
  7. Once you have done the above steps, you should be able to run a server for your new device by editing the vrpn.cfg file to uncomment the line describing your device. Also, others will be able to start using your device. When you've tested this, please send the code back to us for inclusion in the main repository so that everyone can use the new device.

Adding new capabilities to existing devices

When a server for a new device has capabilities that are not supported by the base class, there are two possible courses of action. (1) If the new capability is likely to be shared by several devices of the same type (such as the ability to set how often a tracker sends reports), then the root device class is extended to include encode and decode methods for a new message type and the _Remote class is extended to send or receive messages of the that type. Particular tracker drivers that are able to handle the new commands register handlers for the new messages (using the inherited method register_autodeleted_handler()), or emit the new types of information messages. It is important that the _Remote class continues to work with both devices that handle/emit the new messages and those that do not. (2) If the device is a highly-specific or experimental one, then the new messages and handlers can be built directly into the device driver itself, and the application can send and receive messages using the raw functions in vrpn_Connection, or a separate object (with the same name as the device) can be created just to send/receive the new messages.

It is advisable to think of the most general form of a new message, and attempt to build it into the root device, whenever possible. This allows the maximum code re-use and flexibility with using different devices from the same application.

Creating completely new devices

If you find that the capabilities of the new device are different enough from the existing device classes (or have different semantics), it is better to create a new device class than to try and shoe-horn the server into one of the existing classes. This was done when vrpn_Dial was created (it was deemed to be different enough in semantics from vrpn_Analog to warrant this); even though you may implement either an analog device or a dial from the same physical potentiometer (the difference is that a dial is used to set orientation and an analog is used to give a range of values). If you decide to do this, see the creating a new device class page.