/* * Copyright (c) 2010-2013 Douglas Gilbert. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* * This utility aims to test devices with a serial interface that use a * command response type protocol. The serial interface is one derived * from RS232 or V24. This utility reads ACSII hex pairs from an input * file (or stdin) then sends the corresponding binary to the given . * Then, optionally, it reads the response from and prints it in * ASCII hex on stdout */ #define _GNU_SOURCE #define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char * version_str = "1.05 20130226"; #define DEF_BAUD_RATE B38400 #define DEF_BAUD_RATE_STR "38400" static struct termios tty_saved_attribs; static int tty_saved_fd = -1; static int verbose = 0; static void usage(void) { fprintf(stderr, "Usage: " "hex2tty [-a] [-b ] [-B ] [-c] [-d] [-D] [-h]\n" " [-H ] [-i ] [-n] [-P N|E|O] " "[-r ]\n" " [-R] [-S ] [-v] [-V] [-x] \n" " where:\n" " -a with '-r ' show bytes in ASCII " "as well\n" " -b baud rate of (default: %s)\n" " -B number of data bits: 5, 6, 7 or 8 (default)\n" " -c hardware handshake (RTS+CTS); use twice " "to clear\n" " -d interpret input as ASCII decimal (default: " "ASCII hex)\n" " -D set DTR, use twice to clear DTR (need '-n' " "and '-x'\n" " to keep level after this utility completes)\n" " -h print usage message\n" " -H file containing ASCII hex to send " "to \n" " if not given (and '-i' variant) then " "read stdin\n" " -i same as '-H '\n" " -n no HUPCL (stop RTS+DTR being cleared on " "close)\n" " use twice: set HUPCL (Hang UP on CLose)\n" " -P N|E|O parity: N->none (default), E->even, O->odd\n" " -r after send, read bytes from , " "print\n" " in ASCII hex on stdout\n" " -R set RTS, use twice to clear RTS (may need " "'-n -x')\n" " -S number of stop bits, 1 (default) or 2\n" " -v increase verbosity (more written to log)\n" " -V print version string then exit\n" " -x will not restore previous settings on exit; " "if used\n" " only once will ignore and skip " "output\n\n" "Send bytes, decoded from ASCII hex in or stdin, to " ".\n" "The hex can be in two digit pairs, single digit hex needs to " "be separated\nby whitespace or commas. Hex can appear on " "multiple lines, anything after\n" "a '#' on a line is regarded as a comment. Restores previous " " settings\n(unless '-x' or '-xx' given). Default framing " "is 8-N-1 .\nRead times out after 2 seconds.\n" "Example for keeping settings: 'hex2tty -b 38400 -c -n -x " "/dev/ttyS1'\n", DEF_BAUD_RATE_STR); } static char * serr(void) { return strerror(errno); } static void termination_handler(int signum) { // tcsetattr(STDIN_FILENO, TCSANOW, &stdin_saved_attributes); if (tty_saved_fd >= 0) { if (verbose > 1) fprintf(stderr, "restoring settings to previous " "settings [signum=%d]\n", signum); tcsetattr(tty_saved_fd, TCSANOW, &tty_saved_attribs); close(tty_saved_fd); tty_saved_fd = -1; } fprintf(stderr, "Termination signal causes exit\n"); exit(0); } static int tty_open(const char * tty_dev, int tty_speed, int dtr, int rts, int hhandshake, int no_hupcl, int nbits, int parit, int sbits) { int tty_fd, res, mask, mbits; struct termios new_attributes; // tty_fd = open(tty_dev, (O_RDWR | O_NOCTTY | O_NONBLOCK)); tty_fd = open(tty_dev, (O_RDWR | O_NOCTTY | O_SYNC)); if (tty_fd < 0) { fprintf(stderr, "tty_open: open() of %s failed: %s\n", tty_dev, strerror(errno)); return -1; } tcgetattr(tty_fd, &tty_saved_attribs); tcgetattr(tty_fd, &new_attributes); // Set the new attributes for the serial port, 'man termios' cfsetospeed(&new_attributes, tty_speed); cfsetispeed(&new_attributes, tty_speed); // c_cflag new_attributes.c_cflag |= CREAD; // Enable receiver new_attributes.c_cflag &= ~CSIZE; // clear size mask switch (nbits) { case 5: new_attributes.c_cflag |= CS5; break; case 6: new_attributes.c_cflag |= CS6; break; case 7: new_attributes.c_cflag |= CS7; break; case 8: default: new_attributes.c_cflag |= CS8; // 8 data bit break; } switch (parit) { case 'E': new_attributes.c_cflag |= PARENB; new_attributes.c_cflag &= ~PARODD; break; case 'O': new_attributes.c_cflag |= PARENB; new_attributes.c_cflag |= PARODD; break; case 'N': new_attributes.c_cflag &= ~PARENB; break; } if (1 == sbits) new_attributes.c_cflag &= ~CSTOPB; else new_attributes.c_cflag |= CSTOPB; if (no_hupcl) { // clear Hang Up on CLose (effects DTR+RTS) if (1 == no_hupcl) { new_attributes.c_cflag &= ~HUPCL; if (verbose) fprintf(stderr, "clearing HUPCL so RTS+DTR keep setting " "after close\n"); } else new_attributes.c_cflag |= HUPCL; } if (hhandshake) { if (1 == hhandshake) new_attributes.c_cflag |= CRTSCTS; else new_attributes.c_cflag &= ~CRTSCTS; } // c_iflag if ('N' == parit) new_attributes.c_iflag |= IGNPAR; // Ignore framing and parity // Suggested by Michael Kerrisk [The Linux Programming Interface]. // He also included "| PARMRK". new_attributes.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON); // c_oflag [Turn off corrupting output post-processing] new_attributes.c_oflag &= ~(OPOST); // c_lflag new_attributes.c_lflag &= ~(ICANON | IEXTEN | ECHO | ECHOE | ISIG); // Next two only apply for non-canonical reads (which we have set) new_attributes.c_cc[VMIN] = 0; // Min chars to read new_attributes.c_cc[VTIME] = 20; // Timeout in 100 ms units res = tcsetattr(tty_fd, TCSANOW, &new_attributes); if (res < 0) { fprintf(stderr, "tty_open: tcsetattr() failed: %s\n", strerror(errno)); close(tty_fd); tty_fd = -1; } if (verbose > 1) { if (ioctl(tty_fd, TIOCMGET, &mbits) >= 0) fprintf(stderr, "modem lines set: %s%s%s%s [0x%x]\n", ((mbits & TIOCM_DSR) ? "DSR," : ""), ((mbits & TIOCM_RNG) ? "RING," : ""), ((mbits & TIOCM_CAR) ? "DCD," : ""), ((mbits & TIOCM_CTS) ? "CTS," : ""), mbits); } if (dtr) { mask = TIOCM_DTR; if (1 == dtr) ioctl(tty_fd, TIOCMBIS, &mask); else ioctl(tty_fd, TIOCMBIC, &mask); } if (rts) { mask = TIOCM_RTS; if (1 == rts) ioctl(tty_fd, TIOCMBIS, &mask); else ioctl(tty_fd, TIOCMBIC, &mask); } if ((tty_fd >= 0) && (verbose > 3)) { char b[128]; snprintf(b, sizeof(b) - 1, "stty -a -F %s", tty_dev); printf(">>> Output from this command line invocation: %s\n", b); res = system(b); if (WIFSIGNALED(res) && (WTERMSIG(res) == SIGINT || WTERMSIG(res) == SIGQUIT)) raise(WTERMSIG(res)); /* ignore res of not a signal */ } return tty_fd; } /* print bytes in ASCII-hex, optionally with leading address and/or * trailing ASCII. 'addr_ascii' allows for 4 output types: * > 0 each line has address then up to 16 ASCII-hex bytes * = 0 in addition, the bytes are listed in ASCII to the right * = -1 only the ASCII-hex bytes are listed (i.e. without address) * < -1 ASCII-hex bytes with ASCII to right (and without address) */ static void dStrHex(const char * str, int len, int addr_ascii) { const char* p = str; unsigned char c; char buff[82]; int a = 0; const int bpstart = 5; const int cpstart = 60; int cpos = cpstart; int bpos = bpstart; int i, k; if (len <= 0) return; memset(buff, ' ', 80); buff[80] = '\0'; if (-1 == addr_ascii) { for (k = 0; k < len; k++) { c = *p++; bpos += 3; if (bpos == (bpstart + (9 * 3))) bpos++; sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c); buff[bpos + 2] = ' '; if ((k > 0) && (0 == ((k + 1) % 16))) { printf("%.60s\n", buff); bpos = bpstart; memset(buff, ' ', 80); } } if (bpos > bpstart) printf("%.60s\n", buff); return; } /* addr_ascii >= 0, start each line with address (offset) */ if (addr_ascii >= 0) { k = sprintf(buff + 1, "%.2x", a); buff[k + 1] = ' '; } for (i = 0; i < len; i++) { c = *p++; bpos += 3; if (bpos == (bpstart + (9 * 3))) bpos++; sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c); buff[bpos + 2] = ' '; if (addr_ascii > 0) buff[cpos++] = ' '; else { if ((c < ' ') || (c >= 0x7f)) c = '.'; buff[cpos++] = c; } if (cpos > (cpstart + 15)) { printf("%.76s\n", buff); bpos = bpstart; cpos = cpstart; a += 16; memset(buff, ' ', 80); if (addr_ascii >= 0) { k = sprintf(buff + 1, "%.2x", a); buff[k + 1] = ' '; } } } if (cpos > cpstart) printf("%.76s\n", buff); } int main(int argc, char *argv[]) { int opt, baud, num, k; int ooff = 0; const char * tty_dev = NULL; const char * hex_file = NULL; int and_ascii = 0; int as_decimal = 0; int xopen = 0; int tty_speed = DEF_BAUD_RATE; int hhandshake = 0; int dtr = 0; int no_hupcl = 0; int num_bits = 8; int parity = 'N'; int rts = 0; int stop_bits = 1; int to_read = 0; unsigned char bny[2048]; char hex[2048]; char *cp; char c1, c2, c3; FILE * fp = NULL; while ((opt = getopt(argc, argv, "ab:B:cdDhH:i:nP:r:RS:vVx")) != -1) { switch (opt) { case 'a': ++and_ascii; break; case 'b': baud = 0; baud = atoi(optarg); switch (baud) { case 300: tty_speed = B300; break; case 1200: tty_speed = B1200; break; case 2400: tty_speed = B2400; break; case 4800: tty_speed = B4800; break; case 9600: tty_speed = B9600; break; case 19200: tty_speed = B19200; break; case 38400: tty_speed = B38400; break; case 57600: tty_speed = B57600; break; case 115200: tty_speed = B115200; break; case 230400: tty_speed = B230400; break; default: fprintf(stderr, "Allowable rates: 300, 1200, 2400, 4800, " "9600, 19200, 38400, 57600\n115200 or 230400\n"); exit(EXIT_FAILURE); } break; case 'B': k = atoi(optarg); if ((k < 5) || ( k > 8)) { fprintf(stderr, " should be 5, 6, 7 or 8\n"); exit(EXIT_FAILURE); } num_bits = k; break; case 'c': ++hhandshake; break; case 'd': ++as_decimal; break; case 'D': ++dtr; break; case 'h': usage(); exit(EXIT_SUCCESS); case 'H': hex_file = optarg; break; case 'i': hex_file = optarg; break; case 'n': ++no_hupcl; break; case 'P': switch((parity = toupper(optarg[0]))) { case 'N': case 'E': case 'O': break; default: fprintf(stderr, "expect '-P' argument to be 'N', 'E' or " "'O'\n"); exit(EXIT_FAILURE); } break; case 'r': k = atoi(optarg); if ((k < 0) || ( k > (int)sizeof(bny))) { fprintf(stderr, " to read cannot exceed %d or be " "negative\n", (int)sizeof(bny)); exit(EXIT_FAILURE); } to_read = k; break; case 'R': ++rts; break; case 'S': k = atoi(optarg); if ((k < 1) || ( k > 2)) { fprintf(stderr, " should be 1 or 2\n"); exit(EXIT_FAILURE); } stop_bits = k; break; case 'v': ++verbose; break; case 'V': printf("%s\n", version_str); exit(EXIT_SUCCESS); case 'x': ++xopen; break; default: /* '?' */ usage(); exit(EXIT_FAILURE); } } if (optind < argc) { if (NULL == tty_dev) { tty_dev = argv[optind]; ++optind; } if (optind < argc) { for (; optind < argc; ++optind) fprintf(stderr, "Unexpected extra argument: %s\n", argv[optind]); usage(); exit(EXIT_FAILURE); } } if (NULL == tty_dev) { fprintf(stderr, "missing argument\n"); usage(); exit(EXIT_FAILURE); } if (signal(SIGINT, termination_handler) == SIG_IGN) signal (SIGINT, SIG_IGN); if (signal(SIGHUP, termination_handler) == SIG_IGN) signal (SIGHUP, SIG_IGN); if (signal (SIGTERM, termination_handler) == SIG_IGN) signal (SIGTERM, SIG_IGN); if (1 == xopen) goto bypass_read; else if (hex_file) { if ((fp = fopen(hex_file, "r")) == NULL) { fprintf(stderr, "fopen on %s failed with %s\n", hex_file, strerror(errno)); exit(EXIT_FAILURE); } num = fread(hex, 1, sizeof(hex) - 1, fp); if (num <= 0) { fprintf(stderr, " %s empty or some other problem\n", hex_file); return EXIT_SUCCESS; } } else { // read from stdin num = fread(hex, 1, sizeof(hex) - 1, stdin); if (num <= 0) { fprintf(stderr, "nothing read on stdin\n"); return EXIT_SUCCESS; } } hex[num - 1] = '\0'; if (verbose > 2) fprintf(stderr, "read %d bytes from input\n", num); for (ooff = 0, cp = hex; (cp < (hex + num)) && *cp; ) { c1 = *cp++; if ('#' == c1) { cp = strchr(cp, '\n'); if(NULL == cp) break; continue; } if ((isspace(c1)) || (',' == c1)) { continue; } if (as_decimal) { k = 0; if (isdigit(c1)) { k = (c1 - '0'); c2 = *cp; if (isdigit(c2)) { ++cp; k *= 10; k += (c2 - '0'); c3 = *cp; if (isdigit(c3)) { ++cp; k *= 10; k += (c3 - '0'); } } } else { fprintf(stderr, "bad syntax starting near %.*s\n", 8, cp - 1); break; } if ((k < 0) || (k > 255)) { fprintf(stderr, "decimals need to be from 0 to 255 " "inclusive, starting near %.*s\n", 8, cp - 1); break; } bny[ooff++] = k; continue; } if (isxdigit(c1)) { if (c1 < 'A') k = c1 - '0'; else if (c1 < 'a') k = (c1 - 'A') + 10; else k = (c1 - 'a') + 10; c2 = *cp; if (isxdigit(c2)) { bny[ooff] = k << 4; ++cp; if (c2 < 'A') k = c2 - '0'; else if (c2 < 'a') k = (c2 - 'A') + 10; else k = (c2 - 'a') + 10; bny[ooff++] |= k; } else bny[ooff++] = k; } else { fprintf(stderr, "bad syntax starting at %.*s\n", 8, cp); break; } } if (fp) fclose(fp); if (verbose > 1) { if (ooff <= 0) fprintf(stderr, "NO ASCII %s bytes decoded\n", (as_decimal ? "decimal" : "hex")); else { fprintf(stderr, "decoded %d bytes of ASCII %s:\n", ooff, (as_decimal ? "decimal" : "hex")); for (k = 0; k < ooff; ++k) { if ((k > 0) && (0 == (k % 16))) printf("\n"); fprintf(stderr, " %02x", bny[k]); } fprintf(stderr, "\n"); } } bypass_read: if ((tty_saved_fd = tty_open(tty_dev, tty_speed, dtr, rts, hhandshake, no_hupcl, num_bits, parity, stop_bits)) < 0) exit(EXIT_FAILURE); else if (verbose) fprintf(stderr, "opened %s without problems\n", tty_dev); if (1 == xopen) goto the_end; if (tcflush(tty_saved_fd, TCIOFLUSH) < 0) { fprintf(stderr, "tcflush(TCIOFLUSH) on %s failed: %s\n", tty_dev, serr()); exit(EXIT_FAILURE); } else if (verbose > 1) fprintf(stderr, "flushed without problems\n"); if (ooff > 0) { num = write(tty_saved_fd, bny, ooff); if (num < 0) fprintf(stderr, "write() to failed: %s\n", serr()); } if (to_read > 0) { if (verbose) fprintf(stderr, "About to read %d bytes from \n", to_read); for (k = 0, num = 0; k < to_read; k += num) { num = read(tty_saved_fd, bny + k, to_read - k); if (num <= 0) break; } if (num < 0) fprintf(stderr, "write() to failed: %s\n", serr()); else if (verbose) fprintf(stderr, "read() fetched %d byte%s\n", k, (1 == k) ? "" : "s"); if (k > 0) dStrHex((const char *)bny, k, (and_ascii ? -2 : -1)); } the_end: if (tty_saved_fd >= 0) { if (0 == xopen) { if (verbose > 1) fprintf(stderr, "restoring settings to previous " "state\n"); tcsetattr(tty_saved_fd, TCSANOW, &tty_saved_attribs); } else if (verbose > 1) fprintf(stderr, "leaving raw settings in place\n"); close(tty_saved_fd); tty_saved_fd = -1; } return EXIT_SUCCESS; }