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;