vrpn 07.35
Virtual Reality Peripheral Network
Loading...
Searching...
No Matches
vrpn_OmegaTemperature.C
Go to the documentation of this file.
1// vrpn_OmegaTemperature.C
2// This is a driver for the OmegaTemperature temperature controller.
3// It was written in April 2014 by Russ Taylor.
4
5// INFO about how the device communicates, taken from the user manual:
6// This information comes from the Bulletin E-90-OCN publication, which is
7// the manual for the Omega Series CN7200, CN7600, CN7800, CN7500 microprocessor
8// based temperature process control. It was developed for a CN7800.
9// It communicates over RS-485 using the MODBUS ASCII/RTU communications
10// protocol. The driver is written for the ASCII protocol.
11// A Windows application to test communication with the device can be found
12// at http://www.omega.com/Software/CN7-a_r.html
13
14// A Modbus ASCII frame is as follows (Wikipedia) (big-endian):
15// Colon ':' character.
16// 2-character station address
17// 2-character function code
18// (0x03 read register, 0x02 read data bits, 0x06 write register, 0x05 write bit)
19// n-character data + length
20// 2-character checksum
21// 2-character CR/LF (0x0d 0x0a)
22
23// Modbus application protocol specification:
24// http://www.modbus.com/docs/Modbus_Application_Protocol_V1_1b.pdf
25// Modbus over serial line document:
26// http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
27
28// libmodbus: libmodbus.org has a Modbus library for various operating
29// systems. It has ominous statements in the release notes about Windows
30// support being broken even in the latest version (3.1.1 on 2013-10-06).
31// The latest stable version is 3.0.5; it does not say that it has broken
32// support on Windows. It seems to support the serial RTU protocol.
33// However, the configure script reports that VC++ support has not been
34// tested in a long time; it can be configured and built under Cygwin.
35
36/* XXX The code has not been changed from the Biosciences code yet,
37 only the names have been changed. */
38
39/*
40Using a standard DB-9 cable (female-female connectors on both ends with
41straight-through connections from each pin)
42connect the controller (middle DB-9 connector) to a serial port of your computer.
43Set the serial port at 115,200 speed, 8 bits, 1 stop bit,
44NONE parity, and Hardware flow control.
45The following is the list of text commands supported.
46NOTE: Each command should follow by \r <CR> code:
47(The following notes LIE: There is no space before the . or before the C,
48 and sometimes the C is an E. Also, the order is incorrect. The actual
49 order is stage 1, bath 1, external 1, stage 2, bath 2, external 2.)
50T1<CR> returns temperature readings from STAGE1 sensor: 37 .1 C
51T2<CR> returns temperature readings from BATH1 sensor: 36 .9 C
52T5<CR> returns SET temperature: 37 .0 C
53T3<CR> returns temperature readings from STAGE2 sensor: 37 .1 C
54T4<CR> returns temperature readings from BATH2 sensor: 36 .9 C
55T6<CR> returns SET temperature: 37 .0 C
56CTn<CR> returns readings from n (n=1 - STAGE1, 2 - BATH1, 3 - STAGE2, 4 - BATH2) sensor: 37 .1 C
57ON<CR> turns temperature control ON
58OFF<CR> turns temperature control OFF
59S1 037 0<CR> sets reference temperature for channel I (NOTE: all four digits should be sent to the controller)
60S2 037 0<CR> sets reference temperature for channel II
61*/
62
63#include <stddef.h> // for size_t
64#include <stdio.h> // for sprintf, fprintf, stderr, etc
65#include <string.h> // for strlen, NULL
66
67#include "vrpn_BaseClass.h" // for ::vrpn_TEXT_ERROR, etc
69#include "vrpn_Serial.h" // for vrpn_write_characters, etc
70#include "vrpn_Shared.h" // for vrpn_unbuffer, timeval, etc
71#include "vrpn_MessageMacros.h" // for VRPN_MSG_INFO, VRPN_MSG_WARNING, VRPN_MSG_ERROR
72
74
75#if defined(VRPN_USE_MODBUS) && defined(VRPN_USE_WINSOCK2)
76
77#undef VERBOSE
78
79// Defines the modes in which the device can find itself.
80#define STATUS_RESETTING (-1) // Resetting the device
81#define STATUS_SYNCING (0) // Looking for the first character of report
82#define STATUS_READING (1) // Looking for the rest of the report
83
84#define TIMEOUT_TIME_INTERVAL (2000000L) // max time between reports (usec)
85
86
87// This creates a vrpn_OmegaTemperature. It opens
88// the serial device using the code in the vrpn_Serial_Analog constructor.
89// It uses hardware flow control.
90
91vrpn_OmegaTemperature::vrpn_OmegaTemperature (const char * name, vrpn_Connection * c,
92 const char * port, float temp1, float temp2, bool control_on):
93 vrpn_Serial_Analog(name, c, port, 115200, 8, vrpn_SER_PARITY_NONE, true),
94 vrpn_Analog_Output(name, c),
95 vrpn_Button_Filter(name, c)
96{
97 // XXX Make this configurable?
98 int baud = 38400;
99 char parity = 'n'; // XXX What should this be?
100 int stop_bits = 1;
101 d_modbus = modbus_new_rtu(port, baud, parity, 8, stop_bits);
102 // XXX No code below has been changed yet.
103
104 num_channel = 6;
105 o_num_channel = 3;
106 num_buttons = 1;
107 buttons[0] = control_on;
108
109 // Fill in the arguments to send to the device at reset time.
110 o_channel[0] = temp1;
111 o_channel[1] = temp2;
112 o_channel[2] = control_on;
113
114 // Set the mode to reset
115 status = STATUS_RESETTING;
116
117 // Register to receive the message to request changes and to receive connection
118 // messages.
119 if (d_connection != NULL) {
120 if (register_autodeleted_handler(request_m_id, handle_request_message,
121 this, d_sender_id)) {
122 fprintf(stderr,"vrpn_OmegaTemperature: can't register handler\n");
123 d_connection = NULL;
124 }
125 if (register_autodeleted_handler(request_channels_m_id, handle_request_channels_message,
126 this, d_sender_id)) {
127 fprintf(stderr,"vrpn_OmegaTemperature: can't register handler\n");
128 d_connection = NULL;
129 }
130 if (register_autodeleted_handler(d_ping_message_id, handle_connect_message,
131 this, d_sender_id)) {
132 fprintf(stderr,"vrpn_OmegaTemperature: can't register handler\n");
133 d_connection = NULL;
134 }
135 } else {
136 fprintf(stderr,"vrpn_OmegaTemperature: Can't get connection!\n");
137 }
138
139}
140
141// Command format described in document:
142// S1 037 0<CR> sets reference temperature for channel 1
143// (NOTE: all four digits should be sent to the controller)
144// Actual command format:
145// S1 0370<CR> Sets reference temperature for channel 1 to 37.0 deg C
146// S2 0421<CR> Sets reference temperature for channel 2 to 42.1 deg C
147
148bool vrpn_OmegaTemperature::set_reference_temperature(unsigned channel, float value)
149{
150 char command[128];
151
152 // Fill in the command with the zero-padded integer output for
153 // above the decimal and then a single value for the first point
154 // past the decimal.
155 int whole = static_cast<int>(value);
156 int dec = static_cast<int>(value*10) - whole*10;
157 sprintf(command, "S%d %03d%d\r", channel+1, whole,dec);
158
159 // Send the command to the serial port
160 return (vrpn_write_characters(serial_fd, (unsigned char *)(command), strlen(command)) == strlen(command));
161}
162
163// Command format:
164// ON<CR> sets control on
165// OFF<CR> sets control off
166
167bool vrpn_OmegaTemperature::set_control_status(bool on)
168{
169 char command[128];
170
171 if (on) {
172 sprintf(command, "ON\r");
173 } else {
174 sprintf(command, "OFF\r");
175 }
176
177 // Send the command to the serial port
178 return (vrpn_write_characters(serial_fd, (unsigned char *)(command), strlen(command)) == strlen(command));
179}
180
181// Command format:
182// T1<CR> returns temperature readings from STAGE1 sensor: 37.1C
183// T2<CR> returns temperature readings from BATH1 sensor: 36.9C
184// T5<CR> returns SET temperature: 37.0C
185// T3<CR> returns temperature readings from STAGE2 sensor: 37.1C
186// T4<CR> returns temperature readings from BATH2 sensor: 36.9C
187// T6<CR> returns SET temperature: 37.0C
188// NOTE: Sometimes the C is an E when there is no reading.
189
190bool vrpn_OmegaTemperature::request_temperature(unsigned channel)
191{
192 char command[128];
193
194 sprintf(command, "T%d\r", channel+1);
195#ifdef VERBOSE
196 printf("Sending command: %s", command);
197#endif
198
199 // Send the command to the serial port
200 return (vrpn_write_characters(serial_fd, (unsigned char *)(command), strlen(command)) == strlen(command));
201}
202
203// Convert the four bytes that have been read into a signed integer value.
204// The format (no quotes) looks like: "- 37.1C\r" or " 000.00E\r".
205// I don't think that the - means a minus sign, and it has a space
206// between it and the number.
207// Returns -1000 if there is an error.
208float vrpn_OmegaTemperature::convert_bytes_to_reading(const char *buf)
209{
210 float val;
211 char c;
212
213 // Skip any leading minus sign.
214 if (*buf == '-') { buf++; }
215
216 // Read a fractional number.
217 if (sscanf(buf, "%f%c", &val, &c) != 2) {
218 return -1000;
219 }
220
221 // See if we get and E or C after the number,
222 // or (since E can be part of a floating-point
223 // number) if we get \r.
224 if ( (c != 'E') && (c != 'C') && (c != '\r') ) {
225 return -1000;
226 }
227
228 return val;
229}
230
231
232int vrpn_OmegaTemperature::reset(void)
233{
234 //-----------------------------------------------------------------------
235 // Sleep less thana second and then drain the input buffer to make sure we start
236 // with a fresh slate.
237 vrpn_SleepMsecs(200);
238 vrpn_flush_input_buffer(serial_fd);
239
240 //-----------------------------------------------------------------------
241 // Set the temperatures for channel 1 and 2 and then set the temperature
242 // control to be on or off depending on what we've been asked to do.
243 if (!set_reference_temperature(0, static_cast<float>(o_channel[0]))) {
244 fprintf(stderr,"vrpn_OmegaTemperature::reset(): Cannot send set ref temp 0, trying again\n");
245 return -1;
246 }
247 if (!set_reference_temperature(1, static_cast<float>(o_channel[1]))) {
248 fprintf(stderr,"vrpn_OmegaTemperature::reset(): Cannot send set ref temp 1, trying again\n");
249 return -1;
250 }
251 if (!set_control_status(o_channel[0] != 0)) {
252 fprintf(stderr,"vrpn_OmegaTemperature::reset(): Cannot send set control status, trying again\n");
253 return -1;
254 }
255
256 //-----------------------------------------------------------------------
257 // Send the command to request input from the first channel, and set up
258 // the finite-state machine so we know which thing to request next.
259 d_next_channel_to_read = 0;
260 if (!request_temperature(d_next_channel_to_read)) {
261 fprintf(stderr,"vrpn_OmegaTemperature::reset(): Cannot request temperature, trying again\n");
262 return -1;
263 }
264
265 // We're now waiting for any responses from devices
266 status = STATUS_SYNCING;
267 VRPN_MSG_WARNING("reset complete (this is normal)");
268 vrpn_gettimeofday(&timestamp, NULL); // Set watchdog now
269 return 0;
270}
271
272// This function will read characters until it has a full report, then
273// put that report into analog fields and call the report methods on these.
274// The time stored is that of the first character received as part of the
275// report.
276
277int vrpn_OmegaTemperature::get_report(void)
278{
279 int ret; // Return value from function call to be checked
280
281 //--------------------------------------------------------------------
282 // If we're SYNCing, then the next character we get should be the start
283 // of a report. If we recognize it, go into READing mode and tell how
284 // many characters we expect total. If we don't recognize it, then we
285 // must have misinterpreted a command or something; reset
286 // and start over
287 //--------------------------------------------------------------------
288
289 if (status == STATUS_SYNCING) {
290 // Try to get a character. If none, just return.
291 if (vrpn_read_available_characters(serial_fd, (unsigned char *)(d_buffer), 1) != 1) {
292 return 0;
293 }
294
295 // Got the first character of a report -- go into READING mode
296 // and record that we got one character at this time. Clear the
297 // rest of the buffer to 0's so that we won't be looking at old
298 // data when we parse.
299 // The time stored here is as close as possible to when the
300 // report was generated.
301 d_bufcount = 1;
302 vrpn_gettimeofday(&timestamp, NULL);
303 status = STATUS_READING;
304 size_t i;
305 for (i = 1; i < sizeof(d_buffer); i++) {
306 d_buffer[i] = 0;
307 }
308#ifdef VERBOSE
309 printf("... Got the 1st char\n");
310#endif
311 }
312
313 //--------------------------------------------------------------------
314 // Read as many bytes of this report as we can, storing them
315 // in the buffer.
316 //--------------------------------------------------------------------
317
318 while ( 1 == (ret = vrpn_read_available_characters(serial_fd, (unsigned char *)(&d_buffer[d_bufcount]), 1))) {
319 d_bufcount++;
320 }
321 if (ret == -1) {
322 VRPN_MSG_ERROR("Error reading");
323 status = STATUS_RESETTING;
324 return 0;
325 }
326#ifdef VERBOSE
327 if (ret != 0) printf("... got %d total characters\n", d_bufcount);
328#endif
329 if (d_buffer[d_bufcount-1] != '\r') { // Not done -- go back for more
330 return 0;
331 }
332
333 //--------------------------------------------------------------------
334 // We now have enough characters to make a full report. Check to make
335 // sure that its format matches what we expect. If it does, the next
336 // section will parse it.
337 // Store the report into the appropriate analog channel.
338 //--------------------------------------------------------------------
339
340#ifdef VERBOSE
341 printf(" Complete report: \n%s\n",d_buffer);
342#endif
343 float value = convert_bytes_to_reading(d_buffer);
344 if (value == -1000) {
345 char msg[256];
346 sprintf(msg,"Invalid report, channel %d, resetting", d_next_channel_to_read);
347 VRPN_MSG_ERROR(msg);
348 status = STATUS_RESETTING;
349 }
350 channel[d_next_channel_to_read] = value;
351
352#ifdef VERBOSE
353 printf("got a complete report (%d chars)!\n", d_bufcount);
354#endif
355
356 //--------------------------------------------------------------------
357 // Request a reading from the next channe.
358 //--------------------------------------------------------------------
359
360 d_next_channel_to_read = (d_next_channel_to_read + 1) % 6;
361 if (!request_temperature(d_next_channel_to_read)) {
362 char msg[256];
363 sprintf(msg,"Can't request reading, channel %d, resetting", d_next_channel_to_read);
364 VRPN_MSG_ERROR(msg);
365 status = STATUS_RESETTING;
366 }
367
368 //--------------------------------------------------------------------
369 // Done with the decoding, send the reports and go back to syncing
370 //--------------------------------------------------------------------
371
372 report_changes();
373 status = STATUS_SYNCING;
374 d_bufcount = 0;
375
376 return 1;
377}
378
379bool vrpn_OmegaTemperature::set_specified_channel(unsigned channel, vrpn_float64 value)
380{
381 // XXX Check return status of the set commands?
382 switch (channel) {
383 case 0: // Reference temperature for channels 1 and 2
384 case 1: // Reference temperature for channels 1 and 2
385 set_reference_temperature(channel, static_cast<float>(value));
386 o_channel[channel] = value;
387 break;
388 case 2: // Turn on temperature control if this is nonzero.
389 o_channel[2] = value;
390 buttons[0] = ( value != 0 );
391 set_control_status( value != 0);
392 break;
393 default:
394 return false;
395 }
396 return true;
397}
398
399int vrpn_OmegaTemperature::handle_request_message(void *userdata, vrpn_HANDLERPARAM p)
400{
401 const char *bufptr = p.buffer;
402 vrpn_int32 chan_num;
403 vrpn_int32 pad;
404 vrpn_float64 value;
405 vrpn_OmegaTemperature *me = (vrpn_OmegaTemperature *)userdata;
406
407 // Read the parameters from the buffer
408 vrpn_unbuffer(&bufptr, &chan_num);
409 vrpn_unbuffer(&bufptr, &pad);
410 vrpn_unbuffer(&bufptr, &value);
411
412 // Set the appropriate value, if the channel number is in the
413 // range of the ones we have.
414 if ( (chan_num < 0) || (chan_num >= me->o_num_channel) ) {
415 char msg[1024];
416 sprintf(msg,"vrpn_OmegaTemperature::handle_request_message(): Index out of bounds (%d of %d), value %lg\n",
417 chan_num, me->num_channel, value);
418 me->send_text_message(msg, me->timestamp, vrpn_TEXT_ERROR);
419 return 0;
420 }
421
422 me->set_specified_channel(chan_num, value);
423 return 0;
424}
425
426int vrpn_OmegaTemperature::handle_request_channels_message(void* userdata, vrpn_HANDLERPARAM p)
427{
428 int i;
429 const char* bufptr = p.buffer;
430 vrpn_int32 num;
431 vrpn_int32 pad;
432 vrpn_OmegaTemperature* me = (vrpn_OmegaTemperature *)userdata;
433
434 // Read the values from the buffer
435 vrpn_unbuffer(&bufptr, &num);
436 vrpn_unbuffer(&bufptr, &pad);
437 if (num > me->o_num_channel) {
438 char msg[1024];
439 sprintf(msg,"vrpn_OmegaTemperature::handle_request_channels_message(): Index out of bounds (%d of %d), clipping\n",
440 num, me->o_num_channel);
441 me->send_text_message(msg, me->timestamp, vrpn_TEXT_ERROR);
442 num = me->o_num_channel;
443 }
444 for (i = 0; i < num; i++) {
445 vrpn_unbuffer(&bufptr, &(me->o_channel[i]));
446 me->set_specified_channel(i, me->o_channel[i]);
447 }
448
449 return 0;
450}
451
454int vrpn_OmegaTemperature::handle_connect_message(void *userdata, vrpn_HANDLERPARAM)
455{
456 vrpn_OmegaTemperature *me = (vrpn_OmegaTemperature *)userdata;
457
458 me->report(vrpn_CONNECTION_RELIABLE);
459 return 0;
460}
461
462void vrpn_OmegaTemperature::report_changes(vrpn_uint32 class_of_service)
463{
464 vrpn_Analog::timestamp = timestamp;
465 vrpn_Analog::report_changes(class_of_service);
467}
468
469void vrpn_OmegaTemperature::report(vrpn_uint32 class_of_service)
470{
471 vrpn_Analog::timestamp = timestamp;
472 vrpn_Analog::report(class_of_service);
474}
475
483void vrpn_OmegaTemperature::mainloop()
484{
485 char errmsg[256];
486
487 server_mainloop();
488
489 switch(status) {
490 case STATUS_RESETTING:
491 reset();
492 break;
493
494 case STATUS_SYNCING:
495 case STATUS_READING:
496 {
497 // It turns out to be important to get the report before checking
498 // to see if it has been too long since the last report. This is
499 // because there is the possibility that some other device running
500 // in the same server may have taken a long time on its last pass
501 // through mainloop(). Trackers that are resetting do this. When
502 // this happens, you can get an infinite loop -- where one tracker
503 // resets and causes the other to timeout, and then it returns the
504 // favor. By checking for the report here, we reset the timestamp
505 // if there is a report ready (ie, if THIS device is still operating).
506 while (get_report()) {}; // Keep getting reports so long as there are more
507
508 struct timeval current_time;
509 vrpn_gettimeofday(&current_time, NULL);
510 if ( vrpn_TimevalDuration(current_time,timestamp) > TIMEOUT_TIME_INTERVAL) {
511 sprintf(errmsg,"Timeout... current_time=%ld:%ld, timestamp=%ld:%ld",
512 current_time.tv_sec, static_cast<long>(current_time.tv_usec),
513 timestamp.tv_sec, static_cast<long>(timestamp.tv_usec));
514 VRPN_MSG_ERROR(errmsg);
515 status = STATUS_RESETTING;
516 }
517 }
518 break;
519
520 default:
521 VRPN_MSG_ERROR("Unknown mode (internal error)");
522 break;
523 }
524}
525
526#endif
struct timeval timestamp
Definition: vrpn_Analog.h:41
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
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:423
Generic connection class not specific to the transport mechanism.
This structure is what is passed to a vrpn_Connection message callback.
const char * buffer
#define STATUS_SYNCING
#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
#define TIMEOUT_TIME_INTERVAL
#define VRPN_SUPPRESS_EMPTY_OBJECT_WARNING()
const vrpn_uint32 vrpn_CONNECTION_RELIABLE
Classes of service for messages, specify multiple by ORing them together Priority of satisfying these...
Header containing macros formerly duplicated in a lot of implementation files.
#define VRPN_MSG_ERROR(msg)
#define VRPN_MSG_WARNING(msg)
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...
@ vrpn_SER_PARITY_NONE
Definition: vrpn_Serial.h:16
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:321
unsigned long vrpn_TimevalDuration(struct timeval endT, struct timeval startT)
Return number of microseconds between startT and endT.
Definition: vrpn_Shared.C:138
void vrpn_SleepMsecs(double dMilliSecs)
Definition: vrpn_Shared.C:166
#define vrpn_gettimeofday
Definition: vrpn_Shared.h:99