From e837fd0303ecc925741f9f0eabf8bc58e7fcd492 Mon Sep 17 00:00:00 2001 From: Martin Lund Date: Sat, 13 Aug 2022 00:04:00 +0200 Subject: [PATCH] Add line response feature Add a simple line response feature to make it possible to send e.g. a command string to your serial device and easily receive and parse a line response. This is a convenience feature for simple request/response interaction based on lines. For more advanced interaction the socket feature should be used instead. The line response feature is detailed via the following options: -r, --response-wait Wait for line response then quit. A line is considered any string ending with either CR or NL character. If no line is received tio will quit after response timeout. Any tio text is automatically muted when piping a string to tio while in response mode to make it easy to parse the response. --response-timeout Set timeout [ms] of line response (default: 100). Example: Sending a string (SCPI command) to a test instrument (Korad PSU) and print line response: $ echo "*IDN?" | tio /dev/ttyACM0 --response-wait KORAD KD3305P V4.2 SN:32477045 --- README.md | 3 +++ man/tio.1.in | 28 +++++++++++++++++++- src/bash-completion/tio.in | 10 +++++++ src/configfile.c | 16 ++++++++++- src/main.c | 6 +++++ src/options.c | 18 ++++++++++++- src/options.h | 3 +++ src/print.h | 12 +++++++++ src/tty.c | 54 +++++++++++++++++++++++++++++++++++--- 9 files changed, 143 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c7763ea..5032175 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ when used in combination with [tmux](https://tmux.github.io). * Activate sub-configurations by name or pattern * Redirect I/O to file or IPv4/v6 network socket for scripting or TTY sharing * Pipe input and/or output + * Support for simple line request/response handling * Bash completion * Color support * Remapping of prefix key @@ -87,6 +88,8 @@ The command-line interface is straightforward as reflected in the output from -c, --color 0..255|bold|none|list Colorize tio text (default: bold) -S, --socket Redirect I/O to file or network socket -x, --hexadecimal Enable hexadecimal mode + -r, --response-wait Wait for line response then quit + --response-timeout Response timeout (default: 100) -v, --version Display version -h, --help Display help diff --git a/man/tio.1.in b/man/tio.1.in index 38ce30a..2102af7 100644 --- a/man/tio.1.in +++ b/man/tio.1.in @@ -214,6 +214,21 @@ If port is 0 or no port is provided default port 3333 is used. At present there is a hardcoded limit of 16 clients connected at one time. .RE +.TP +.BR \-r ", " \-\-response-wait + +Wait for line response then quit. A line is considered any string ending with +either CR or NL character. If no line is received tio will quit after response +timeout. + +Any tio text is automatically muted when piping a string to tio while in +response mode to make it easy to parse the response. + +.TP +.BR " \-\-response\-timeout " \fI + +Set timeout [ms] of line response (default: 100). + .TP .BR \-v ", " \-\-version @@ -339,6 +354,10 @@ Enable hexadecimal mode Set socket to redirect I/O to .IP "\fBprefix-ctrl-key" Set prefix ctrl key (a..z, default: t) +.IP "\fBresponse-wait" +Enable wait for line response +.IP "\fBresponse-timeout" +Set line response timeout .SH "CONFIGURATION FILE EXAMPLES" @@ -475,7 +494,14 @@ $ nc -N 10.0.0.42 4444 Pipe command to the serial device: $ echo "ls -la" | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0 - + +.TP +Pipe command to the serial device and wait for line response (string ending with CR or NL): + +$ echo "*IDN?" | tio /dev/ttyACM0 --response-wait +.TP +In this mode, only the response will be printed. + .TP Likewise, to pipe data from file to the serial device: diff --git a/src/bash-completion/tio.in b/src/bash-completion/tio.in index 3d87cb6..67b936c 100644 --- a/src/bash-completion/tio.in +++ b/src/bash-completion/tio.in @@ -29,6 +29,8 @@ _tio() -L --list-devices \ -c --color \ -S --socket \ + -r --response-wait \ + --response-timeout \ -x --hexadecimal \ -v --version \ -h --help" @@ -112,6 +114,14 @@ _tio() COMPREPLY=( $(compgen -W "unix: inet: inet6:" -- ${cur}) ) return 0 ;; + -r | --response-wait) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + --response-timeout) + COMPREPLY=( $(compgen -W "1 10 100" -- ${cur}) ) + return 0 + ;; -x | --hexadecimal) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 diff --git a/src/configfile.c b/src/configfile.c index b8a1649..1a31ae3 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -249,7 +249,21 @@ static int data_handler(void *user, const char *section, const char *name, option.prefix_key = value[0]; } } - + else if (!strcmp(name, "response-wait")) + { + if (!strcmp(value, "enable")) + { + option.response_wait = true; + } + else if (!strcmp(value, "disable")) + { + option.response_wait = false; + } + } + else if (!strcmp(name, "response-timeout")) + { + option.response_timeout = atoi(value); + } } return 0; } diff --git a/src/main.c b/src/main.c index 7f31645..02f8a47 100644 --- a/src/main.c +++ b/src/main.c @@ -63,6 +63,12 @@ int main(int argc, char *argv[]) { // Enter non interactive mode interactive_mode = false; + + // Mute tio text in response mode + if (option.response_wait) + { + option.mute = true; + } } /* Configure output terminal */ diff --git a/src/options.c b/src/options.c index df3c2bc..49e99d2 100644 --- a/src/options.c +++ b/src/options.c @@ -43,6 +43,7 @@ enum opt_t OPT_LOG_FILE, OPT_LOG_STRIP, OPT_LINE_PULSE_DURATION, + OPT_RESPONSE_TIMEOUT, }; /* Default options */ @@ -74,6 +75,9 @@ struct option_t option = .hex_mode = false, .prefix_code = 20, // ctrl-t .prefix_key = 't', + .response_wait = false, + .response_timeout = 100, + .mute = false, }; void print_help(char *argv[]) @@ -103,6 +107,8 @@ void print_help(char *argv[]) printf(" -c, --color 0..255|bold|none|list Colorize tio text (default: bold)\n"); printf(" -S, --socket Redirect I/O to file or network socket\n"); printf(" -x, --hexadecimal Enable hexadecimal mode\n"); + printf(" -r, --response-wait Wait for line response then quit\n"); + printf(" --response-timeout Response timeout (default: 100)\n"); printf(" -v, --version Display version\n"); printf(" -h, --help Display help\n"); printf("\n"); @@ -287,6 +293,8 @@ void options_parse(int argc, char *argv[]) {"map", required_argument, 0, 'm' }, {"color", required_argument, 0, 'c' }, {"hexadecimal", no_argument, 0, 'x' }, + {"response-wait", no_argument, 0, 'r' }, + {"response-timeout", required_argument, 0, OPT_RESPONSE_TIMEOUT }, {"version", no_argument, 0, 'v' }, {"help", no_argument, 0, 'h' }, {0, 0, 0, 0 } @@ -296,7 +304,7 @@ void options_parse(int argc, char *argv[]) int option_index = 0; /* Parse argument using getopt_long */ - c = getopt_long(argc, argv, "b:d:f:s:p:o:O:netLlS:m:c:xvh", long_options, &option_index); + c = getopt_long(argc, argv, "b:d:f:s:p:o:O:netLlS:m:c:xrvh", long_options, &option_index); /* Detect the end of the options */ if (c == -1) @@ -421,6 +429,14 @@ void options_parse(int argc, char *argv[]) option.hex_mode = true; break; + case 'r': + option.response_wait = true; + break; + + case OPT_RESPONSE_TIMEOUT: + option.response_timeout = string_to_long(optarg); + break; + case 'v': printf("tio v%s\n", VERSION); exit(EXIT_SUCCESS); diff --git a/src/options.h b/src/options.h index ed8a4fa..7650047 100644 --- a/src/options.h +++ b/src/options.h @@ -67,6 +67,9 @@ struct option_t bool hex_mode; unsigned char prefix_code; unsigned char prefix_key; + bool response_wait; + int response_timeout; + bool mute; }; extern struct option_t option; diff --git a/src/print.h b/src/print.h index 24a02b9..c2aba7c 100644 --- a/src/print.h +++ b/src/print.h @@ -33,53 +33,65 @@ extern char ansi_format[]; #define ansi_printf(format, args...) \ { \ + if (!option.mute) { \ if (option.color < 0) \ fprintf (stdout, "\r" format "\r\n", ## args); \ else \ fprintf (stdout, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \ + } \ } #define ansi_error_printf(format, args...) \ { \ + if (!option.mute) { \ if (option.color < 0) \ fprintf (stderr, "\r" format "\r\n", ## args); \ else \ fprintf (stderr, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \ fflush(stderr); \ + } \ } #define ansi_printf_raw(format, args...) \ { \ + if (!option.mute) { \ if (option.color < 0) \ fprintf (stdout, format, ## args); \ else \ fprintf (stdout, "%s" format ANSI_RESET, ansi_format, ## args); \ + } \ } #define tio_warning_printf(format, args...) \ { \ + if (!option.mute) { \ if (print_tainted) \ putchar('\n'); \ if (option.color < 0) \ fprintf (stdout, "\r[%s] Warning: " format "\r\n", current_time(), ## args); \ else \ ansi_printf("[%s] Warning: " format, current_time(), ## args); \ + } \ } #define tio_printf(format, args...) \ { \ + if (!option.mute) { \ if (print_tainted) \ putchar('\n'); \ ansi_printf("[%s] " format, current_time(), ## args); \ print_tainted = false; \ + } \ } #define tio_printf_raw(format, args...) \ { \ + if (!option.mute) { \ if (print_tainted) \ putchar('\n'); \ ansi_printf_raw("[%s] " format, current_time(), ## args); \ print_tainted = false; \ + } \ } #ifdef DEBUG diff --git a/src/tty.c b/src/tty.c index d04679e..a1e4233 100644 --- a/src/tty.c +++ b/src/tty.c @@ -1015,6 +1015,9 @@ int tty_connect(void) int status; bool next_timestamp = false; char* now = NULL; + struct timeval tv; + struct timeval *tv_p = &tv; + bool ignore_stdin = false; /* Open tty device */ fd = open(option.tty_device, O_RDWR | O_NOCTTY | O_NONBLOCK); @@ -1108,12 +1111,28 @@ int tty_connect(void) { FD_ZERO(&rdfs); FD_SET(fd, &rdfs); - FD_SET(STDIN_FILENO, &rdfs); + if (!ignore_stdin) + { + FD_SET(STDIN_FILENO, &rdfs); + } maxfd = MAX(fd, STDIN_FILENO); maxfd = MAX(maxfd, socket_add_fds(&rdfs, true)); + /* Manage timeout */ + if ((option.response_wait) && (option.response_timeout != 0)) + { + // Set response timeout + tv_p->tv_sec = 0; + tv_p->tv_usec = option.response_timeout * 1000; + } + else + { + // No timeout + tv_p = NULL; + } + /* Block until input becomes available */ - status = select(maxfd + 1, &rdfs, NULL, NULL, NULL); + status = select(maxfd + 1, &rdfs, NULL, NULL, tv_p); if (status > 0) { bool forward = false; @@ -1181,6 +1200,15 @@ int tty_connect(void) { next_timestamp = true; } + + if (option.response_wait) + { + if ((input_char == '\r') || (input_char == '\n')) + { + tty_sync(fd); + exit(EXIT_SUCCESS); + } + } } } else if (FD_ISSET(STDIN_FILENO, &rdfs)) @@ -1195,8 +1223,21 @@ int tty_connect(void) else if (bytes_read == 0) { /* Reached EOF (when piping to stdin) */ - tty_sync(fd); - exit(EXIT_SUCCESS); + if (option.response_wait) + { + /* Stdin pipe closed but not blocking so stop listening + * to stdin in response mode. + * + * Note: select() really indicates not if data is ready + * but if file descriptor is non-blocking for I/O + * operation. */ + ignore_stdin = true; + } + else + { + tty_sync(fd); + exit(EXIT_SUCCESS); + } } /* Process input byte by byte */ @@ -1257,6 +1298,11 @@ int tty_connect(void) tio_error_printf("select() failed (%s)", strerror(errno)); exit(EXIT_FAILURE); } + else + { + // Timeout (only happens in response wait mode) + exit(EXIT_FAILURE); + } } return TIO_SUCCESS;