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