vrpn 07.35
Virtual Reality Peripheral Network
Loading...
Searching...
No Matches
vrpn_CerealBox.C
Go to the documentation of this file.
1// vrpn_CerealBox.C
2// This is a driver for the BG Systems CerealBox 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. You can find out more
6// at www.bgsystems.com. This code is written for version 3.07 of
7// the EEPROM code.
8// This code is based on their driver code, which was posted
9// on their web site as of the summer of 1999. This code reads the
10// characters as they arrive, rather than waiting "long enough" for
11// them to get here; this should allow the CerealBox to be used at
12// the same time as a tracking device without slowing the tracker
13// down.
14
15#include <stdio.h> // for fprintf, stderr, perror, etc
16#include <string.h> // for NULL, strlen, strncmp
17
18#include "vrpn_BaseClass.h" // for ::vrpn_TEXT_ERROR
19#include "vrpn_CerealBox.h"
20#include "vrpn_Serial.h"
21#include "vrpn_Shared.h" // for timeval, vrpn_gettimeofday
22
23#undef VERBOSE
24
25static const char offset = 0x21; // Offset added to some characters to avoid ctl chars
26static const double REV_PER_TICK = 1.0/4096; // How many revolutions per encoder tick?
27
28// Defines the modes in which the box can find itself.
29#define STATUS_RESETTING (-1) // Resetting the box
30#define STATUS_SYNCING (0) // Looking for the first character of report
31#define STATUS_READING (1) // Looking for the rest of the report
32
33#define MAX_TIME_INTERVAL (2000000) // max time between reports (usec)
34
35// This creates a vrpn_CerealBox and sets it to reset mode. It opens
36// the serial device using the code in the vrpn_Serial_Analog constructor.
37// The box seems to autodetect the baud rate when the "T" command is sent
38// to it.
40 const char * port, int baud,
41 const int numbuttons, const int numchannels, const int numencoders):
42 vrpn_Serial_Analog(name, c, port, baud),
43 vrpn_Button_Filter(name, c),
44 vrpn_Dial(name, c),
45 _numbuttons(numbuttons),
46 _numchannels(numchannels),
47 _numencoders(numencoders)
48{
49 // Verify the validity of the parameters
50 if (_numbuttons > 24) {
51 fprintf(stderr,"vrpn_CerealBox: Can only support 24 buttons, not %d\n",
53 _numbuttons = 24;
54 }
55 if (_numchannels > 8) {
56 fprintf(stderr,"vrpn_CerealBox: Can only support 8 analogs, not %d\n",
58 _numchannels = 8;
59 }
60 if (_numencoders > 8) {
61 fprintf(stderr,"vrpn_CerealBox: Can only support 8 encoders, not %d\n",
63 _numencoders = 8;
64 }
65
66 // Set the parameters in the parent classes
70
71 vrpn_gettimeofday(&timestamp, NULL); // Set watchdog now
72
73 // Set the status of the buttons, analogs and encoders to 0 to start
75 _bufcount = 0;
76
77 // Set the mode to reset
79}
80
82{
83 int i;
84
85 for (i = 0; i < _numbuttons; i++) {
87 }
88 for (i = 0; i < _numchannels; i++) {
90 }
91 for (i = 0; i < _numencoders; i++) {
92 vrpn_Dial::dials[i] = 0.0;
93 }
94}
95
96// This routine will reset the CerealBox, asking it to send the requested number
97// of analogs, buttons and encoders. It verifies that it can communicate with the
98// device and checks the version of the EPROMs in the device is 3.07.
99
101{
102 struct timeval timeout;
103 unsigned char inbuf[45];
104 const char *Cpy = "Copyright (c), BG Systems";
105 int major, minor, bug; // Version of the EEPROM
106 unsigned char reset_str[32]; // Reset string sent to box
107 int ret;
108
109 //-----------------------------------------------------------------------
110 // Set the values back to zero for all buttons, analogs and encoders
111 clear_values();
112
113 //-----------------------------------------------------------------------
114 // Check that the box exists and has the correct EEPROM version. The
115 // "T" command to the box should cause the 44-byte EEPROM string to be
116 // returned. This string defines the version and type of the box.
117 // Give it a reasonable amount of time to finish (2 seconds), then timeout
119 vrpn_write_characters(serial_fd, (const unsigned char *)"T", 1);
120 timeout.tv_sec = 2;
121 timeout.tv_usec = 0;
122 ret = vrpn_read_available_characters(serial_fd, inbuf, 44, &timeout);
123 inbuf[44] = 0; // Make sure string is NULL-terminated
124 if (ret < 0) {
125 perror("vrpn_CerealBox: Error reading from box\n");
126 return -1;
127 }
128 if (ret == 0) {
129 fprintf(stderr,"vrpn_CerealBox: No response from box\n");
130 return -1;
131 }
132 if (ret != 44) {
133 fprintf(stderr,"vrpn_CerealBox: Got %d of 44 expected characters\n",ret);
134 return -1;
135 }
136
137 // Parse the copyright string for the version and other information
138 // Code here is similar to check_rev() function in the BG example code.
139 if (strncmp((char *)inbuf, Cpy, strlen(Cpy))) {
140 fprintf(stderr,"vrpn_CerealBox: Copyright string mismatch: %s\n",inbuf);
141 return -1;
142 }
143 major = inbuf[38] - '0';
144 minor = inbuf[40] - '0';
145 bug = inbuf[41] - '0';
146 if ( (3 != major) || (0 != minor) || (7 != bug) ) {
147 fprintf(stderr, "vrpn_CerealBox: Bad EEPROM version (want 3.07, got %d.%d%d)\n",
148 major, minor, bug);
149 return -1;
150 }
151 printf("vrpn_CerealBox: Box of type %c found\n",inbuf[42]);
152
153 //-----------------------------------------------------------------------
154 // Compute the proper string to initialize the device based on how many
155 // of each type of input/output that is selected. This follows init_cereal()
156 // in BG example code.
157 { int i;
158 char ana1_4 = 0; // Bits 1-4 do analog channels 1-4
159 char ana5_8 = 0; // Bits 1-4 do analog channels 5-8
160 char dig1_3 = 0; // Bits 1-3 enable groups of 8 inputs
161 char enc1_4 = 0; // Bits 1-4 enable encoders 1-4
162 char enc5_8 = 0; // Bits 1-4 enable encoders 5-8
163
164 // Figure out which analog channels to use and set them
165 for (i = 0; i < 4; i++) {
166 if (i < _numchannels) {
167 ana1_4 |= (1<<i);
168 }
169 }
170 for (i = 0; i < 4; i++) {
171 if (i+4 < _numchannels) {
172 ana5_8 |= (1<<i);
173 }
174 }
175
176 // Figure out which banks of digital inputs to use and set them
177 for (i = 0; i < _numbuttons; i++) {
178 dig1_3 |= (1 << (i/8));
179 }
180
181 // Figure out which encoder channels to use and set them
182 for (i = 0; i < 4; i++) {
183 if (i < _numencoders) {
184 enc1_4 |= (1<<i);
185 }
186 }
187 for (i = 0; i < 4; i++) {
188 if (i+4 < _numencoders) {
189 enc5_8 |= (1<<i);
190 }
191 }
192
193 reset_str[0] = 'c';
194 reset_str[1] = (unsigned char)(ana1_4 + offset); // Hope we don't need to set baud rate
195 reset_str[2] = (unsigned char)((ana5_8 | (dig1_3 << 4)) + offset);
196 reset_str[3] = (unsigned char)(0 + offset);
197 reset_str[4] = (unsigned char)(0 + offset);
198 reset_str[5] = (unsigned char)(enc1_4 + offset);
199 reset_str[6] = (unsigned char)(enc1_4 + offset); // Set encoders 1-4 for incremental
200 reset_str[7] = (unsigned char)(enc5_8 + offset);
201 reset_str[8] = (unsigned char)(enc5_8 + offset); // Set encoders 5-8 for incremental
202 reset_str[9] = '\n';
203 reset_str[10] = 0;
204 }
205
206 // Send the string and then wait for an acknowledgement from the box.
207 vrpn_write_characters(serial_fd, reset_str, 10);
208 timeout.tv_sec = 2;
209 timeout.tv_usec = 0;
210 ret = vrpn_read_available_characters(serial_fd, inbuf, 2, &timeout);
211 if (ret < 0) {
212 perror("vrpn_CerealBox: Error reading ack from box\n");
213 return -1;
214 }
215 if (ret == 0) {
216 fprintf(stderr,"vrpn_CerealBox: No ack from box\n");
217 return -1;
218 }
219 if (ret != 2) {
220 fprintf(stderr,"vrpn_CerealBox: Got %d of 2 expected ack characters\n",ret);
221 return -1;
222 }
223 if (inbuf[0] != 'a') {
224 fprintf(stderr,"vrpn_CerealBox: Bad ack: wanted 'a', got '%c'\n",inbuf[0]);
225 return -1;
226 }
227
228 //-----------------------------------------------------------------------
229 // Ask the box to send a report, and go into SYNCING mode to get it.
230 vrpn_write_characters(serial_fd, (const unsigned char *)"pE", 2);
232 printf("CerealBox reset complete.\n");
233
234 //-----------------------------------------------------------------------
235 // Figure out how many characters to expect in each report from the device
236 // There is a 'p' to start and '\n' to finish, two bytes for each analog
237 // value, 4 bytes for each encoder. Buttons are enabled in banks of 8,
238 // but each bank of 8 is returned in 2 bytes (4 bits each).
240 ((_numbuttons+7) / 8) * 2;
241
242 vrpn_gettimeofday(&timestamp, NULL); // Set watchdog now
243 return 0;
244}
245
246// This function will read characters until it has a full report, then
247// put that report into the time, analog, button and encoder fields and call
248// the report methods on these. The time stored is that of
249// the first character received as part of the report. Reports start with
250// the header "p" and end with "\n", and is of the length _expected_chars.
251// If we get a report that is not valid, we assume that we have lost a
252// character or something and re-synchronize by waiting
253// until the start-of-report character ('p') comes around again.
254// The routine that calls this one
255// makes sure we get a full reading often enough (ie, it is responsible
256// for doing the watchdog timing to make sure the box hasn't simply
257// stopped sending characters).
258// Returns 1 if got a complete report, 0 otherwise.
259
261{
262 int ret; // Return value from function call to be checked
263 int i; // Loop counter
264 int nextchar = 1; // Index of the next character to read
265
266 //--------------------------------------------------------------------
267 // The reports are each _expected_chars characters long, and each start with an
268 // ASCII 'p' character. If we're synching, read a byte at a time until we
269 // find a 'p' character.
270 //--------------------------------------------------------------------
271
272 if (status == STATUS_SYNCING) {
273 // Try to get a character. If none, just return.
275 return 0;
276 }
277
278 // If it is not a 'p', we don't want it but we
279 // need to look at the next one, so just return and stay
280 // in Syncing mode so that we will try again next time through.
281 if ( _buffer[0] != 'p') {
282 fprintf(stderr,"vrpn_CerealBox: Syncing (looking for 'p', "
283 "got '%c')\n", _buffer[0]);
284 return 0;
285 }
286
287 // Got the first character of a report -- go into READING mode
288 // and record that we got one character at this time. The next
289 // bit of code will attempt to read the rest of the report.
290 // The time stored here is as close as possible to when the
291 // report was generated.
292 _bufcount = 1;
295#ifdef VERBOSE
296 printf("... Got the 1st char\n");
297#endif
298 }
299
300 //--------------------------------------------------------------------
301 // Read as many bytes of this report as we can, storing them
302 // in the buffer. We keep track of how many have been read so far
303 // and only try to read the rest. The routine that calls this one
304 // makes sure we get a full reading often enough (ie, it is responsible
305 // for doing the watchdog timing to make sure the device hasn't simply
306 // stopped sending characters).
307 //--------------------------------------------------------------------
308
311 if (ret == -1) {
312 send_text_message("vrpn_CerealBox: Error reading", timestamp, vrpn_TEXT_ERROR);
314 return 0;
315 }
316 _bufcount += ret;
317#ifdef VERBOSE
318 if (ret != 0) printf("... got %d characters (%d total)\n",ret, _bufcount);
319#endif
320 if (_bufcount < _expected_chars) { // Not done -- go back for more
321 return 0;
322 }
323
324 //--------------------------------------------------------------------
325 // We now have enough characters to make a full report. Check to make
326 // sure that its format matches what we expect. If it does, the next
327 // section will parse it. If it does not, we need to go back into
328 // synch mode and ignore this report. A well-formed report has the
329 // first character 'p', and the last character is '\n'.
330 //--------------------------------------------------------------------
331
332 if (_buffer[0] != 'p') {
334 fprintf(stderr,"vrpn_CerealBox: Not 'p' in record\n");
335 return 0;
336 }
337 if (_buffer[_expected_chars-1] != '\n') {
339 fprintf(stderr,"vrpn_CerealBox: No carriage return in record\n");
340 return 0;
341 }
342
343#ifdef VERBOSE
344 printf("got a complete report (%d of %d)!\n", _bufcount, _expected_chars);
345#endif
346
347 //--------------------------------------------------------------------
348 // Ask the device to send us another report. Ideally, this would be
349 // sent earlier so that we can overlap with the previous report. However,
350 // when we ask after the first character we start losing parts of the
351 // reports when we've turned on a lot of inputs. So, we do it here
352 // after the report has come in.
353 //--------------------------------------------------------------------
354
355 vrpn_write_characters(serial_fd, (const unsigned char *)"pE", 2);
356
357 //--------------------------------------------------------------------
358 // Decode the report and store the values in it into the parent classes
359 // (analog, button and encoder). This code is modelled on the routines
360 // convert_serial() and unpack_encoders() in the BG systems code.
361 //--------------------------------------------------------------------
362
363 { // Digital code. There appear to be 4 bits (four buttons) stored
364 // in each byte, in the low-order 4 bits after the offset of 0x21
365 // has been removed from each byte. They seem to come in highest-
366 // buttons first, with the highest within each bank in the leftmost
367 // bit. This assumes we are not using MP for digital inputs.
368
369 int i;
370 int numbuttonchars;
371
372 // Read two characters for each eight buttons that are on, from the
373 // highest bank down.
374 numbuttonchars = ((_numbuttons+7) / 8) * 2;
375 for (i = numbuttonchars-1; i >= 0; i--) {
376 // Find the four bits for these buttons by subtracting
377 // the offset to get them into the low-order 4 bits
378 char bits = (char)(_buffer[nextchar++] - offset);
379
380 // Set the buttons for each bit
381 buttons[ i*4 + 3 ] = ( (bits & 0x08) != 0);
382 buttons[ i*4 + 2 ] = ( (bits & 0x04) != 0);
383 buttons[ i*4 + 1 ] = ( (bits & 0x02) != 0);
384 buttons[ i*4 + 0 ] = ( (bits & 0x01) != 0);
385 }
386 }
387
388 {// Analog code. Looks like there are two characters for each
389 // analog value; this conversion code grabbed right from the
390 // BG code. They seem to come in lowest-numbered first.
391
392 int intval, i;
393 double realval;
394 for (i = 0; i < _numchannels; i++) {
395 intval = (0x3f & (_buffer[nextchar++]-offset)) << 6;
396 intval |= (0x3f & (_buffer[nextchar++]-offset));
397 realval = -1.0 + (2.0 * intval/4095.0);
398 channel[i] = realval;
399 }
400 }
401
402 { // Encoders. They come packed as 24-bit values with 6 bits in
403 // each byte (offset by 0x21). They seem to come least-significant
404 // part first. This decoding is valid only for incremental
405 // encoders. Remember to convert the encoder values into fractions
406 // of a revolution.
407
408 for (i = 0; i < _numencoders; i++) {
409 int enc0, enc1, enc2, enc3;
410 long increment;
411
412 enc0 = (_buffer[nextchar++]-offset) & 0x3f;
413 increment = enc0;
414 enc1 = (_buffer[nextchar++]-offset) & 0x3f;
415 increment |= enc1 << 6;
416 enc2 = (_buffer[nextchar++]-offset) & 0x3f;
417 increment |= enc2 << 12;
418 enc3 = (_buffer[nextchar++]-offset) & 0x3f;
419 increment |= enc3 << 18;
420 if ( increment & 0x800000 ) {
421 dials[i] = (int)(increment - 16777216) * REV_PER_TICK;
422 } else {
423 dials[i] = (int)(increment) * REV_PER_TICK;
424 }
425 }
426 }
427
428 //--------------------------------------------------------------------
429 // Done with the decoding, send the reports and go back to syncing
430 //--------------------------------------------------------------------
431
434 _bufcount = 0;
435 return 1;
436}
437
438void vrpn_CerealBox::report_changes(vrpn_uint32 class_of_service)
439{
443
444 vrpn_Analog::report_changes(class_of_service);
447}
448
449void vrpn_CerealBox::report(vrpn_uint32 class_of_service)
450{
454
455 vrpn_Analog::report(class_of_service);
458}
459
460// This routine is called each time through the server's main loop. It will
461// take a course of action depending on the current status of the cerealbox,
462// either trying to reset it or trying to get a reading from it.
464{
465 // Call the generic server mainloop, since we are a server
467
468 switch(status) {
469 case STATUS_RESETTING:
470 reset();
471 break;
472
473 case STATUS_SYNCING:
474 case STATUS_READING:
475 {
476 // It turns out to be important to get the report before checking
477 // to see if it has been too long since the last report. This is
478 // because there is the possibility that some other device running
479 // in the same server may have taken a long time on its last pass
480 // through mainloop(). Trackers that are resetting do this. When
481 // this happens, you can get an infinite loop -- where one tracker
482 // resets and causes the other to timeout, and then it returns the
483 // favor. By checking for the report here, we reset the timestamp
484 // if there is a report ready (ie, if THIS device is still operating).
485 while (get_report()) {}; // Keep getting reports as long as they come
486 struct timeval current_time;
487 vrpn_gettimeofday(&current_time, NULL);
488 if ( vrpn_TimevalDuration(current_time,timestamp) > MAX_TIME_INTERVAL) {
489 fprintf(stderr,"CerealBox failed to read... current_time=%ld:%ld, timestamp=%ld:%ld\n",
490 current_time.tv_sec, static_cast<long>(current_time.tv_usec),
491 timestamp.tv_sec, static_cast<long>(timestamp.tv_usec));
492 send_text_message("Too long since last report, resetting", current_time, vrpn_TEXT_ERROR);
494 }
495 }
496 break;
497
498 default:
499 fprintf(stderr,"vrpn_CerealBox: Unknown mode (internal error)\n");
500 break;
501 }
502}
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)
Definition: vrpn_Button.C:383
vrpn_int32 num_buttons
Definition: vrpn_Button.h:48
struct timeval timestamp
Definition: vrpn_Button.h:49
virtual void report_changes(void)
Definition: vrpn_Button.C:423
unsigned char lastbuttons[vrpn_BUTTON_MAX_BUTTONS]
Definition: vrpn_Button.h:46
unsigned char buttons[vrpn_BUTTON_MAX_BUTTONS]
Definition: vrpn_Button.h:45
vrpn_CerealBox(const char *name, vrpn_Connection *c, const char *port, int baud, const int numbuttons, const int numchannels, const int numencoders)
virtual int reset(void)
unsigned char _buffer[512]
virtual void clear_values(void)
unsigned _expected_chars
unsigned _bufcount
virtual void mainloop()
Called once through each main loop iteration to handle updates. Remote object mainloop() should call ...
virtual int get_report(void)
struct timeval timestamp
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
#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
int vrpn_write_characters(int comm, const unsigned char *buffer, size_t bytes)
Write the buffer to the serial port.
Definition: vrpn_Serial.C:651
int vrpn_flush_input_buffer(int comm)
Throw out any characters within the input buffer.
Definition: vrpn_Serial.C:438
int vrpn_read_available_characters(int comm, unsigned char *buffer, size_t bytes)
Definition: vrpn_Serial.C:515
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.
Definition: vrpn_Shared.C:138
#define vrpn_gettimeofday
Definition: vrpn_Shared.h:99