-
Notifications
You must be signed in to change notification settings - Fork 216
Creating a new device class
If you are writing a new driver for an existing type of device (a new Tracker, for example), see the page on writing new drivers. If you have read that page and plan to write a whole new type of device class, read on.
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 names like vrpn_Tracker_Remote. Server objects have names specific to the particular device they serve (for example, vrpn_Tracker_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 vrpn_Dial device class provides a simple example of a device, and can be used as a model for new classes.
Many of the functions required in a root base class are encapsulated in the vrpn_BaseClass, from which all devices should inherit. The device must still have its own root device class, to define the encode/decode routines and to specialize the pure virtual methods of the vrpn_BaseClass. The root device 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 sample class hierarchy is the vrpn_Dial class, which inherits from vrpn_BaseClass; vrpn_Dial_Remote derives from vrpn_Dial, as do all of the servers for various devices (such as the vrpn_Dial_Example_Server) -- both the remote and example are described in vrpn_Dial.h, and their methods defined in vrpn_Dial.C. Drivers for specific devices are found in a separate file for each device.
The vrpn_BaseClass object (and the virtual vrpn_BaseClassUnique class from which it derives) implement much of what is needed for an object type in VRPN, and includes pure virtual methods to ensure that required methods are implemented. The virtual vrpn_BaseClassUnique is present so that objects that inherit from more than one VRPN object class (for example, servers that implement both trackers and buttons) have only one copy of data members and methods. This is done under the assumption that all subclasses will use the same server name! For example, the Phantom server inherits from Tracker, Button and ForceDevice; the client opens three remote devices to communicate with it, but they all share the same name. This convention should be followed by new device types.
The BaseClass constructor takes the name of the object (which may include the full path, such as Tracker0@myhost.mydomain) and an optional pointer to a vrpn_Connection. If no Connection (or a NULL Connection) pointer is passed, the object opens a new vrpn_Connection by calling vrpn_get_connection_by_name() with the name of the object as a parameter (this is how Remote objects find their connections). Servers will normally have opened a Connection to receive client requests on; a pointer to this Connection should be passed. Derived classes need to call vrpn_BaseClass::init() within their constructors; this allows the server and message type methods (described below) to be called. The constructor and init() assign values to the following member variables: d_connection points to the vrpn_Connection that the device is using to communicate, d_servicename is a string that contains the base name of the device (the part before the @), d_sender_id is the ID to be used when sending messages on d_connection, d_text_message_id is used to send and receive text messages. The init() function calls register_types() (a pure virtual function that should be defined in the root base class) to allow the derived class to register its message types with its d_connection. It also calls register_senders() (with a do-nothing default function that can be overridden) to allow the derived class to register any additional senders that it will be implementing (this will probably never be needed).
Devices that need to receive messages should use the member function register_autodeleted_handler() to register their handlers; this will ensure that the handlers are removed when the object is deleted or goes out of scope; failure to do this causes segmentation violations. Devices that wish to send text messages (either info, warning or error) should use the method send_text_message() to do so. Messages that are warnings or errors will normally be printed by the default system text handler, as described in the VRPN text handling page.
Most objects in VRPN are set up as clients and servers (servers run the devices and clients are used in the application code to communicate with them). The Remote object is the client and the device object is the server, regardless of which way most communication goes. All client objects should call client_mainloop(), and all server objects should call server_mainloop() within their mainloop() functions. This implements the mechanism by which clients can verify that their server is active, and will print warnings and then errors if its connection is dropped. In the future, it will also be used to implement a mechanism that allows clients to re-initialize state in the server when connections are dropped and re-established (such as which buttons should be toggles).
BaseClass also implements a connectionPtr() method, which applications can use to find out what connection an object is using.