vrpn  07.33
Virtual Reality Peripheral Network
vrpn_Analog_Radamec_SPI.C
Go to the documentation of this file.
1 // vrpn_Radamec_SPI.C
2 // This is a driver for the Radamec Serial Position Interface
3 // "virtual set" camera tracking rig. It plugs into a serial
4 // line and communicates using RS-232 (this is a raw-mode driver).
5 // You can find out more at www.radamec.com. They have a manual with
6 // a section on the serial interface; this code is based on that and
7 // communications with the vendor and experimentation. It was written in
8 // August 2000 by Russ Taylor.
9 
10 // INFO about how the device communicates:
11 // Reports coming from the device have a 1-byte header that tells
12 // which of 7 possible commands is being sent (0xA0-0xA6). Each then has
13 // a 1-byte Camera ID, then other parameters specific to the type, then
14 // a CRC checksum.
15 // Most of the report types can be sent to the instrument to set
16 // various parameters. Report type 4 is mainly a command/query message,
17 // which the device echoes.
18 
19 
20 #include <stdio.h> // for sprintf
21 #include <string.h> // for NULL, memcpy
22 
24 #include "vrpn_BaseClass.h" // for ::vrpn_TEXT_ERROR, etc
25 #include "vrpn_Serial.h"
26 #include "vrpn_Shared.h" // for timeval, vrpn_unbuffer, etc
27 #include "vrpn_MessageMacros.h" // for VRPN_MSG_INFO, VRPN_MSG_WARNING, VRPN_MSG_ERROR
28 
29 #undef VERBOSE
30 
31 // Defines the modes in which the device can find itself.
32 #define STATUS_RESETTING (-1) // Resetting the device
33 #define STATUS_SYNCING (0) // Looking for the first character of report
34 #define STATUS_READING (1) // Looking for the rest of the report
35 
36 #define MAX_TIME_INTERVAL (2000000) // max time between reports (usec)
37 
38 // This creates a vrpn_Radamec_SPI and sets it to reset mode. It opens
39 // the serial device using the code in the vrpn_Serial_Analog constructor.
40 
42  const char * port, int baud):
43  vrpn_Serial_Analog(name, c, port, baud, 8, vrpn_SER_PARITY_ODD),
44  _numchannels(4), // This is an estimate; will change when reports come
45  _camera_id(-1) // Queried from the controller during reset
46 {
47  // Set the parameters in the parent classes
49 
50  // Set the status of the buttons and analogs to 0 to start
51  clear_values();
52 
53  // Set the mode to reset
55 }
56 
58 {
59  int i;
60 
61  for (i = 0; i < _numchannels; i++) {
63  }
64 }
65 
66 
75 unsigned char vrpn_Radamec_SPI::compute_crc(const unsigned char *head, int len)
76 {
77  int i;
78  unsigned char sum;
79 
80  // Sum up the bytes, allowing them to overflow the unsigned char
81  sum = 0;
82  for (i = 0; i < len; i++) {
83  sum = (unsigned char)( sum + head[i] );
84  }
85 
86  // Unsigned subtraction from 40H, again allowing the subtraction to overflow
87  return (unsigned char)(0x40 - sum);
88 }
89 
96 int vrpn_Radamec_SPI::send_command(const unsigned char *cmd, int len)
97 {
98  int ret;
99  unsigned char *outbuf = new unsigned char[len+1]; // Leave room for the CRC
100 
101  // Put the command into the output buffer
102  memcpy(outbuf, cmd, len);
103 
104  // Add the CRC
105  outbuf[len] = compute_crc(cmd, len);
106 
107  // Send the command
108  ret = vrpn_write_characters(serial_fd, outbuf, len+1);
109 
110  // Tell if this all worked.
111  if (ret == len+1) {
112  return 0;
113  } else {
114  return -1;
115  }
116 }
117 
118 
123 vrpn_uint32 vrpn_Radamec_SPI::convert_24bit_unsigned(const unsigned char *buf)
124 {
125  vrpn_uint32 retval;
126  unsigned char bigend_buf[4];
127  const unsigned char *bufptr = bigend_buf;
128 
129  // Store the three values into three bytes of a big-endian 32-bit integer
130  bigend_buf[0] = 0;
131  bigend_buf[1] = buf[0];
132  bigend_buf[2] = buf[1];
133  bigend_buf[3] = buf[2];
134 
135  // Convert the value to an integer
136  vrpn_unbuffer((const char **)&bufptr, &retval);
137  return retval;
138 }
139 
140 
145 vrpn_int32 vrpn_Radamec_SPI::convert_16bit_unsigned(const unsigned char *buf)
146 {
147  vrpn_int32 retval;
148  unsigned char bigend_buf[4];
149  const unsigned char *bufptr = bigend_buf;
150 
151  // Store the three values into two bytes of a big-endian 32-bit integer
152  bigend_buf[0] = 0;
153  bigend_buf[1] = 0;
154  bigend_buf[2] = buf[0];
155  bigend_buf[3] = buf[1];
156 
157  // Convert the value to an integer
158  vrpn_unbuffer((const char **)&bufptr, &retval);
159  return retval;
160 }
161 
180 double vrpn_Radamec_SPI::int_to_pan(vrpn_uint32 val)
181 {
182  return (((int)val) - 0x7ffff) / 900.0;
183 }
184 
185 double vrpn_Radamec_SPI::int_to_zoom(vrpn_uint32 val)
186 {
187  //XXX Unknown conversion, return the raw value
188  return val;
189 }
190 
191 double vrpn_Radamec_SPI::int_to_focus(vrpn_uint32 val)
192 {
193  //XXX Unknown conversion, return the raw value
194  return val;
195 }
196 
197 double vrpn_Radamec_SPI::int_to_height(vrpn_uint32 val)
198 {
199  //XXX Unknown conversion, send the integer along unchanged
200  return val;
201 }
202 
206 double vrpn_Radamec_SPI::int_to_X(vrpn_uint32 mm, vrpn_uint32 frac)
207 {
208  return 0.001 * (mm + frac/65536.0);
209 }
210 
214 {
215  return 0.01 * val;
216 }
217 
218 
219 // This routine will reset the Radamec_SPI, requesting the camera number from the
220 // device and then turning on stream mode. XXX The device never seems to return a
221 // response to these queries, but always seems to go ahead and spew reports, even
222 // when not genlocked. If you find a device that doesn't send reports, this may
223 // be a good place to look for the problem.
224 // Commands Responses Meanings
225 // <A4><FF><02><CRC> <A4><##><02><CRC> Request camera number; returns camera number in ##
226 // <A4><##><01><CRC> <A4><##><01><CRC> Start stream mode on the camera we were told we are
227 
229 {
230  unsigned char command[128];
231 /* XXX commented out, since the response doesn't come, but the device still works.
232  struct timeval timeout;
233  unsigned char inbuf[128];
234  int ret;
235  char errmsg[256];
236 */
237  //-----------------------------------------------------------------------
238  // Send the command to request the camera ID and then read the response.
239  // Give it a reasonable amount of time to finish (2 seconds), then timeout
240 
242  sprintf((char *)command, "%c%c%c", 0xa4, 0xff, 0x02);
243  send_command((unsigned char *)command, 3);
244 /* XXX commented out, since the response doesn't come, but the device still works.
245  timeout.tv_sec = 2;
246  timeout.tv_usec = 0;
247  ret = vrpn_read_available_characters(serial_fd, inbuf, 4, &timeout);
248  inbuf[4] = 0; // Make sure string is NULL-terminated
249  if (ret < 0) {
250  perror("vrpn_Radamec_SPI reset: Error reading camera ID from device\n");
251  return -1;
252  }
253  if (ret == 0) {
254  VRPN_MSG_ERROR("reset: No response to camera ID from device");
255  return -1;
256  }
257  if (ret != 4) {
258  sprintf(errmsg,"reset: Got %d of %d expected characters for camera ID\n",ret, 4);
259  VRPN_MSG_ERROR(errmsg);
260  return -1;
261  }
262 
263  // Make sure the string we got back is what we expected and then find out the camera ID
264  if ( (inbuf[0] != 0xa4) || (inbuf[2] != 0x02) || (inbuf[3] != compute_crc(inbuf,3)) ) {
265  VRPN_MSG_ERROR("reset: Bad response to camera # request");
266  return -1;
267  }
268  _camera_id = inbuf[1];
269 */
270  //-----------------------------------------------------------------------
271  // Send the command to put the camera into stream mode and then read back
272  // to make sure we got a response.
273 
274  sprintf((char *)command, "%c%c%c", 0xa4, _camera_id, 0x01);
275  send_command(command, 3);
276 /* XXX commented out, since the response doesn't come, but the device still works.
277  timeout.tv_sec = 2;
278  timeout.tv_usec = 0;
279  ret = vrpn_read_available_characters(serial_fd, inbuf, 4, &timeout);
280  inbuf[4] = 0; // Make sure string is NULL-terminated
281  if (ret < 0) {
282  VRPN_MSG_ERROR("reset: Error reading from device");
283  return -1;
284  }
285  if (ret == 0) {
286  VRPN_MSG_ERROR("reset: No response from device");
287  return -1;
288  }
289  if (ret != 4) {
290  sprintf(errmsg,"vrpn_Radamec_SPI reset: Got %d of %d expected characters\n",ret, 4);
291  VRPN_MSG_ERROR(errmsg);
292  return -1;
293  }
294 
295  // Make sure the string we got back is what we expected
296  if ( (inbuf[0] != 0xa4) || (inbuf[1] != _camera_id) || (inbuf[2] != 0x01) ||
297  (inbuf[3] != compute_crc(inbuf,3)) ) {
298  VRPN_MSG_ERROR("reset: Bad response to start stream mode command");
299  return -1;
300  }
301 */
302  // We're now waiting for a response from the box
304 
305  VRPN_MSG_WARNING("reset complete (this is good)");
306 
307  vrpn_gettimeofday(&timestamp, NULL); // Set watchdog now
308  return 0;
309 }
310 
311 // This function will read characters until it has a full report, then
312 // put that report into analog fields and call the report methods on these.
313 // The time stored is that of the first character received as part of the
314 // report.
315 // Reports start with different characters, and the length of the report
316 // depends on what the first character of the report is. We switch based
317 // on the first character of the report to see how many more to expect and
318 // to see how to handle the report.
319 
321 {
322  int ret; // Return value from function call to be checked
323  char errmsg[256];
324 
325  //--------------------------------------------------------------------
326  // If we're SYNCing, then the next character we get should be the start
327  // of a report. If we recognize it, go into READing mode and tell how
328  // many characters we expect total. If we don't recognize it, then we
329  // must have misinterpreted a command or something; reset the Magellan
330  // and start over
331  //--------------------------------------------------------------------
332 
333  if (status == STATUS_SYNCING) {
334  // Try to get a character. If none, just return.
336  return 0;
337  }
338 
339  switch (_buffer[0]) {
340  case 0xa0:
341  _expected_chars = 15; status = STATUS_READING; break;
342  case 0xa1:
343  _expected_chars = 18; status = STATUS_READING; break;
344  case 0xa2:
345  _expected_chars = 30; status = STATUS_READING; break;
346  case 0xa3:
347  _expected_chars = 18; status = STATUS_READING; break;
348  case 0xa4:
349  _expected_chars = 4; status = STATUS_READING; break;
350  case 0xa5:
351  _expected_chars = 5; status = STATUS_READING; break;
352  case 0xa6:
353  _expected_chars = 26; status = STATUS_READING; break;
354 
355  default:
356  // Not a recognized command, keep looking
357  return 0;
358  }
359 
360 
361  // Got the first character of a report -- go into READING mode
362  // and record that we got one character at this time. The next
363  // bit of code will attempt to read the rest of the report.
364  // The time stored here is as close as possible to when the
365  // report was generated.
366  _bufcount = 1;
369 #ifdef VERBOSE
370  printf("... Got the 1st char\n");
371 #endif
372  }
373 
374  //--------------------------------------------------------------------
375  // Read as many bytes of this report as we can, storing them
376  // in the buffer. We keep track of how many have been read so far
377  // and only try to read the rest.
378  //--------------------------------------------------------------------
379 
382  if (ret == -1) {
383  VRPN_MSG_ERROR("Error reading");
385  return 0;
386  }
387  _bufcount += ret;
388 #ifdef VERBOSE
389  if (ret != 0) printf("... got %d characters (%d total)\n",ret, _bufcount);
390 #endif
391  if (_bufcount < _expected_chars) { // Not done -- go back for more
392  return 0;
393  }
394 
395  //--------------------------------------------------------------------
396  // We now have enough characters to make a full report. Check to make
397  // sure that its format matches what we expect. If it does, the next
398  // section will parse it. If it does not, we need to go back into
399  // synch mode and ignore this report. A well-formed report has the
400  // correct CRC as its last character, and has the camera ID matching
401  // what we expect as its second character.
402  //--------------------------------------------------------------------
403 
406  VRPN_MSG_WARNING("Bad CRC in report (ignoring this report)");
407  return 0;
408  }
409  _camera_id = _buffer[1];
410 
411 #ifdef VERBOSE
412  printf("got a complete report (%d of %d)!\n", _bufcount, _expected_chars);
413 #endif
414 
415  //--------------------------------------------------------------------
416  // Decode the report and store the values in it into the analog values
417  // if appropriate.
418  //--------------------------------------------------------------------
419 
420  switch ( _buffer[0] ) {
421  case 0xa0: // Pan, Tilt, Zoom, Focus
422  _numchannels = 4;
427  break;
428 
429  case 0xa1: // Pan, Tilt, Zoom, Focus, Height
430  _numchannels = 5;
436  break;
437 
438  case 0xa2: // Pan, Tilt, Zoom, Focus, Height, X, Y, Orientation
439  _numchannels = 8;
445  // Note that fraction is first, and is unsigned, in the buffer
448  // Note that fraction is first, and is unsigned, in the buffer
452  break;
453 
454  case 0xa4: // Response to our reset commands -- ignore them
455  break;
456 
457  // Note that case 0xa3 should never happen, since we don't send this.
458  // We'll let the "default" case complain about getting it.
459 
460  // Note that 0xa5 should not happen, since we don't send it.
461  // We'll let the "default" case complain about getting it.
462 
463 
464  case 0xa6: // Pan, Tilt, Zoom, Focus, Height, X, Y, Orientation
465  // The same as A2, except that fractional X,Y not included.
466  _numchannels = 8;
472  // Note that fraction is not in the buffer
474  // Note that fraction is not in the buffer
477  break;
478 
479  default:
480  sprintf(errmsg,"vrpn_Radamec_SPI: Unhandled command (0x%02x), resetting\n", _buffer[0]);
481  VRPN_MSG_ERROR(errmsg);
483  return 0;
484  }
485 
486  //--------------------------------------------------------------------
487  // Done with the decoding, send the reports and go back to syncing
488  //--------------------------------------------------------------------
489 
490  report(); // Report, rather than report_changes(), since it is an absolute device
492  _bufcount = 0;
493 
494  return 1;
495 }
496 
497 void vrpn_Radamec_SPI::report_changes(vrpn_uint32 class_of_service)
498 {
500 
501  vrpn_Analog::report_changes(class_of_service);
502 }
503 
504 void vrpn_Radamec_SPI::report(vrpn_uint32 class_of_service)
505 {
507 
508  vrpn_Analog::report(class_of_service);
509 }
510 
519 {
520  char errmsg[256];
521 
522  server_mainloop();
523 
524  switch(status) {
525  case STATUS_RESETTING:
526  reset();
527  break;
528 
529  case STATUS_SYNCING:
530  case STATUS_READING:
531  {
532  // It turns out to be important to get the report before checking
533  // to see if it has been too long since the last report. This is
534  // because there is the possibility that some other device running
535  // in the same server may have taken a long time on its last pass
536  // through mainloop(). Trackers that are resetting do this. When
537  // this happens, you can get an infinite loop -- where one tracker
538  // resets and causes the other to timeout, and then it returns the
539  // favor. By checking for the report here, we reset the timestamp
540  // if there is a report ready (ie, if THIS device is still operating).
541  while (get_report()) {}; // Keep getting reports so long as there are more
542 
543  struct timeval current_time;
544  vrpn_gettimeofday(&current_time, NULL);
545  if ( vrpn_TimevalDuration(current_time,timestamp) > MAX_TIME_INTERVAL) {
546  sprintf(errmsg,"Timeout... current_time=%ld:%ld, timestamp=%ld:%ld",
547  current_time.tv_sec, static_cast<long>(current_time.tv_usec),
548  timestamp.tv_sec, static_cast<long>(timestamp.tv_usec));
549  VRPN_MSG_ERROR(errmsg);
551  }
552  }
553  break;
554 
555  default:
556  VRPN_MSG_ERROR("Unknown mode (internal error)");
557  break;
558  }
559 }
vrpn_Radamec_SPI::int_to_focus
double int_to_focus(vrpn_uint32 val)
Definition: vrpn_Analog_Radamec_SPI.C:191
vrpn_Serial_Analog
Definition: vrpn_Analog.h:63
vrpn_Analog::status
int status
Definition: vrpn_Analog.h:43
vrpn_BaseClass.h
vrpn_TimevalDuration
unsigned long vrpn_TimevalDuration(struct timeval endT, struct timeval startT)
Return number of microseconds between startT and endT.
Definition: vrpn_Shared.C:129
vrpn_Radamec_SPI::timestamp
struct timeval timestamp
Definition: vrpn_Analog_Radamec_SPI.h:30
vrpn_Analog::channel
vrpn_float64 channel[vrpn_CHANNEL_MAX]
Definition: vrpn_Analog.h:38
STATUS_RESETTING
#define STATUS_RESETTING
Definition: vrpn_Analog_Radamec_SPI.C:32
vrpn_Radamec_SPI::int_to_orientation
double int_to_orientation(vrpn_uint32 val)
Convert from the 1/100 degree increments into degrees.
Definition: vrpn_Analog_Radamec_SPI.C:213
STATUS_SYNCING
#define STATUS_SYNCING
Definition: vrpn_Analog_Radamec_SPI.C:33
vrpn_Analog::report
virtual void report(vrpn_uint32 class_of_service=vrpn_CONNECTION_LOW_LATENCY, const struct timeval time=vrpn_ANALOG_NOW)
Send a report whether something has changed or not (for servers) Optionally, tell what time to stamp ...
Definition: vrpn_Analog.C:94
vrpn_Radamec_SPI::_buffer
unsigned char _buffer[512]
Definition: vrpn_Analog_Radamec_SPI.h:27
vrpn_Radamec_SPI::int_to_zoom
double int_to_zoom(vrpn_uint32 val)
Definition: vrpn_Analog_Radamec_SPI.C:185
vrpn_Analog::timestamp
struct timeval timestamp
Definition: vrpn_Analog.h:41
vrpn_Serial.h
vrpn_Serial: Pulls all the serial port routines into one file to make porting to new operating system...
vrpn_Analog_Radamec_SPI.h
vrpn_Radamec_SPI::_camera_id
int _camera_id
Definition: vrpn_Analog_Radamec_SPI.h:23
vrpn_Radamec_SPI::_status
int _status
Definition: vrpn_Analog_Radamec_SPI.h:22
vrpn_unbuffer
VRPN_API int vrpn_unbuffer(const char **buffer, timeval *t)
Utility routine for taking a struct timeval from a buffer that was sent as a message.
Definition: vrpn_Shared.C:312
vrpn_Radamec_SPI::send_command
int send_command(const unsigned char *cmd, int len)
Compute the CRC for the message, append it, and send message. Returns 0 on success,...
Definition: vrpn_Analog_Radamec_SPI.C:96
vrpn_flush_input_buffer
int vrpn_flush_input_buffer(int comm)
Throw out any characters within the input buffer.
Definition: vrpn_Serial.C:435
vrpn_SER_PARITY_ODD
Definition: vrpn_Serial.h:17
VRPN_MSG_WARNING
#define VRPN_MSG_WARNING(msg)
Definition: vrpn_MessageMacros.h:39
vrpn_Radamec_SPI::get_report
virtual int get_report(void)
Definition: vrpn_Analog_Radamec_SPI.C:320
vrpn_Shared.h
vrpn_Radamec_SPI::mainloop
virtual void mainloop()
Called once through each main loop iteration to handle updates.
Definition: vrpn_Analog_Radamec_SPI.C:518
vrpn_Radamec_SPI::reset
virtual int reset(void)
Definition: vrpn_Analog_Radamec_SPI.C:228
vrpn_Radamec_SPI::_bufcount
unsigned _bufcount
Definition: vrpn_Analog_Radamec_SPI.h:28
vrpn_Radamec_SPI::clear_values
virtual void clear_values(void)
Definition: vrpn_Analog_Radamec_SPI.C:57
vrpn_Radamec_SPI::report_changes
virtual void report_changes(vrpn_uint32 class_of_service=vrpn_CONNECTION_LOW_LATENCY)
send report iff changed
Definition: vrpn_Analog_Radamec_SPI.C:497
MAX_TIME_INTERVAL
#define MAX_TIME_INTERVAL
Definition: vrpn_Analog_Radamec_SPI.C:36
vrpn_Serial_Analog::serial_fd
int serial_fd
Definition: vrpn_Analog.h:72
vrpn_Connection
Generic connection class not specific to the transport mechanism.
Definition: vrpn_Connection.h:510
vrpn_Analog::num_channel
vrpn_int32 num_channel
Definition: vrpn_Analog.h:40
vrpn_gettimeofday
#define vrpn_gettimeofday
Definition: vrpn_Shared.h:89
vrpn_Radamec_SPI::int_to_X
double int_to_X(vrpn_uint32 mm, vrpn_uint32 frac)
Convert from the millimeter and fraction-of-millimeter values returned by the device into meters.
Definition: vrpn_Analog_Radamec_SPI.C:206
vrpn_Radamec_SPI::compute_crc
unsigned char compute_crc(const unsigned char *head, int len)
Compute the CRC for the message or report starting at head with length len.
Definition: vrpn_Analog_Radamec_SPI.C:75
vrpn_read_available_characters
int vrpn_read_available_characters(int comm, unsigned char *buffer, size_t bytes)
Definition: vrpn_Serial.C:512
vrpn_Radamec_SPI::convert_16bit_unsigned
vrpn_int32 convert_16bit_unsigned(const unsigned char *buf)
Convert a 16-bit unsigned value from a buffer into an integer.
Definition: vrpn_Analog_Radamec_SPI.C:145
vrpn_Radamec_SPI::report
virtual void report(vrpn_uint32 class_of_service=vrpn_CONNECTION_LOW_LATENCY)
send report whether or not changed
Definition: vrpn_Analog_Radamec_SPI.C:504
vrpn_Analog::last
vrpn_float64 last[vrpn_CHANNEL_MAX]
Definition: vrpn_Analog.h:39
vrpn_Radamec_SPI::_expected_chars
unsigned _expected_chars
Definition: vrpn_Analog_Radamec_SPI.h:26
vrpn_Radamec_SPI::int_to_tilt
double int_to_tilt(vrpn_uint32 val)
Definition: vrpn_Analog_Radamec_SPI.h:47
vrpn_Radamec_SPI::convert_24bit_unsigned
vrpn_uint32 convert_24bit_unsigned(const unsigned char *buf)
Convert a 24-bit value from a buffer into an unsigned integer value.
Definition: vrpn_Analog_Radamec_SPI.C:123
vrpn_MessageMacros.h
Header containing macros formerly duplicated in a lot of implementation files.
vrpn_Radamec_SPI::vrpn_Radamec_SPI
vrpn_Radamec_SPI(const char *name, vrpn_Connection *c, const char *port, int baud=38400)
Definition: vrpn_Analog_Radamec_SPI.C:41
vrpn_Radamec_SPI::_numchannels
int _numchannels
Definition: vrpn_Analog_Radamec_SPI.h:24
vrpn_write_characters
int vrpn_write_characters(int comm, const unsigned char *buffer, size_t bytes)
Write the buffer to the serial port.
Definition: vrpn_Serial.C:643
vrpn_Radamec_SPI::int_to_height
double int_to_height(vrpn_uint32 val)
Definition: vrpn_Analog_Radamec_SPI.C:197
vrpn_Radamec_SPI::int_to_pan
double int_to_pan(vrpn_uint32 val)
----------------— Conversion of encoder indices to values -------------— Pan and tilt axis have a...
Definition: vrpn_Analog_Radamec_SPI.C:180
vrpn_Analog::report_changes
virtual void report_changes(vrpn_uint32 class_of_service=vrpn_CONNECTION_LOW_LATENCY, const struct timeval time=vrpn_ANALOG_NOW)
Send a report only if something has changed (for servers) Optionally, tell what time to stamp the val...
Definition: vrpn_Analog.C:71
VRPN_MSG_ERROR
#define VRPN_MSG_ERROR(msg)
Definition: vrpn_MessageMacros.h:46
vrpn_BaseClassUnique::server_mainloop
void server_mainloop(void)
Handles functions that all servers should provide in their mainloop() (ping/pong, for example) Should...
Definition: vrpn_BaseClass.C:603
STATUS_READING
#define STATUS_READING
Definition: vrpn_Analog_Radamec_SPI.C:34