Tutorial - VRPN Server

Introduction

DOWNLOAD VRPN_SERVER.ZIP

We've already covered how to setup an existing VRPN server and how to use VRPN in your code in the first tutorial.

Here's probably the smallest amount of code in order to create your own standalone VRPN server.

We will go through all the steps so you can understand how things work.

Attached to this page (at the bottom), you will find a package that contains the code to create a server streaming data from one tracker, an analog of 10 axis, and 10 buttons.

The package includes a full VRPN 07.29 pre-compiled package, so you should just have to open the "vrpnServerSimple" solution and compile !

If you already have a VRPN package, you can remove the vrpn folder and change the include/link folders in Visual Studio.

The code of the package is for windows and would require only minimum adaptation to work on other platforms.

If you want to share your VRPN servers we would be happy to store them here so that they can be centralized in the Tracking section.

Streaming data from a tracker

If you want to stream 3d tracker data (position + orientation), you will need to create a class that derives off of vrpn_Tracker and overload the mainloop() method :

class myTracker : public vrpn_Tracker

{

public:

myTracker( vrpn_Connection *c = 0 );

virtual ~myTracker() {};

virtual void mainloop();

protected:

struct timeval _timestamp;

};

You will set the actual values to be streamed in this mainloop method. The members to be set are pos for the position reported by the tracker, and d_quat for the orientation.

void

myTracker::mainloop()

{

vrpn_gettimeofday(&_timestamp, NULL);

vrpn_Tracker::timestamp = _timestamp;

// We will just put a fake data in the position of our tracker

static float angle = 0; angle += 0.001f;

// the pos array contains the position value of the tracker

// XXX Set your values here

pos[0] = sinf( angle );

pos[1] = 0.0f;

pos[2] = 0.0f;

// the d_quat array contains the orientation value of the tracker, stored as a quaternion

// XXX Set your values here

d_quat[0] = 0.0f;

d_quat[1] = 0.0f;

d_quat[2] = 0.0f;

d_quat[3] = 1.0f;

char msgbuf[1000];

d_sensor = 0;

int len = vrpn_Tracker::encode_to(msgbuf);

if (d_connection->pack_message(len, _timestamp, position_m_id, d_sender_id, msgbuf,

vrpn_CONNECTION_LOW_LATENCY))

{

fprintf(stderr,"can't write message: tossing\n");

}

server_mainloop();

}

If you want to report multiple trackers, you can either create several instances of the class, or report several trakers in the same class.

For the last option, you have to change modify the pos and d_quat values, set the d_sensor value to the channel you want to report, and repeat the encode/pack/mainloop functions.

In the constructor of your tracker, you will also be able to specify the name of your tracker :

myTracker::myTracker( vrpn_Connection *c /*= 0 */ ) :

vrpn_Tracker( "Tracker0", c )

{

}

The "Tracker0" is the name that you will have to specify when you connect to your server : Tracker0@localhost for example.

You might also have noticed that the constructor takes a vrpn_Connection as an argument.

This is the network socket that will be used to stream the data, and is created only once for all your devices, like that :

// Creating the network server

vrpn_Connection_IP* m_Connection = new vrpn_Connection_IP();

The constructor of the vrpn_Connection_IP can take several arguments, among which the listen port (which is 3883 by default). You can check the options in the vrpn_Connection.h file.

The last thing to run the server is to call mainloop on all your devices AND on the connection, otherwise nothing will happen :

while(true)

{

serverTracker->mainloop();

m_Connection->mainloop();

// Calling Sleep to let the CPU breathe.

SleepEx(1,FALSE);

}

If you want your server to stream as fast as possible, even if it means taking 100% of a CPU, you can remove the Sleep function. This should only be used if you have a dedicated computer to stream the data.

If you run your VRPN server on the same machine as your application, you're probably better of with at least a Sleep of 0ms : this will simply yield the process and give some time for the system to execute other processes).

With a Sleep of 1ms, there may be a ridiculously small lag, but at least the server will not eat all your CPU.

This is the same as the -millisleep option of the standard vrpn_server.

Analogs and Buttons

Streaming analog and button values is roughly the same except two important things :

1/ With the tracker, you don't have to specify how many trackers you will stream. With analog and buttons, you must specify it with :

vrpn_Button::num_buttons = 10;

or

vrpn_Analog::num_channel = 10;

2/ You have to add a report_changes function call :

vrpn_Analog::report_changes();

vrpn_Button::report_changes();

VRPN Server, at your service

Here's the whole code for a tracker, and an analog and a button with 10 channels each. Don't forget to link with vrpn.lib

// VRPN Server tutorial

// by Sebastien Kuntz, for the VR Geeks (http://www.vrgeeks.org)

// August 2011

#include <stdio.h>

#include <tchar.h>

#include <math.h>

#include "vrpn_Text.h"

#include "vrpn_Tracker.h"

#include "vrpn_Analog.h"

#include "vrpn_Button.h"

#include "vrpn_Connection.h"

#include <iostream>

using namespace std;

/////////////////////// TRACKER /////////////////////////////

// your tracker class must inherit from the vrpn_Tracker class

class myTracker : public vrpn_Tracker

{

public:

myTracker( vrpn_Connection *c = 0 );

virtual ~myTracker() {};

virtual void mainloop();

protected:

struct timeval _timestamp;

};

myTracker::myTracker( vrpn_Connection *c /*= 0 */ ) :

vrpn_Tracker( "Tracker0", c )

{

}

void

myTracker::mainloop()

{

vrpn_gettimeofday(&_timestamp, NULL);

vrpn_Tracker::timestamp = _timestamp;

// We will just put a fake data in the position of our tracker

static float angle = 0; angle += 0.001f;

// the pos array contains the position value of the tracker

// XXX Set your values here

pos[0] = sinf( angle );

pos[1] = 0.0f;

pos[2] = 0.0f;

// the d_quat array contains the orientation value of the tracker, stored as a quaternion

// XXX Set your values here

d_quat[0] = 0.0f;

d_quat[1] = 0.0f;

d_quat[2] = 0.0f;

d_quat[3] = 1.0f;

char msgbuf[1000];

d_sensor = 0;

int len = vrpn_Tracker::encode_to(msgbuf);

if (d_connection->pack_message(len, _timestamp, position_m_id, d_sender_id, msgbuf,

vrpn_CONNECTION_LOW_LATENCY))

{

fprintf(stderr,"can't write message: tossing\n");

}

server_mainloop();

}

/////////////////////// ANALOG /////////////////////////////

// your analog class must inherin from the vrpn_Analog class

class myAnalog : public vrpn_Analog

{

public:

myAnalog( vrpn_Connection *c = 0 );

virtual ~myAnalog() {};

virtual void mainloop();

protected:

struct timeval _timestamp;

};

myAnalog::myAnalog( vrpn_Connection *c /*= 0 */ ) :

vrpn_Analog( "Analog0", c )

{

vrpn_Analog::num_channel = 10;

vrpn_uint32 i;

for (i = 0; i < (vrpn_uint32)vrpn_Analog::num_channel; i++) {

vrpn_Analog::channel[i] = vrpn_Analog::last[i] = 0;

}

}

void

myAnalog::mainloop()

{

vrpn_gettimeofday(&_timestamp, NULL);

vrpn_Analog::timestamp = _timestamp;

// forcing values to change otherwise vrpn doesn't report the changes

static float f = 0; f+=0.001;

for( unsigned int i=0; i<vrpn_Analog::num_channel;i++)

{

// XXX Set your values here !

channel[i] = i / 10.f + f;

}

// Send any changes out over the connection.

vrpn_Analog::report_changes();

server_mainloop();

}

/////////////////////// BUTTON /////////////////////////////

// your button class must inherit from the vrpn_Button class

class myButton : public vrpn_Button

{

public:

myButton( vrpn_Connection *c = 0 );

virtual ~myButton() {};

virtual void mainloop();

protected:

struct timeval _timestamp;

};

myButton::myButton( vrpn_Connection *c /*= 0 */ ) :

vrpn_Button( "Button0", c )

{

// Setting the number of buttons to 10

vrpn_Button::num_buttons = 10;

vrpn_uint32 i;

// initializing all buttons to false

for (i = 0; i < (vrpn_uint32)vrpn_Button::num_buttons; i++) {

vrpn_Button::buttons[i] = vrpn_Button::lastbuttons[i] = 0;

}

}

void

myButton::mainloop()

{

vrpn_gettimeofday(&_timestamp, NULL);

vrpn_Button::timestamp = _timestamp;

// forcing values to change otherwise vrpn doesn't report the changes

static int b=0; b++;

for( unsigned int i=0; i<vrpn_Button::num_buttons;i++)

{

// XXX Set your values here !

buttons[i] = (i+b)%2;

}

// Send any changes out over the connection.

vrpn_Button::report_changes();

server_mainloop();

}

////////////// MAIN ///////////////////

int _tmain(int argc, _TCHAR* argv[])

{

// Creating the network server

vrpn_Connection_IP* m_Connection = new vrpn_Connection_IP();

// Creating the tracker

myTracker* serverTracker = new myTracker(m_Connection );

myAnalog* serverAnalog = new myAnalog(m_Connection );

myButton* serverButton = new myButton(m_Connection );

cout << "Created VRPN server." << endl;

while(true)

{

serverTracker->mainloop();

serverAnalog->mainloop();

serverButton->mainloop();

m_Connection->mainloop();

// Calling Sleep to let the CPU breathe.

SleepEx(1,FALSE);

}

}

Some interesting notes by Jan Ciger :

Note1: You can use vrpn_sleep instead of SleepEX for Mac and Linux portability.

Note2: No need to encode and pack the message of the tracker yourself - just use

tracker->report_pose(0, t, position0, quaternion0);

0 - sensor, t - struct timeval_t, the rest is 3 resp. 4 element array of vrpn_float64s

Note3: You can use multiple inheritance to create a device that has multiple capabilities :

class myAnalogButtonTracker : public vrpn_Analog, public vrpn_Tracker, public vrpn_Button