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 <ms>

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
This commit is contained in:
Martin Lund 2022-08-13 00:04:00 +02:00
parent a75e04b883
commit e837fd0303
9 changed files with 143 additions and 7 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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 */

View file

@ -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 <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 <ms> 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);

View file

@ -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;

View file

@ -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

View file

@ -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;