API
 
Loading...
Searching...
No Matches
za_serial.c
Go to the documentation of this file.
1/*
2 * \file za_serial.c
3 * \author Eric Dand
4 * \version 1.0
5 * \date 28 November 2014
6 * \copyright Apache Software License Version 2.0
7 *
8 * Implementation file for ASCII portion of the Zaber Serial API in C.
9 * See za_serial.h for documentation.
10 */
11#include <string.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include "za_serial.h"
15
16static int za_verbose = 1;
17
18void za_set_verbose(int value)
19{
20 za_verbose = value;
21}
22
23
24#include <fcntl.h>
25#include <sys/stat.h>
26#include <sys/types.h>
27#include <termios.h>
28#include <unistd.h>
29
30#if defined(NDEBUG)
31#define PRINT_ERROR(M)
32#define PRINTF_ERROR(M, ...)
33#define PRINT_SYSCALL_ERROR(M)
34#else
35#define PRINT_ERROR(M) do { if (za_verbose) { fprintf(stderr, "(%s: %d) " M\
36 "\n", __FILE__, __LINE__); } } while(0)
37#define PRINTF_ERROR(M, ...) do { if (za_verbose) { fprintf(stderr,\
38 "(%s: %d) " M "\n", __FILE__, __LINE__, __VA_ARGS__); } } while(0)
39#include <errno.h>
40#define PRINT_SYSCALL_ERROR(M) do { if (za_verbose) {\
41 fprintf(stderr, "(%s: %d) [ERROR] " M " failed: %s.\n",\
42 __FILE__, __LINE__, strerror(errno)); } } while(0)
43#endif
44/* A little sugar for checking return values from system calls.
45 * I would have liked to use GNU/GCC's "statement expressions" so that one
46 * do something like "z_port port = SYSCALL(open([parameters]))", but they're
47 * a GNU extension, and therefore unfriendly to non-GCC compilers.
48 * Workaround to avoid dependence on statement expressions for SYSCALL macro:
49 * use SYSCALL on result of assignment instead. */
50#define SYSCALL(F) do { if ((F) < 0) { PRINT_SYSCALL_ERROR(#F);\
51 return Z_ERROR_SYSTEM_ERROR; } } while(0)
52
53int za_connect(z_port *port, const char *port_name)
54{
55 struct termios tio, orig_tio;
56
57 if (port == NULL || port_name == NULL)
58 {
59 PRINT_ERROR("[ERROR] port and port_name cannot be NULL.");
61 }
62
63 /* blocking read/write */
64 SYSCALL(*port = open(port_name, O_RDWR | O_NOCTTY));
65 SYSCALL(tcgetattr(*port, &orig_tio));
66 memcpy(&tio, &orig_tio, sizeof(struct termios)); /* copy padding too */
67
68 /* cfmakeraw() without cfmakeraw() for cygwin compatibility */
69 tio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
70 | IGNCR | ICRNL | IXON);
71 tio.c_oflag &= ~OPOST;
72 tio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
73 tio.c_cflag &= ~(CSIZE | PARENB);
74 /* end cfmakeraw() */
75 tio.c_cflag = CS8|CREAD|CLOCAL;
76
77 /* READ_TIMEOUT is defined in za_serial.h */
78 if (READ_TIMEOUT % 100 != 0)
79 {
80 tio.c_cc[VTIME] = READ_TIMEOUT / 100 + 1; /* Round up */
81 }
82 else
83 {
84 tio.c_cc[VTIME] = READ_TIMEOUT / 100;
85 }
86 tio.c_cc[VMIN] = 0;
87
88 SYSCALL(cfsetospeed(&tio, B115200) & cfsetispeed(&tio, B115200));
89
90 while(memcmp(&orig_tio, &tio, sizeof(struct termios)) != 0)
91 { /* memcmp is only OK here because we used memcpy above */
92 SYSCALL(tcsetattr(*port, TCSAFLUSH, &tio));
93 SYSCALL(tcgetattr(*port, &orig_tio));
94 }
95
96 return Z_SUCCESS;
97}
98
99int za_disconnect(z_port port)
100{
101 SYSCALL(close(port));
102 return Z_SUCCESS;
103}
104
105/* a subtle feature of za_send() that is implied but not explicitly documented:
106 * if you pass a pointer to a 0-length string (ie. just a \0) for command,
107 * za_send() will send the minimal command of "/\n" automagically, as it will
108 * assume you are sending a command without content, and that you have
109 * forgotten the leading '/' and trailing '\n'. Mention of this is probably
110 * best left out of the official docs since it's a hacky way to use za_send().
111 */
112int za_send( z_port port,
113 const char *command,
114 size_t sMaxSz
115 )
116{
117 int nlast,
118 length,
119 written = 0;
120
121 if (command == NULL)
122 {
123 PRINT_ERROR("[ERROR] command cannot be NULL.");
125 }
126
127 if (command[0] != '/')
128 { /* send a '/' if they forgot it */
129 SYSCALL(nlast = write(port, "/", 1));
130 written += nlast;
131 }
132 length = strnlen(command, sMaxSz);
133 SYSCALL(nlast = write(port, command, length));
134 written += nlast;
135 if (nlast != length)
136 { /* write can return a short write: test for it */
137 PRINTF_ERROR("[ERROR] write did not write entire message: "
138 "could only write %d bytes of %d.", nlast, length);
140 }
141
142 if (length == 0 || command[length-1] != '\n')
143 { /* send a newline if they forgot it */
144 SYSCALL(nlast = write(port, "\n", 1));
145 written += nlast;
146 }
147
148 return written;
149}
150
151int za_receive(z_port port, char *destination, int length)
152{
153 int nread = 0,
154 nlast;
155 char c;
156
157 for (;;)
158 {
159 SYSCALL(nlast = (int) read(port, &c, 1));
160
161 if (nlast == 0) /* timed out */
162 {
163 //PRINTF_ERROR("[INFO] Read timed out after reading %d "
164 // "bytes.", nread);
165 return Z_ERROR_TIMEOUT;
166 }
167
168 if (destination != NULL)
169 {
170 destination[nread] = c;
171 }
172 nread += nlast;
173
174 if (nread == length)
175 {
176 PRINTF_ERROR("[ERROR] Read destination buffer not large "
177 "enough. Recommended size: 256B. Your size: %dB.",
178 length);
180 }
181
182 if (c == '\n')
183 {
184 nread -= 2; /* prepare to cut off the "\r\n" */
185 if (nread < 0)
186 {
187 PRINT_ERROR("[ERROR] Reply too short. It is likely that "
188 "only a partial reply was read.");
190 }
191 if (destination != NULL)
192 {
193 destination[nread] = '\0'; /* chomp the "\r\n" */
194 }
195 return nread;
196 }
197 }
198}
199
200int za_setbaud(z_port port, int baud)
201{
202 struct termios tio;
203 speed_t tbaud;
204 switch (baud)
205 {
206 case 9600:
207 tbaud = B9600;
208 break;
209 case 19200:
210 tbaud = B19200;
211 break;
212 case 38400:
213 tbaud = B38400;
214 break;
215 case 57600:
216 tbaud = B57600;
217 break;
218 case 115200:
219 tbaud = B115200;
220 break;
221 default:
222 PRINTF_ERROR("[ERROR] Invalid baud rate. Valid rates are "
223 "9600, 19200, 38400, 57600, and 115200 (default).\n"
224 "Your rate: %d.", baud);
226 }
227
228 SYSCALL(tcgetattr(port, &tio));
229 SYSCALL(cfsetospeed(&tio, tbaud) & cfsetispeed(&tio, tbaud));
230 SYSCALL(tcsetattr(port, TCSAFLUSH, &tio));
231
232 return Z_SUCCESS;
233}
234
235int za_drain(z_port port)
236{
237 struct termios tio;
238 int old_timeout;
239 char c;
240
241 /* set timeout to 0.1s */
242 SYSCALL(tcgetattr(port, &tio));
243 old_timeout = tio.c_cc[VTIME];
244 tio.c_cc[VTIME] = 1;
245 SYSCALL(tcsetattr(port, TCSANOW, &tio));
246
247 /* flush and read whatever else comes in */
248 SYSCALL(tcflush(port, TCIFLUSH));
249 while(read(port, &c, 1) > 0);
250
251 /* set timeout back to what it was */
252 tio.c_cc[VTIME] = old_timeout;
253 SYSCALL(tcsetattr(port, TCSANOW, &tio));
254
255 return Z_SUCCESS;
256}
257
258
259/* A helper for za_decode. Copies from source to destination, until delim or
260 * a '\0' is found, then null-terminates destination.
261 *
262 * Returns the number of bytes copied, including the added null-terminator. */
263static size_t copy_until_delim(char *destination, const char *source,
264 const char delim, size_t destination_length)
265{
266 size_t i;
267
268 for(i = 0; source[i] != delim && source[i] != '\0'
269 && i < destination_length - 1; i++)
270 {
271 destination[i] = source[i];
272 }
273
274 destination[i] = '\0'; /* null-terminate instead of copying delim */
275
276 return i + 1;
277}
278
279static int decode_reply( struct za_reply *destination,
280 const char *reply,
281 size_t sMaxSz
282 )
283{
284 char buffer[8]; /* needs to be at least 5B: set to 8 because
285 it will be padded to 8 from 5 anyway. */
286 size_t offset,
287 length;
288
289 if (strnlen(reply, sMaxSz) < 18)
290 {
291 PRINTF_ERROR("[ERROR] Reply could not be decoded: shorter than expected. "
292 "It is likely that only a partial reply was read.\n"
293 "Reply value: %s", reply);
295 }
296
297 destination->message_type = '@';
298
299 /* device address: 2 digits, 00-99
300 *
301 * The device address is part of the same "token" as the message type,
302 * so we call strtol on the same token, skipping the first char.
303 *
304 * We don't check the length here for future-proofing:
305 * if we add support for more device addresses (ie. more digits),
306 * this should handle it gracefully. */
307 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
308 destination->device_address = (int) strtol(buffer + 1, NULL, 10);
309 reply += offset;
310
311 /* axis number: 1 digit, 0-9
312 *
313 * The axis number may be 2 digits (or more) in the future, but it's
314 * unlikely to go over 2^31 - 1 any time soon, so we convert to
315 * standard int and use that. */
316 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
317 destination->axis_number = (int) strtol(buffer, NULL, 10);
318 reply += offset;
319
320 /* reply flags: 2 letters
321 *
322 * Only replies have reply flags. Value is either "OK" or "RJ". */
323 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
324 if (offset > sizeof(destination->reply_flags))
325 {
326 PRINTF_ERROR("[ERROR] Reply could not be decoded: reply flags too "
327 "long. Maximum length: %zu. Your length: %zu. Reply flags "
328 "value: %s\n.", sizeof(destination->reply_flags), offset,
329 buffer);
331 }
332 strcpy(destination->reply_flags, buffer);
333 reply += offset;
334
335 /* device status: 4 letters
336 *
337 * Replies and alerts have a "status" field. Value is either "IDLE" or
338 * "BUSY", depending on what the device is doing at the time. */
339 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
340 if (offset > sizeof(destination->device_status))
341 {
342 PRINTF_ERROR("[ERROR] Reply could not be decoded: device status too "
343 "long. Expected length: %zu. Your length: %zu. Device status "
344 "value: %s\n.", sizeof(destination->device_status), offset,
345 buffer);
347 }
348 strcpy(destination->device_status, buffer);
349 reply += offset;
350
351 /* warning flags: 2 letters
352 *
353 * Replies and alerts have warning flags. Warning flags are typically
354 * "--". All other possible warning flag values should be two
355 * consecutive capital letters. */
356 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
357 if (offset > sizeof(destination->warning_flags))
358 {
359 PRINTF_ERROR("[ERROR] Reply could not be decoded: warning flags too "
360 "long. Expected length: %zu. Your length: %zu. Warning flags "
361 "value: %s\n.", sizeof(destination->warning_flags), offset,
362 buffer);
364 }
365 strcpy(destination->warning_flags, buffer);
366 reply += offset;
367
368 /* data: 1+ characters, probably less than 128 characters
369 *
370 * Replies and info have data. This can be anything, including spaces,
371 * numbers, and human-readable text. */
372 length = strnlen(reply, sMaxSz); /* get length of remaining data */
373 if (length > sizeof(destination->response_data))
374 {
375 PRINTF_ERROR("[ERROR] Reply could not be decoded: response data too "
376 "long. Maximum length: %zu. Your length: %zu. Data: %s.\n",
377 sizeof(destination->response_data), length, reply);
379 }
380 strcpy(destination->response_data, reply);
381
382 return Z_SUCCESS;
383
384}
385
386static int decode_alert( struct za_reply *destination,
387 const char *reply,
388 size_t sMaxSz
389 )
390{
391 size_t offset;
392 char buffer[8]; /* needs to be at least 5B: set to 8 because
393 it will be padded to 8 from 5 anyway. */
394
395 if (strnlen(reply, sMaxSz) < 13)
396 {
397 PRINTF_ERROR("[ERROR] Reply could not be decoded: shorter than expected. "
398 "It is likely that only a partial reply was read.\n"
399 "Reply value: %s", reply);
401 }
402
403 destination->message_type = '!';
404
405 /* device address: 2 digits, 00-99
406 *
407 * The device address is part of the same "token" as the message type,
408 * so we call strtol on the same token, skipping the first char.
409 *
410 * We don't check the length here for future-proofing:
411 * if we add support for more device addresses (ie. more digits),
412 * this should handle it gracefully. */
413 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
414 destination->device_address = (int) strtol(buffer + 1, NULL, 10);
415 reply += offset;
416
417 /* axis number: 1 digit, 0-9
418 *
419 * The axis number may be 2 digits (or more) in the future, but it's
420 * unlikely to go over 2^31 - 1 any time soon, so we convert to
421 * standard int and use that. */
422 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
423 destination->axis_number = (int) strtol(buffer, NULL, 10);
424 reply += offset;
425
426 destination->reply_flags[0] = '\0';
427
428 /* device status: 4 letters
429 *
430 * Replies and alerts have a "status" field. Value is either "IDLE" or
431 * "BUSY", depending on what the device is doing at the time. */
432 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
433 if (offset > sizeof(destination->device_status))
434 {
435 PRINTF_ERROR("[ERROR] Reply could not be decoded: device status too "
436 "long. Expected length: %zu. Your length: %zu. Device status "
437 "value: %s\n.", sizeof(destination->device_status), offset,
438 buffer);
440 }
441 strcpy(destination->device_status, buffer);
442 reply += offset;
443
444 /* warning flags: 2 letters
445 *
446 * Replies and alerts have warning flags. Warning flags are typically
447 * "--". All other possible warning flag values should be two
448 * consecutive capital letters. */
449 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
450 if (offset > sizeof(destination->warning_flags))
451 {
452 PRINTF_ERROR("[ERROR] Reply could not be decoded: warning flags too "
453 "long. Expected length: %zu. Your length: %zu. Warning flags "
454 "value: %s\n.", sizeof(destination->warning_flags), offset,
455 buffer);
457 }
458 strcpy(destination->warning_flags, buffer);
459
460 destination->response_data[0] = '\0';
461
462 return Z_SUCCESS;
463}
464
465static int decode_info( struct za_reply *destination,
466 const char *reply,
467 size_t sMaxSz
468 )
469{
470 size_t length,
471 offset;
472 char buffer[8]; /* needs to be at least 5B: set to 8 because
473 it will be padded to 8 from 5 anyway. */
474
475 if (strnlen(reply, sMaxSz) < 7)
476 {
477 PRINTF_ERROR("[ERROR] Reply could not be decoded: shorter than expected. "
478 "It is likely that only a partial reply was read.\n"
479 "Reply value: %s", reply);
481 }
482
483 destination->message_type = '#';
484
485 /* device address: 2 digits, 00-99
486 *
487 * The device address is part of the same "token" as the message type,
488 * so we call strtol on the same token, skipping the first char.
489 *
490 * We don't check the length here for future-proofing:
491 * if we add support for more device addresses (ie. more digits),
492 * this should handle it gracefully. */
493 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
494 destination->device_address = (int) strtol(buffer + 1, NULL, 10);
495 reply += offset;
496
497 /* axis number: 1 digit, 0-9
498 *
499 * The axis number may be 2 digits (or more) in the future, but it's
500 * unlikely to go over 2^31 - 1 any time soon, so we convert to
501 * standard int and use that. */
502 offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
503 destination->axis_number = (int) strtol(buffer, NULL, 10);
504 reply += offset;
505
506 destination->reply_flags[0] = '\0';
507 destination->device_status[0] = '\0';
508 destination->warning_flags[0] = '\0';
509
510 /* data: 1+ characters, probably less than 128 characters
511 *
512 * Replies and info have data. This can be anything, including spaces,
513 * numbers, and human-readable text. */
514 length = strnlen(reply,sMaxSz); /* get length of remaining data */
515 if (length > sizeof(destination->response_data))
516 {
517 PRINTF_ERROR("[ERROR] Reply could not be decoded: response data too "
518 "long. Maximum length: %zu. Your length: %zu. Data: %s.\n",
519 sizeof(destination->response_data), length, reply);
521 }
522 strcpy(destination->response_data, reply);
523
524 return Z_SUCCESS;
525}
526
527/* This function tokenizes using the above helper function to copy
528 * token-by-token from reply into buffer, or directly into destination,
529 * depending on the field in destination we are populating. It could probably
530 * be prettier, more clever, or more efficient, but it's written to be
531 * readable, reliable, and robust first.
532 *
533 * Note that we can safely use strcpy here because the above helper function is
534 * guaranteed to write a NUL at the end of its destination string.
535 *
536 * See http://www.zaber.com/wiki/Manuals/ASCII_Protocol_Manual#Replies for
537 * more info on replies.
538 */
539int za_decode( struct za_reply *destination,
540 const char *reply,
541 size_t sMaxSz
542 )
543{
544 char message_type;
545
546 if (destination == NULL || reply == NULL)
547 {
548 PRINT_ERROR("[ERROR] decoding requires both a non-NULL destination "
549 "and reply to decode.");
551 }
552
553 if (strnlen(reply, sMaxSz) == 0)
554 {
555 PRINT_ERROR("[ERROR] Reply could not be decoded: no data.");
557 }
558 message_type = reply[0];
559
560 /* most replies are 18 chars or longer: info and alerts can be shorter */
561 switch(message_type)
562 {
563 case '@':
564 return decode_reply(destination, reply, sMaxSz);
565 case '!':
566 return decode_alert(destination, reply, sMaxSz);
567 case '#':
568 return decode_info(destination, reply, sMaxSz);
569 default:
570 PRINTF_ERROR("[ERROR] Reply could not be decoded: unexpected "
571 "message type. Valid types are '@' (reply), '!' (alert), "
572 "and '#' (info). Your type: '%c'. \n", message_type);
574 }
575}
#define READ_TIMEOUT
Definition z_common.h:53
@ Z_ERROR_COULD_NOT_DECODE
Definition z_common.h:76
@ Z_ERROR_SYSTEM_ERROR
Definition z_common.h:67
@ Z_ERROR_NULL_PARAMETER
Definition z_common.h:71
@ Z_SUCCESS
Definition z_common.h:65
@ Z_ERROR_INVALID_BAUDRATE
Definition z_common.h:73
@ Z_ERROR_BUFFER_TOO_SMALL
Definition z_common.h:69
@ Z_ERROR_TIMEOUT
Definition z_common.h:78
#define SYSCALL(F)
Definition za_serial.c:50
static size_t copy_until_delim(char *destination, const char *source, const char delim, size_t destination_length)
Definition za_serial.c:263
static int decode_reply(struct za_reply *destination, const char *reply, size_t sMaxSz)
Definition za_serial.c:279
int za_connect(z_port *port, const char *port_name)
Definition za_serial.c:53
int za_setbaud(z_port port, int baud)
Definition za_serial.c:200
#define PRINT_ERROR(M)
Definition za_serial.c:35
void za_set_verbose(int value)
Definition za_serial.c:18
static int za_verbose
Definition za_serial.c:16
static int decode_info(struct za_reply *destination, const char *reply, size_t sMaxSz)
Definition za_serial.c:465
int za_receive(z_port port, char *destination, int length)
Definition za_serial.c:151
int za_decode(struct za_reply *destination, const char *reply, size_t sMaxSz)
Definition za_serial.c:539
static int decode_alert(struct za_reply *destination, const char *reply, size_t sMaxSz)
Definition za_serial.c:386
int za_send(z_port port, const char *command, size_t sMaxSz)
Definition za_serial.c:112
int za_drain(z_port port)
Definition za_serial.c:235
#define PRINTF_ERROR(M,...)
Definition za_serial.c:37
int za_disconnect(z_port port)
Definition za_serial.c:99
Provides a set of functions for interacting with Zaber devices in the ASCII protocol.
char device_status[5]
Definition za_serial.h:44
char message_type
Definition za_serial.h:35
int device_address
Definition za_serial.h:37
char reply_flags[3]
Definition za_serial.h:42
char warning_flags[3]
Definition za_serial.h:47
int axis_number
Definition za_serial.h:40
char response_data[128]
Definition za_serial.h:50