API
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 
16 static int za_verbose = 1;
17 
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 
53 int 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 
99 int 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  */
112 int 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.");
124  return Z_ERROR_NULL_PARAMETER;
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);
139  return Z_ERROR_SYSTEM_ERROR;
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 
151 int 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.");
189  return Z_ERROR_SYSTEM_ERROR;
190  }
191  if (destination != NULL)
192  {
193  destination[nread] = '\0'; /* chomp the "\r\n" */
194  }
195  return nread;
196  }
197  }
198 }
199 
200 int 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 
235 int 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. */
263 static 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 
279 static 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 
386 static 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 
465 static 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  */
539 int 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.");
550  return Z_ERROR_NULL_PARAMETER;
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 }
GeneratorWrapper< T > value(T &&value)
Definition: catch.hpp:4001
#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