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, const char *command)
113 {
114  int nlast,
115  length,
116  written = 0;
117 
118  if (command == NULL)
119  {
120  PRINT_ERROR("[ERROR] command cannot be NULL.");
121  return Z_ERROR_NULL_PARAMETER;
122  }
123 
124  if (command[0] != '/')
125  { /* send a '/' if they forgot it */
126  SYSCALL(nlast = write(port, "/", 1));
127  written += nlast;
128  }
129  length = strlen(command);
130  SYSCALL(nlast = write(port, command, length));
131  written += nlast;
132  if (nlast != length)
133  { /* write can return a short write: test for it */
134  PRINTF_ERROR("[ERROR] write did not write entire message: "
135  "could only write %d bytes of %d.", nlast, length);
136  return Z_ERROR_SYSTEM_ERROR;
137  }
138 
139  if (length == 0 || command[length-1] != '\n')
140  { /* send a newline if they forgot it */
141  SYSCALL(nlast = write(port, "\n", 1));
142  written += nlast;
143  }
144 
145  return written;
146 }
147 
148 int za_receive(z_port port, char *destination, int length)
149 {
150  int nread = 0,
151  nlast;
152  char c;
153 
154  for (;;)
155  {
156  SYSCALL(nlast = (int) read(port, &c, 1));
157 
158  if (nlast == 0) /* timed out */
159  {
160  //PRINTF_ERROR("[INFO] Read timed out after reading %d "
161  // "bytes.", nread);
162  return Z_ERROR_TIMEOUT;
163  }
164 
165  if (destination != NULL)
166  {
167  destination[nread] = c;
168  }
169  nread += nlast;
170 
171  if (nread == length)
172  {
173  PRINTF_ERROR("[ERROR] Read destination buffer not large "
174  "enough. Recommended size: 256B. Your size: %dB.",
175  length);
177  }
178 
179  if (c == '\n')
180  {
181  nread -= 2; /* prepare to cut off the "\r\n" */
182  if (nread < 0)
183  {
184  PRINT_ERROR("[ERROR] Reply too short. It is likely that "
185  "only a partial reply was read.");
186  return Z_ERROR_SYSTEM_ERROR;
187  }
188  if (destination != NULL)
189  {
190  destination[nread] = '\0'; /* chomp the "\r\n" */
191  }
192  return nread;
193  }
194  }
195 }
196 
197 int za_setbaud(z_port port, int baud)
198 {
199  struct termios tio;
200  speed_t tbaud;
201  switch (baud)
202  {
203  case 9600:
204  tbaud = B9600;
205  break;
206  case 19200:
207  tbaud = B19200;
208  break;
209  case 38400:
210  tbaud = B38400;
211  break;
212  case 57600:
213  tbaud = B57600;
214  break;
215  case 115200:
216  tbaud = B115200;
217  break;
218  default:
219  PRINTF_ERROR("[ERROR] Invalid baud rate. Valid rates are "
220  "9600, 19200, 38400, 57600, and 115200 (default).\n"
221  "Your rate: %d.", baud);
223  }
224 
225  SYSCALL(tcgetattr(port, &tio));
226  SYSCALL(cfsetospeed(&tio, tbaud) & cfsetispeed(&tio, tbaud));
227  SYSCALL(tcsetattr(port, TCSAFLUSH, &tio));
228 
229  return Z_SUCCESS;
230 }
231 
232 int za_drain(z_port port)
233 {
234  struct termios tio;
235  int old_timeout;
236  char c;
237 
238  /* set timeout to 0.1s */
239  SYSCALL(tcgetattr(port, &tio));
240  old_timeout = tio.c_cc[VTIME];
241  tio.c_cc[VTIME] = 1;
242  SYSCALL(tcsetattr(port, TCSANOW, &tio));
243 
244  /* flush and read whatever else comes in */
245  SYSCALL(tcflush(port, TCIFLUSH));
246  while(read(port, &c, 1) > 0);
247 
248  /* set timeout back to what it was */
249  tio.c_cc[VTIME] = old_timeout;
250  SYSCALL(tcsetattr(port, TCSANOW, &tio));
251 
252  return Z_SUCCESS;
253 }
254 
255 
256 /* A helper for za_decode. Copies from source to destination, until delim or
257  * a '\0' is found, then null-terminates destination.
258  *
259  * Returns the number of bytes copied, including the added null-terminator. */
260 static size_t copy_until_delim(char *destination, const char *source,
261  const char delim, size_t destination_length)
262 {
263  size_t i;
264 
265  for(i = 0; source[i] != delim && source[i] != '\0'
266  && i < destination_length - 1; i++)
267  {
268  destination[i] = source[i];
269  }
270 
271  destination[i] = '\0'; /* null-terminate instead of copying delim */
272 
273  return i + 1;
274 }
275 
276 static int decode_reply(struct za_reply *destination, const char *reply)
277 {
278  char buffer[8]; /* needs to be at least 5B: set to 8 because
279  it will be padded to 8 from 5 anyway. */
280  size_t offset,
281  length;
282 
283  if (strlen(reply) < 18)
284  {
285  PRINTF_ERROR("[ERROR] Reply could not be decoded: shorter than expected. "
286  "It is likely that only a partial reply was read.\n"
287  "Reply value: %s", reply);
289  }
290 
291  destination->message_type = '@';
292 
293  /* device address: 2 digits, 00-99
294  *
295  * The device address is part of the same "token" as the message type,
296  * so we call strtol on the same token, skipping the first char.
297  *
298  * We don't check the length here for future-proofing:
299  * if we add support for more device addresses (ie. more digits),
300  * this should handle it gracefully. */
301  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
302  destination->device_address = (int) strtol(buffer + 1, NULL, 10);
303  reply += offset;
304 
305  /* axis number: 1 digit, 0-9
306  *
307  * The axis number may be 2 digits (or more) in the future, but it's
308  * unlikely to go over 2^31 - 1 any time soon, so we convert to
309  * standard int and use that. */
310  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
311  destination->axis_number = (int) strtol(buffer, NULL, 10);
312  reply += offset;
313 
314  /* reply flags: 2 letters
315  *
316  * Only replies have reply flags. Value is either "OK" or "RJ". */
317  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
318  if (offset > sizeof(destination->reply_flags))
319  {
320  PRINTF_ERROR("[ERROR] Reply could not be decoded: reply flags too "
321  "long. Maximum length: %zu. Your length: %zu. Reply flags "
322  "value: %s\n.", sizeof(destination->reply_flags), offset,
323  buffer);
325  }
326  strcpy(destination->reply_flags, buffer);
327  reply += offset;
328 
329  /* device status: 4 letters
330  *
331  * Replies and alerts have a "status" field. Value is either "IDLE" or
332  * "BUSY", depending on what the device is doing at the time. */
333  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
334  if (offset > sizeof(destination->device_status))
335  {
336  PRINTF_ERROR("[ERROR] Reply could not be decoded: device status too "
337  "long. Expected length: %zu. Your length: %zu. Device status "
338  "value: %s\n.", sizeof(destination->device_status), offset,
339  buffer);
341  }
342  strcpy(destination->device_status, buffer);
343  reply += offset;
344 
345  /* warning flags: 2 letters
346  *
347  * Replies and alerts have warning flags. Warning flags are typically
348  * "--". All other possible warning flag values should be two
349  * consecutive capital letters. */
350  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
351  if (offset > sizeof(destination->warning_flags))
352  {
353  PRINTF_ERROR("[ERROR] Reply could not be decoded: warning flags too "
354  "long. Expected length: %zu. Your length: %zu. Warning flags "
355  "value: %s\n.", sizeof(destination->warning_flags), offset,
356  buffer);
358  }
359  strcpy(destination->warning_flags, buffer);
360  reply += offset;
361 
362  /* data: 1+ characters, probably less than 128 characters
363  *
364  * Replies and info have data. This can be anything, including spaces,
365  * numbers, and human-readable text. */
366  length = strlen(reply); /* get length of remaining data */
367  if (length > sizeof(destination->response_data))
368  {
369  PRINTF_ERROR("[ERROR] Reply could not be decoded: response data too "
370  "long. Maximum length: %zu. Your length: %zu. Data: %s.\n",
371  sizeof(destination->response_data), length, reply);
373  }
374  strcpy(destination->response_data, reply);
375 
376  return Z_SUCCESS;
377 
378 }
379 
380 static int decode_alert(struct za_reply *destination, const char *reply)
381 {
382  size_t offset;
383  char buffer[8]; /* needs to be at least 5B: set to 8 because
384  it will be padded to 8 from 5 anyway. */
385 
386  if (strlen(reply) < 13)
387  {
388  PRINTF_ERROR("[ERROR] Reply could not be decoded: shorter than expected. "
389  "It is likely that only a partial reply was read.\n"
390  "Reply value: %s", reply);
392  }
393 
394  destination->message_type = '!';
395 
396  /* device address: 2 digits, 00-99
397  *
398  * The device address is part of the same "token" as the message type,
399  * so we call strtol on the same token, skipping the first char.
400  *
401  * We don't check the length here for future-proofing:
402  * if we add support for more device addresses (ie. more digits),
403  * this should handle it gracefully. */
404  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
405  destination->device_address = (int) strtol(buffer + 1, NULL, 10);
406  reply += offset;
407 
408  /* axis number: 1 digit, 0-9
409  *
410  * The axis number may be 2 digits (or more) in the future, but it's
411  * unlikely to go over 2^31 - 1 any time soon, so we convert to
412  * standard int and use that. */
413  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
414  destination->axis_number = (int) strtol(buffer, NULL, 10);
415  reply += offset;
416 
417  destination->reply_flags[0] = '\0';
418 
419  /* device status: 4 letters
420  *
421  * Replies and alerts have a "status" field. Value is either "IDLE" or
422  * "BUSY", depending on what the device is doing at the time. */
423  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
424  if (offset > sizeof(destination->device_status))
425  {
426  PRINTF_ERROR("[ERROR] Reply could not be decoded: device status too "
427  "long. Expected length: %zu. Your length: %zu. Device status "
428  "value: %s\n.", sizeof(destination->device_status), offset,
429  buffer);
431  }
432  strcpy(destination->device_status, buffer);
433  reply += offset;
434 
435  /* warning flags: 2 letters
436  *
437  * Replies and alerts have warning flags. Warning flags are typically
438  * "--". All other possible warning flag values should be two
439  * consecutive capital letters. */
440  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
441  if (offset > sizeof(destination->warning_flags))
442  {
443  PRINTF_ERROR("[ERROR] Reply could not be decoded: warning flags too "
444  "long. Expected length: %zu. Your length: %zu. Warning flags "
445  "value: %s\n.", sizeof(destination->warning_flags), offset,
446  buffer);
448  }
449  strcpy(destination->warning_flags, buffer);
450 
451  destination->response_data[0] = '\0';
452 
453  return Z_SUCCESS;
454 }
455 
456 static int decode_info(struct za_reply *destination, const char *reply)
457 {
458  size_t length,
459  offset;
460  char buffer[8]; /* needs to be at least 5B: set to 8 because
461  it will be padded to 8 from 5 anyway. */
462 
463  if (strlen(reply) < 7)
464  {
465  PRINTF_ERROR("[ERROR] Reply could not be decoded: shorter than expected. "
466  "It is likely that only a partial reply was read.\n"
467  "Reply value: %s", reply);
469  }
470 
471  destination->message_type = '#';
472 
473  /* device address: 2 digits, 00-99
474  *
475  * The device address is part of the same "token" as the message type,
476  * so we call strtol on the same token, skipping the first char.
477  *
478  * We don't check the length here for future-proofing:
479  * if we add support for more device addresses (ie. more digits),
480  * this should handle it gracefully. */
481  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
482  destination->device_address = (int) strtol(buffer + 1, NULL, 10);
483  reply += offset;
484 
485  /* axis number: 1 digit, 0-9
486  *
487  * The axis number may be 2 digits (or more) in the future, but it's
488  * unlikely to go over 2^31 - 1 any time soon, so we convert to
489  * standard int and use that. */
490  offset = copy_until_delim(buffer, reply, ' ', sizeof(buffer));
491  destination->axis_number = (int) strtol(buffer, NULL, 10);
492  reply += offset;
493 
494  destination->reply_flags[0] = '\0';
495  destination->device_status[0] = '\0';
496  destination->warning_flags[0] = '\0';
497 
498  /* data: 1+ characters, probably less than 128 characters
499  *
500  * Replies and info have data. This can be anything, including spaces,
501  * numbers, and human-readable text. */
502  length = strlen(reply); /* get length of remaining data */
503  if (length > sizeof(destination->response_data))
504  {
505  PRINTF_ERROR("[ERROR] Reply could not be decoded: response data too "
506  "long. Maximum length: %zu. Your length: %zu. Data: %s.\n",
507  sizeof(destination->response_data), length, reply);
509  }
510  strcpy(destination->response_data, reply);
511 
512  return Z_SUCCESS;
513 }
514 
515 /* This function tokenizes using the above helper function to copy
516  * token-by-token from reply into buffer, or directly into destination,
517  * depending on the field in destination we are populating. It could probably
518  * be prettier, more clever, or more efficient, but it's written to be
519  * readable, reliable, and robust first.
520  *
521  * Note that we can safely use strcpy here because the above helper function is
522  * guaranteed to write a NUL at the end of its destination string.
523  *
524  * See http://www.zaber.com/wiki/Manuals/ASCII_Protocol_Manual#Replies for
525  * more info on replies.
526  */
527 int za_decode(struct za_reply *destination, const char *reply)
528 {
529  char message_type;
530 
531  if (destination == NULL || reply == NULL)
532  {
533  PRINT_ERROR("[ERROR] decoding requires both a non-NULL destination "
534  "and reply to decode.");
535  return Z_ERROR_NULL_PARAMETER;
536  }
537 
538  if (strlen(reply) == 0)
539  {
540  PRINT_ERROR("[ERROR] Reply could not be decoded: no data.");
542  }
543  message_type = reply[0];
544 
545  /* most replies are 18 chars or longer: info and alerts can be shorter */
546  switch(message_type)
547  {
548  case '@':
549  return decode_reply(destination, reply);
550  case '!':
551  return decode_alert(destination, reply);
552  case '#':
553  return decode_info(destination, reply);
554  default:
555  PRINTF_ERROR("[ERROR] Reply could not be decoded: unexpected "
556  "message type. Valid types are '@' (reply), '!' (alert), "
557  "and '#' (info). Your type: '%c'. \n", message_type);
559  }
560 }
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
int za_decode(struct za_reply *destination, const char *reply)
Definition: za_serial.c:527
#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:260
static int decode_reply(struct za_reply *destination, const char *reply)
Definition: za_serial.c:276
static int decode_alert(struct za_reply *destination, const char *reply)
Definition: za_serial.c:380
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:197
#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)
Definition: za_serial.c:456
int za_receive(z_port port, char *destination, int length)
Definition: za_serial.c:148
int za_send(z_port port, const char *command)
Definition: za_serial.c:112
int za_drain(z_port port)
Definition: za_serial.c:232
#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