vrpn 07.35
Virtual Reality Peripheral Network
Loading...
Searching...
No Matches
vrpn_ImmersionBox.C
Go to the documentation of this file.
1// vrpn_ImmersionBox.C
2// This is a driver for an Immersion Corporation Immersion Box controller.
3// This box is a serial-line device that allows the user to connect
4// analog inputs, digital inputs, digital outputs, and digital
5// encoders and read from them over RS-232.
6//
7// This code is based on the ImmersionBox code previously written as part
8// of the vrpn library
9
10#include <math.h> // for floor
11#include <stdio.h> // for fprintf, stderr, printf
12#include <string.h> // for NULL, strcpy
13
14#include "vrpn_BaseClass.h" // for ::vrpn_TEXT_ERROR
15#include "vrpn_ImmersionBox.h"
16#include "vrpn_Serial.h"
17#include "vrpn_Shared.h" // for timeval, vrpn_gettimeofday
18
19#undef VERBOSE
20
21// low-level stuff
22//static const unsigned char CMD_BASIC = static_cast<unsigned char>(0xC0); // mask for command
23//static const unsigned char CMD_HOMEREF = static_cast<unsigned char>(0xC1);
24//static const unsigned char CMD_HOMEPOS = static_cast<unsigned char>(0xC2);
25//static const unsigned char CMD_SETHOME = static_cast<unsigned char>(0xC3);
26//static const unsigned char CMD_BAUDSET = static_cast<unsigned char>(0xC4);
27//static const unsigned char CMD_ENDSESS = static_cast<unsigned char>(0xC5);
28//static const unsigned char CMD_GETMAXS = static_cast<unsigned char>(0xC6);
29//static const unsigned char CMD_SETPARM = static_cast<unsigned char>(0xC7);
30static const unsigned char CMD_GETNAME = static_cast<unsigned char>(0xC8);
31static const unsigned char CMD_GETPRID = static_cast<unsigned char>(0xC9);
32static const unsigned char CMD_GETMODL = static_cast<unsigned char>(0xCA);
33static const unsigned char CMD_GETSERN = static_cast<unsigned char>(0xCB);
34static const unsigned char CMD_GETCOMM = static_cast<unsigned char>(0xCC);
35static const unsigned char CMD_GETPERF = static_cast<unsigned char>(0xCD);
36static const unsigned char CMD_GETVERS = static_cast<unsigned char>(0xCE);
37//static const unsigned char CMD_GETMOTN = static_cast<unsigned char>(0xCF);
38//static const unsigned char CMD_SETHREF = static_cast<unsigned char>(0xD0);
39//static const unsigned char CMD_FACREST = static_cast<unsigned char>(0xD1);
40//static const unsigned char CMD_INSMARK = static_cast<unsigned char>(0xD2);
41
42//static const double PAUSE_RESET = .015;
43//static const double PAUSE_END = .015;
44//static const double PAUSE_RESTORE = 2.0;
45//static const double PAUSE_BYTE = .015;
46
47// Defines the modes in which the box can find itself.
48#define STATUS_RESETTING (-1) // Resetting the box
49#define STATUS_SYNCING (0) // Looking for the first character of report
50#define STATUS_READING (1) // Looking for the rest of the report
51
52/* Strings for establishing connection */
53char S_INITIALIZE[5] = "IMMC";
54char S_START[6] = "BEGIN";
55char S_END[4] = "END";
56
57#define MAX_TIME_INTERVAL (2000000) // max time between reports (usec)
58
59static void pause (double delay) {
60 if (delay < 0)
61 delay = 0;
62 unsigned long interval = (long) floor(1000000.0 * delay);
63
64 struct timeval start, now;
65 vrpn_gettimeofday (&start, NULL);
66
67 do {
68 vrpn_gettimeofday (&now, NULL);
69 } while (vrpn_TimevalDuration(now, start) < interval);
70
71}
72
73// This creates a vrpn_ImmersionBox and sets it to reset mode. It opens
74// the serial device using the code in the vrpn_Serial_Analog constructor.
75// The box will autodetect the baud rate when the "IMMC" command is sent
76// to it.
77
80 const char * port,
81 int baud,
82 const int numbuttons,
83 const int numchannels,
84 const int numencoders):
85 vrpn_Serial_Analog(name, c, port, baud),
86 vrpn_Button_Filter(name, c),
87 vrpn_Dial(name, c),
88 _numbuttons(numbuttons),
89 _numchannels(numchannels),
90 _numencoders(numencoders)
91{
92
93
94 // Verify the validity of the parameters
96 fprintf(stderr,"vrpn_ImmersionBox: Can only support %d buttons, not %d\n", MAX_IBUTTONS, _numbuttons);
98 }
100 fprintf(stderr,"vrpn_ImmersionBox: Can only support %d analog channels, not %d\n", MAX_ICHANNELS, _numchannels);
102 }
104 fprintf(stderr,"vrpn_ImmersionBox: Can only support %d encoders, not %d\n", MAX_IENCODERS, _numencoders);
106 }
107
108 // explicitly clear the identification variables
109 iname[0] = 0;
110 comment[0] = 0;
111 serial[0] = 0;
112 id[0] = 0;
113 model[0] = 0;
114 vers[0] = 0;
115 parmf[0] = 0;
116
117 // Set the parameters in the parent classes
121
122 vrpn_gettimeofday(&timestamp, NULL); // Set watchdog now
123
124 // Set the status of the buttons, analogs and encoders to 0 to start
125 clear_values();
126 dataRecordLength = 0;
127
128 // Set the mode to reset
130}
131
133{
134 int i;
135
136 for (i = 0; i < _numbuttons; i++) {
138 }
139 for (i = 0; i < _numchannels; i++) {
141 }
142 for (i = 0; i < _numencoders; i++) {
143 vrpn_Dial::dials[i] = 0.0;
144 }
145}
146
147// This routine will reset the ImmersionBox, asking it to send the requested number
148// of analogs, buttons and encoders.
149// It verifies that it can communicate with the device and checks the version of
150// the device.
151
153{
154
155 //-----------------------------------------------------------------------
156 // Set the values back to zero for all buttons, analogs and encoders
157 clear_values();
158
159 //-----------------------------------------------------------------------
160 // sending an end at this time will force the ibox into the reset mode, if it
161 // was not already. if the ibox is in the power up mode, nothing will happen because
162 // it 'should' be waiting to sync up the baudrate
163
164 // try to synchronize for 2 seconds
165
166 if (syncBaudrate (10.0)) {
167 printf("vrpn_ImmersionBox found\n");
168 } else {
169 return -1;
170 }
171
172 if (0 == sendIboxCommand((char) CMD_GETNAME, (char *) &iname, .1)) {
173 fprintf(stderr,"problems with ibox command CMD_GETNAME\n");
174 return -1;
175 }
176 if (0 == sendIboxCommand((char) CMD_GETPRID, (char *) &id, .1)) {
177 fprintf(stderr,"problems with ibox command CMD_GETPRID\n");
178 return -1;
179 }
180 if (0 == sendIboxCommand((char) CMD_GETMODL, (char *) &model, .1)){
181 fprintf(stderr,"problems with ibox command CMD_GETMODL\n");
182 return -1;
183 }
184 if (0 == sendIboxCommand((char) CMD_GETSERN, (char *) &serial, .1)){
185 fprintf(stderr,"problems with ibox command CMD_GETSERN\n");
186 return -1;
187 }
188 if (0 == sendIboxCommand((char) CMD_GETCOMM, (char *) &comment, .1)){
189 fprintf(stderr,"problems with ibox command CMD_GETCOMM\n");
190 return -1;
191 }
192 if (0 == sendIboxCommand((char) CMD_GETPERF, (char *) &parmf, .1)){
193 fprintf(stderr,"problems with ibox command CMD_GETPERF\n");
194 return -1;
195 }
196 if (0 == sendIboxCommand((char) CMD_GETVERS, (char *) &vers, .1)){
197 fprintf(stderr,"problems with ibox command CMD_GETVERS\n");
198 return -1;
199 }
200
201#ifdef VERBOSE
202 printf("%s\n%s\n%s\n%s\n%s\n%s\n", iname, id, serial, comment, parmf, vers);
203#endif
204
205 //-----------------------------------------------------------------------
206 // Compute the proper string to initialize the device based on how many
207 // of each type of input/output that is selected.
208
209 setupCommand (0, _numchannels, _numencoders);
210
211 unsigned char command[26] = "";
212 command[0] = 0xcf;
213 command[1] = 0x0;
214 command[2] = (unsigned char) 10; // milliseconds between reports
215 command[3] = commandByte;
216
217 for (int i = 4; i < 25;i++)
218 command[i] = 0x0;
219
220 //-----------------------------------------------------------------------
221
222 int result = vrpn_write_characters(serial_fd, (const unsigned char *) command, 25);
223
224 // Ask the box to send a report, ensure echo received
225 if (result < 25) {
226 fprintf(stderr,"vrpnImmersionBox::reset: could not write command\n");
227 return -1;
228 }
229
230 pause (.1);
231
232 // look for command echo
233 result = vrpn_read_available_characters(serial_fd, (unsigned char *) command, 1);
234
235 if (result <= 0 || command[0] != 0xcf) {
236 fprintf(stderr,"vrpnImmersionBox::reset: no command echo\n");
237 return -1;
238 }
239
240 // flush the input buffer
242
243 printf("ImmersionBox reset complete.\n");
244
246 vrpn_gettimeofday(&timestamp, NULL); // Set watchdog now
247 return 0;
248}
249
250// This function will read characters until it has a full report, then
251// put that report into the time, analog, button and encoder fields and call
252// the report methods on these. The time stored is that of
253// the first character received as part of the report.
254//
255// Reports start with the header packetHeader followed by dataRecordLength bytes
256//
257// If we get a report that is not valid, we assume that we have lost a
258// character or something and re-synchronize by flushing the buffer
259//
260// The routine that calls this one
261// makes sure we get a full reading often enough (ie, it is responsible
262// for doing the watchdog timing to make sure the box hasn't simply
263// stopped sending characters).
264// Returns 1 if got a full report, 0 otherwise.
265
267{
268 unsigned char responseString[MAX_IBOX_STRING];
269 int i;
270 unsigned int buttonBits = 0;
271
272 struct timeval timeout;
273 timeout.tv_sec = 0;
274 timeout.tv_usec = 100000;
275
277
278 // process as long as we can get characters
280 // if not a record start, skip it
281 if (buffer[0] != dataPacketHeader) {
282 continue;
283 }
284 // we got a good one... we're reading now
286 break;
287 }
288
289 // we broke out.. if we're not reading, then we have nothing to do
290 if (STATUS_READING != status) {
291 return 0;
292 }
293
294
295 // we're reading now, get the report
296
297 // get the expected number of data record bytes
299 (unsigned char *) responseString,
300 dataRecordLength,
301 &timeout );
302
303 if (result < dataRecordLength) {
305 return 0;
306 }
307
308 // parse the report here
309 buttonBits = responseString[0];
310
311 for (i = 0; i < _numbuttons; i++) {
313 vrpn_Button::buttons[i] = static_cast<unsigned char>(buttonBits & (1 << i));
314 }
315
316#if VERBOSE
318 cerr << "left button pressed " << endl;
319#endif
320
321 // if we processed timer bits we would do so here
322
323 // here is where we decode the analog stuff
324 for (i = 0; i < _numchannels; i++) {
326 }
327
328 // here is where we convert the angle encoders
329 for (i = 0; i < _numencoders; i++) {
330 vrpn_Dial::dials[i] = 0.0;
331 }
332
334 vrpn_gettimeofday(&timestamp, NULL); // Set watchdog now
335
336 return 1;
337}
338
349
360
361// This routine is called each time through the server's main loop. It will
362// take a course of action depending on the current status of the cerealbox,
363// either trying to reset it or trying to get a reading from it.
365{
367
368 switch(status) {
369 case STATUS_RESETTING:
370 reset();
371 break;
372
373 case STATUS_SYNCING:
374 case STATUS_READING:
375 {
376 // It turns out to be important to get the report before checking
377 // to see if it has been too long since the last report. This is
378 // because there is the possibility that some other device running
379 // in the same server may have taken a long time on its last pass
380 // through mainloop(). Trackers that are resetting do this. When
381 // this happens, you can get an infinite loop -- where one tracker
382 // resets and causes the other to timeout, and then it returns the
383 // favor. By checking for the report here, we reset the timestamp
384 // if there is a report ready (ie, if THIS device is still operating).
385 while (get_report()) {}; // Get multiple reports if available
386 struct timeval current_time;
387 vrpn_gettimeofday(&current_time, NULL);
388 if ( vrpn_TimevalDuration(current_time,timestamp) > MAX_TIME_INTERVAL) {
389 fprintf(stderr,"Tracker failed to read... current_time=%ld:%ld, timestamp=%ld:%ld\n",
390 current_time.tv_sec, static_cast<long>(current_time.tv_usec),
391 timestamp.tv_sec, static_cast<long>(timestamp.tv_usec));
392 send_text_message("Too long since last report, resetting", current_time, vrpn_TEXT_ERROR);
394 }
395 }
396 break;
397
398 default:
399 fprintf(stderr,"vrpn_ImmersionBox: Unknown mode (internal error)\n");
400 break;
401 }
402}
403
404// utility routine (optionally) to send commands to the ibox,
405// (optionally) to return results,
406// (optionally) delaying between sending the command and receiving the results
407
408int vrpn_ImmersionBox::sendIboxCommand (char cmd, char * returnString, double delay)
409{
410 struct timeval timeout;
411 timeout.tv_sec = 0;
412 timeout.tv_usec = 15000;
413 char command[2];
414 char responseString [MAX_IBOX_STRING+1];
415 int result;
416
417 if (cmd) {
418 command[0] = cmd;
419 result = vrpn_write_characters(serial_fd, (const unsigned char *) command, 1);
420 if (delay > 0)
421 pause (delay);
422
423 if (NULL == returnString)
424 // if we are not anticipating a return, go back now
425 return result;
426 }
427
428 // read the response to the command
429 result = vrpn_read_available_characters(serial_fd, (unsigned char *) responseString, MAX_IBOX_STRING, &timeout );
430
431 if (result <= 0)
432 return 0;
433
434 // since we're looking for a response, we need to ensure the command was properly
435 // echoed back... To begin with, bit 7 must be set
436 if (!(responseString[0] & 0x80))
437 return 0;
438
439 // the bits, excepting bit 7 must match
440 if ((responseString[0] & 0x7f) != (cmd & 0x7f))
441 return 0;
442
443 // copy the remainder of the response into the response
444 strcpy (returnString, &responseString[1]);
445
446 return 1;
447}
448
449
450// sync the baud rate on the ibox.
451// seconds determines how long the process is permitted to continue
452int vrpn_ImmersionBox::syncBaudrate (double seconds) {
453
454 struct timeval miniDelay;
455 miniDelay.tv_sec = 0;
456 miniDelay.tv_usec = 50000;
457
458 unsigned long maxDelay = 1000000L * (long) seconds;
459 struct timeval start_time;
460 vrpn_gettimeofday(&start_time, NULL);
461
462 int loggedOn = 0;
463 unsigned char responseString[8];
464 const unsigned char * matchString = (unsigned char *) S_INITIALIZE ; // IMMC
465 int index, numRead;
466
467 if (serial_fd < 0)
468 return 0;
470 vrpn_write_characters(serial_fd, (const unsigned char *)"E", 1);
471 pause (0.01);
472
473 while (!loggedOn) {
474 struct timeval current_time;
475 vrpn_gettimeofday(&current_time, NULL);
476 if (vrpn_TimevalDuration(current_time, start_time) > maxDelay ) {
477 // if we've timed out, go back unhappy
478 fprintf(stderr,"vrpn_ImmersionBox::syncBaudrate timeout expired: %lf secs \n", seconds);
479 break; // out of while loop
480 }
481
482 // send "IMMC"
483 if (4 != vrpn_write_characters(serial_fd, matchString, 4)) {
484 fprintf(stderr,"vrpn_ImmersionBox::syncBaudrate could not write to serial port\n");
485 break; // out of while loop
486 }
487
488 pause (0.015);
489
490 // wait for 4 characters
491 numRead = vrpn_read_available_characters(serial_fd, responseString, 4, &miniDelay);
492
493 if (numRead <= 0)
494 continue;
495
496 // get 4 characters, hopefully "IMMC"
497 for (index = 0; index < 4; index++) {
498 // get a character, check for failure
499 if (responseString[index] != matchString[index])
500 break;
501 }
502
503 // if we got all four, we're done
504 if (4 == index)
505 loggedOn = 1;
506 }
507
508 if (!loggedOn)
509 return 0;
510
511 // now begin the session && ensure that its an ibox we're talking to
512 matchString = (const unsigned char *) "IBOX";
513 vrpn_write_characters(serial_fd, (const unsigned char *)"BEGIN", 5);
514 numRead = vrpn_read_available_characters(serial_fd, responseString, 4, &miniDelay);
515
516 if (numRead <= 0)
517 return 0;
518
519 // check 4 characters, hopefully "IBOX"
520 for (index = 0; index < 4; index++) {
521 // get a character, check for failure
522 if (responseString[index] != matchString[index])
523 return 0;
524 }
526 return 1;
527}
vrpn_float64 last[vrpn_CHANNEL_MAX]
Definition vrpn_Analog.h:39
vrpn_float64 channel[vrpn_CHANNEL_MAX]
Definition vrpn_Analog.h:38
struct timeval timestamp
Definition vrpn_Analog.h:41
vrpn_int32 num_channel
Definition vrpn_Analog.h:40
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
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
void server_mainloop(void)
Handles functions that all servers should provide in their mainloop() (ping/pong, for example) Should...
int send_text_message(const char *msg, struct timeval timestamp, vrpn_TEXT_SEVERITY type=vrpn_TEXT_NORMAL, vrpn_uint32 level=0)
Sends a NULL-terminated text message from the device d_sender_id.
All button servers should derive from this class, which provides the ability to turn any of the butto...
Definition vrpn_Button.h:66
virtual void report_changes(void)
vrpn_int32 num_buttons
Definition vrpn_Button.h:48
struct timeval timestamp
Definition vrpn_Button.h:49
virtual void report_changes(void)
unsigned char lastbuttons[vrpn_BUTTON_MAX_BUTTONS]
Definition vrpn_Button.h:46
unsigned char buttons[vrpn_BUTTON_MAX_BUTTONS]
Definition vrpn_Button.h:45
Generic connection class not specific to the transport mechanism.
virtual void report_changes(void)
Definition vrpn_Dial.C:54
struct timeval timestamp
Definition vrpn_Dial.h:28
vrpn_float64 dials[vrpn_DIAL_MAX]
Definition vrpn_Dial.h:26
virtual void report(void)
Definition vrpn_Dial.C:82
vrpn_int32 num_dials
Definition vrpn_Dial.h:27
virtual int reset(void)
virtual int get_report(void)
virtual void clear_values(void)
virtual void mainloop(void)
Called once through each main loop iteration to handle updates. Remote object mainloop() should call ...
vrpn_ImmersionBox(const char *name, vrpn_Connection *c, const char *port, int baud, const int numbuttons, const int numchannels, const int numencoders)
struct timeval timestamp
unsigned char buffer[1024]
Definition vrpn_Analog.h:75
#define STATUS_SYNCING
#define MAX_TIME_INTERVAL
#define STATUS_READING
#define STATUS_RESETTING
All types of client/server/peer objects in VRPN should be derived from the vrpn_BaseClass type descri...
@ vrpn_TEXT_ERROR
char S_START[6]
char S_INITIALIZE[5]
char S_END[4]
#define MAX_IENCODERS
#define MAX_ICHANNELS
#define MAX_IBUTTONS
#define MAX_IBOX_STRING
int vrpn_write_characters(int comm, const unsigned char *buffer, size_t bytes)
Write the buffer to the serial port.
int vrpn_flush_input_buffer(int comm)
Throw out any characters within the input buffer.
int vrpn_read_available_characters(int comm, unsigned char *buffer, size_t bytes)
vrpn_Serial: Pulls all the serial port routines into one file to make porting to new operating system...
unsigned long vrpn_TimevalDuration(struct timeval endT, struct timeval startT)
Return number of microseconds between startT and endT.
#define vrpn_gettimeofday
Definition vrpn_Shared.h:99