Rui Chen <rui@chenrui.dev>
Ralph Siemsen <ralphs@netwinder.org>
Victor Oliveira <rhapsodyv@gmail.com>
+Attila Veghelyi <aveghelyi@dension.com>
Thanks to everyone who has contributed to this project.
-=== tio v2.0 ===
+=== tio v2.2 ===
+
+
+
+Changes since tio v2.1:
+
+ * Add shell completion of sub-configuration names
+
+ Does not work with sub configuration names that contains one or more
+ white spaces.
+
+ * Beautify help
+
+ * Fix error message
+
+ * Simplify configfile implementation
+
+
+
+Changes since tio v2.0:
+
+ * Fix output line delay
+
+ Apply output line delay on lines ending with \n.
+
+ On most systems lines ends with \n or \r\n.
+
+ * Do not print timestamps in hex mode
+
+ * Improve input mechanism in hex mode
+
+ Print the 2 character hex code that you input in hex mode but then
+ delete it before sending. This way it is easier to keep track of what
+ you are inputting. It basically mimics the ctrl-shift-u input mechanism
+ that is used to input unicode.
+
+ * Add support for sending prefix character to serial device
+
+ Do so by inputting prefix key twice, e.g. input ctrl-t ctrl-t to send
+ ctrl-t character to serial device.
+
+ * Clean up indentation
+
+ * Update example tiorc
+
+Attila Veghelyi:
+
+ * Add bit reverse order feature
# tio - a simple serial device I/O tool
-[![CircleCI](https://circleci.com/gh/tio/tio/tree/master.svg?style=shield)](https://circleci.com/gh/tio/tio/tree/master)
-[![tio](https://snapcraft.io/tio/badge.svg)](https://snapcraft.io/tio)
-[![Packaging status](https://repology.org/badge/tiny-repos/tio.svg)](https://repology.org/project/tio/versions)
+[![](https://img.shields.io/circleci/build/gh/tio/tio?token=da7e7fd0d0ee99b9f986f8877dcdbe28f73d9e06)](https://circleci.com/gh/tio/tio/tree/master)
+[![](https://img.shields.io/github/v/release/tio/tio?sort=semver)](https://github.com/tio/tio/releases)
+[![](https://img.shields.io/tokei/lines/github/tio/tio)](https://github.com/tio/tio)
+[![](https://img.shields.io/repology/repositories/tio)](https://repology.org/project/tio/versions)
## 1. Introduction
* Sensible defaults
* Support for non-standard baud rates
* Support for RS-485 mode
+ * Support for mark and space parity
* List available serial devices by ID
* Show RX/TX statistics
* Toggle serial lines
* Pulse serial lines with configurable pulse duration
* Local echo support
- * Map characters (nl, cr-nl, bs, lowercase to uppercase, etc.)
+ * Remapping of characters (nl, cr-nl, bs, lowercase to uppercase, etc.)
* Line timestamps
* Support for delayed output per character
* Support for delayed output per line
* Redirect I/O to UNIX socket 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
+ * Bash completion on options, serial device names, and sub-configuration names
+ * Configurable text color
* Visual or audible alert on connect/disconnect
* Remapping of prefix key
* Man page documentation
The command-line interface is straightforward as reflected in the output from
'tio --help':
```
- Usage: tio [<options>] <tty-device|sub-config>
-
- Connect to tty device directly or via sub-configuration.
-
- Options:
- -b, --baudrate <bps> Baud rate (default: 115200)
- -d, --databits 5|6|7|8 Data bits (default: 8)
- -f, --flow hard|soft|none Flow control (default: none)
- -s, --stopbits 1|2 Stop bits (default: 1)
- -p, --parity odd|even|none|mark|space Parity (default: none)
- -o, --output-delay <ms> Output character delay (default: 0)
- -O, --output-line-delay <ms> Output line delay (default: 0)
- --line-pulse-duration <duration> Set line pulse duration
- -n, --no-autoconnect Disable automatic connect
- -e, --local-echo Enable local echo
- -t, --timestamp Enable line timestamp
- --timestamp-format <format> Set timestamp format (default: 24hour)
- -L, --list-devices List available serial devices
- -l, --log Enable log to file
- --log-file <filename> Set log filename
- --log-strip Strip control characters and escape sequences
- -m, --map <flags> Map characters
- -c, --color 0..255|bold|none|list Colorize tio text (default: bold)
- -S, --socket <socket> Redirect I/O to socket
- -x, --hexadecimal Enable hexadecimal mode
- -r, --response-wait Wait for line response then quit
- --response-timeout <ms> Response timeout (default: 100)
- --rs-485 Enable RS-485 mode
- --rs-485-config <config> Set RS-485 configuration
- --alert bell|blink|none Alert on connect/disconnect (default: none)
- -v, --version Display version
- -h, --help Display help
-
- Options and sub-configurations may be set via configuration file.
-
- See the man page for more details.
+ Usage: tio [<options>] <tty-device|sub-config>
+
+ Connect to tty device directly or via sub-configuration.
+
+ Options:
+ -b, --baudrate <bps> Baud rate (default: 115200)
+ -d, --databits 5|6|7|8 Data bits (default: 8)
+ -f, --flow hard|soft|none Flow control (default: none)
+ -s, --stopbits 1|2 Stop bits (default: 1)
+ -p, --parity odd|even|none|mark|space Parity (default: none)
+ -o, --output-delay <ms> Output character delay (default: 0)
+ -O, --output-line-delay <ms> Output line delay (default: 0)
+ --line-pulse-duration <duration> Set line pulse duration
+ -n, --no-autoconnect Disable automatic connect
+ -e, --local-echo Enable local echo
+ -t, --timestamp Enable line timestamp
+ --timestamp-format <format> Set timestamp format (default: 24hour)
+ -L, --list-devices List available serial devices
+ -l, --log Enable log to file
+ --log-file <filename> Set log filename
+ --log-strip Strip control characters and escape sequences
+ -m, --map <flags> Map characters
+ -c, --color 0..255|bold|none|list Colorize tio text (default: bold)
+ -S, --socket <socket> Redirect I/O to socket
+ -x, --hexadecimal Enable hexadecimal mode
+ -r, --response-wait Wait for line response then quit
+ --response-timeout <ms> Response timeout (default: 100)
+ --rs-485 Enable RS-485 mode
+ --rs-485-config <config> Set RS-485 configuration
+ --alert bell|blink|none Alert on connect/disconnect (default: none)
+ -v, --version Display version
+ -h, --help Display help
+
+ Options and sub-configurations may be set via configuration file.
+
+ See the man page for more details.
```
tio features full bash autocompletion.
+#### 3.1.1 Examples
Typical use is without options:
```
Using serial devices by ID ensures that tio automatically reconnects to the
correct serial device if it is disconnected and then reconnected.
+List available serial devices by ID:
+```
+$ tio --list-devices
+```
+Note: One can also use tio shell completion on /dev which will automatically
+list all available serial tty devices.
+
+Log to file with autogenerated filename:
+```
+$ tio --log /dev/ttyUSB0
+```
+
+Enable ISO8601 timestamps per line:
+```
+$ tio --timestamp --timestamp-format iso8601 /dev/ttyUSB0
+```
+
+Redirect I/O to IPv4 network socket on port 4242:
+```
+$ tio --socket inet:4242 /dev/ttyUSB0
+```
Inject data to the serial device:
```
```
[20:19:12.040] Key commands:
-[20:19:12.040] ctrl-t ? List available key commands
-[20:19:12.040] ctrl-t b Send break
-[20:19:12.040] ctrl-t c Show configuration
-[20:19:12.040] ctrl-t e Toggle local echo mode
-[20:19:12.040] ctrl-t g Toggle serial port line
-[20:19:12.040] ctrl-t h Toggle hexadecimal mode
-[20:19:12.040] ctrl-t l Clear screen
-[20:19:12.040] ctrl-t L Show line states
-[20:19:12.040] ctrl-t p Pulse serial port line
-[20:19:12.040] ctrl-t q Quit
-[20:19:12.041] ctrl-t s Show statistics
-[20:19:12.041] ctrl-t T Toggle line timestamp mode
-[20:19:12.041] ctrl-t U Toggle conversion to uppercase
-[20:19:12.041] ctrl-t v Show version
+[20:19:12.040] ctrl-t ? List available key commands
+[20:19:12.040] ctrl-t b Send break
+[20:19:12.040] ctrl-t c Show configuration
+[20:19:12.040] ctrl-t e Toggle local echo mode
+[20:19:12.040] ctrl-t g Toggle serial port line
+[20:19:12.040] ctrl-t h Toggle hexadecimal mode
+[20:19:12.040] ctrl-t l Clear screen
+[20:19:12.040] ctrl-t L Show line states
+[20:19:12.040] ctrl-t p Pulse serial port line
+[20:19:12.040] ctrl-t q Quit
+[20:19:12.041] ctrl-t s Show statistics
+[20:19:12.041] ctrl-t t Toggle line timestamp mode
+[20:19:12.041] ctrl-t U Toggle conversion to uppercase
+[20:19:12.041] ctrl-t v Show version
+[20:19:12.041] ctrl-t ctrl-t Send ctrl-t character
```
If needed, the prefix key (ctrl-t) can be remapped via configuration file.
$ tio usb12
```
-Another configuration file example is available [here](example/tiorc).
+Another more elaborate configuration file example is available [here](example/tiorc).
## 4. Installation
Install latest stable version:
```
- $ snap install tio
+$ snap install tio
```
### 4.3 Installation using brew (MacOS, Linux)
If you have [brew](http://brew.sh) installed:
```
- $ brew install tio
+$ brew install tio
```
### 4.4 Installation using MSYS2 (Windows)
If you have [MSYS2](https://www.msys2.org) installed:
```
- $ pacman -S tio
+$ pacman -S tio
```
### 4.5 Installation from source
Install steps:
```
- $ meson build
- $ meson compile -C build
- $ meson install -C build
+$ meson build
+$ meson compile -C build
+$ meson install -C build
```
See meson\_options.txt for tio specific build options.
timestamp = disable
log = disable
log-strip = disable
+local-echo = disable
color = bold
rs-485 = disable
+response-wait = disable
alert = none
# Sub-configuraions
Map NL to CR-NL on output
.IP "\fBOLTU"
Map lowercase characters to uppercase on output
+.IP "\fBMSB2LSB"
+Map MSB bit order to LSB on output
.P
If defining more than one flag, the flags must be comma separated.
.RE
Toggle conversion to uppercase on output
.IP "\fBctrl-t v"
Show version
+.IP "\fBctrl-t ctrl-t"
+Send ctrl-t character
.SH "HEXADECIMAL MODE"
.PP
-tio(1) User Commands tio(1)
+tio(1) User Commands tio(1)
NAME
tio - a simple serial device I/O tool
Disable automatic connect.
- By default tio automatically connects to the provided device if present. If the device is not present, it will wait for it to appear and then connect. If the connection is lost
- (eg. device disconnects), it will wait for the device to reappear and then reconnect.
+ By default tio automatically connects to the provided device if present. If the device is not present, it will wait for it to appear and then connect. If the connection is lost (eg. device disconnects), it will wait for the device to reappear and then reconnect.
However, if the --no-autoconnect option is provided, tio will exit if the device is not present or an established connection is lost.
OLTU Map lowercase characters to uppercase on output
+ MSB2LSB Map MSB bit order to LSB on output
+
If defining more than one flag, the flags must be comma separated.
-x, --hexadecimal
Redirect I/O to socket.
- Any input from clients connected to the socket is sent on the serial port as if entered at the terminal where tio is running (except that ctrl-t sequences are not recognized),
- and any input from the serial port is multiplexed to the terminal and all connected clients.
+ Any input from clients connected to the socket is sent on the serial port as if entered at the terminal where tio is running (except that ctrl-t sequences are not recognized), and any input from the serial port is multiplexed to the terminal and all connected
+ clients.
Sockets remain open while the serial port is disconnected, and writes will block.
ctrl-t v Show version
+ ctrl-t ctrl-t Send ctrl-t character
+
HEXADECIMAL MODE
In hexadecimal mode each incoming byte is printed out as a hexadecimal value.
AUTHOR
Created by Martin Lund <martin.lund@keep-it-simple.com>.
-tio 2.0 2022-09-11 tio(1)
+tio 2.2 2022-10-18 tio(1)
project('tio', 'c',
- version : '2.0',
+ version : '2.2',
license : [ 'GPL-2'],
meson_version : '>= 0.53.2',
default_options : [ 'warning_level=2', 'buildtype=release', 'c_std=gnu99' ]
)
# The tag date of the project_version(), update when the version bumps.
-version_date = '2022-09-11'
+version_date = '2022-10-18'
# Test for dynamic baudrate configuration interface
compiler = meson.get_compiler('c')
return 0
;;
-m | --map)
- COMPREPLY=( $(compgen -W "ICRNL IGNCR INLCR INLCRNL OCRNL ODELBS ONLCRNL" -- ${cur}) )
+ COMPREPLY=( $(compgen -W "ICRNL IGNCR INLCR INLCRNL OCRNL ODELBS ONLCRNL MSB2LSB" -- ${cur}) )
return 0
;;
-t | --timestamp)
;;
esac
+ sub_configs="`tio --complete-sub-configs`"
+
if [ -d /dev/serial/by-id ]; then
ttys=$(printf '%s\n' /dev/tty* /dev/serial/by-id/*)
else
ttys=$(printf '%s\n' /dev/tty*)
fi
- COMPREPLY=( $(compgen -W "${ttys}" -- ${cur}) )
+ COMPREPLY=( $(compgen -W "${ttys} ${sub_configs}" -- ${cur}) )
return 0
}
#include "timestamp.h"
#include "alert.h"
-static struct config_t *c;
+struct config_t
+{
+ const char *user;
+
+ char *path;
+ char *section_name;
+ char *match;
+
+ char *tty;
+ char *flow;
+ char *parity;
+ char *log_filename;
+ char *socket;
+ char *map;
+};
+
+static struct config_t c;
static int get_match(const char *input, const char *pattern, char **match)
{
UNUSED(user);
// If section matches current section being parsed
- if (!strcmp(section, c->section_name))
+ if (!strcmp(section, c.section_name))
{
// Set configuration parameter if found
if (!strcmp(name, "tty"))
{
- asprintf(&c->tty, value, c->match);
- option.tty_device = c->tty;
+ asprintf(&c.tty, value, c.match);
+ option.tty_device = c.tty;
}
else if (!strcmp(name, "baudrate"))
{
}
else if (!strcmp(name, "flow"))
{
- asprintf(&c->flow, "%s", value);
- option.flow = c->flow;
+ asprintf(&c.flow, "%s", value);
+ option.flow = c.flow;
}
else if (!strcmp(name, "stopbits"))
{
}
else if (!strcmp(name, "parity"))
{
- asprintf(&c->parity, "%s", value);
- option.parity = c->parity;
+ asprintf(&c.parity, "%s", value);
+ option.parity = c.parity;
}
else if (!strcmp(name, "output-delay"))
{
}
else if (!strcmp(name, "log-file"))
{
- asprintf(&c->log_filename, "%s", value);
- option.log_filename = c->log_filename;
+ asprintf(&c.log_filename, "%s", value);
+ option.log_filename = c.log_filename;
}
else if (!strcmp(name, "log-strip"))
{
}
else if (!strcmp(name, "map"))
{
- asprintf(&c->map, "%s", value);
- option.map = c->map;
+ asprintf(&c.map, "%s", value);
+ option.map = c.map;
}
else if (!strcmp(name, "color"))
{
}
else if (!strcmp(name, "socket"))
{
- asprintf(&c->socket, "%s", value);
- option.socket = c->socket;
+ asprintf(&c.socket, "%s", value);
+ option.socket = c.socket;
}
else if (!strcmp(name, "prefix-ctrl-key"))
{
if (strcmp(varname, "pattern"))
return 0;
- if (!strcmp(varval, c->user))
+ if (!strcmp(varval, c.user))
{
/* pattern matches as plain text */
- asprintf(&c->section_name, "%s", section);
+ asprintf(&c.section_name, "%s", section);
}
- else if (get_match(c->user, varval, &c->match) > 0)
+ else if (get_match(c.user, varval, &c.match) > 0)
{
/* pattern matches as regex */
- asprintf(&c->section_name, "%s", section);
+ asprintf(&c.section_name, "%s", section);
}
return 0;
UNUSED(varname);
UNUSED(varval);
- if (!strcmp(section, c->user))
+ if (!strcmp(section, c.user))
{
/* section name matches as plain text */
- asprintf(&c->section_name, "%s", section);
+ asprintf(&c.section_name, "%s", section);
+ }
+
+ return 0;
+}
+
+static int section_name_print_handler(void *user, const char *section, const char *varname,
+ const char *varval)
+{
+ UNUSED(user);
+ UNUSED(varname);
+ UNUSED(varval);
+
+ static char *section_previous = "";
+
+ if (strcmp(section, section_previous) != 0)
+ {
+ printf("%s ", section);
+ section_previous = strdup(section);
}
return 0;
static int resolve_config_file(void)
{
- asprintf(&c->path, "%s/tio/tiorc", getenv("XDG_CONFIG_HOME"));
- if (!access(c->path, F_OK))
+ asprintf(&c.path, "%s/tio/tiorc", getenv("XDG_CONFIG_HOME"));
+ if (!access(c.path, F_OK))
{
return 0;
}
- free(c->path);
+ free(c.path);
- asprintf(&c->path, "%s/.config/tio/tiorc", getenv("HOME"));
- if (!access(c->path, F_OK))
+ asprintf(&c.path, "%s/.config/tio/tiorc", getenv("HOME"));
+ if (!access(c.path, F_OK))
{
return 0;
}
- free(c->path);
+ free(c.path);
- asprintf(&c->path, "%s/.tiorc", getenv("HOME"));
- if (!access(c->path, F_OK))
+ asprintf(&c.path, "%s/.tiorc", getenv("HOME"));
+ if (!access(c.path, F_OK))
{
return 0;
}
- free(c->path);
+ free(c.path);
- c->path = NULL;
+ c.path = NULL;
return -EINVAL;
}
+void config_file_show_sub_configurations(void)
+{
+ memset(&c, 0, sizeof(struct config_t));
+
+ // Find config file
+ if (resolve_config_file() != 0)
+ {
+ // None found - stop parsing
+ return;
+ }
+
+ ini_parse(c.path, section_name_print_handler, NULL);
+}
+
void config_file_parse(void)
{
int ret;
- c = malloc(sizeof(struct config_t));
- if (!c)
- {
- tio_error_printf("Insufficient memory allocation");
- exit(EXIT_FAILURE);
- }
- memset(c, 0, sizeof(struct config_t));
+ memset(&c, 0, sizeof(struct config_t));
// Find config file
if (resolve_config_file() != 0)
}
// Set user input which may be tty device or sub config
- c->user = option.tty_device;
+ c.user = option.tty_device;
- if (!c->user)
+ if (!c.user)
{
return;
}
// Parse default (unnamed) settings
- asprintf(&c->section_name, "%s", "");
- ret = ini_parse(c->path, data_handler, NULL);
+ asprintf(&c.section_name, "%s", "");
+ ret = ini_parse(c.path, data_handler, NULL);
if (ret < 0)
{
tio_error_printf("Unable to parse configuration file (%d)", ret);
exit(EXIT_FAILURE);
}
- free(c->section_name);
- c->section_name = NULL;
+ free(c.section_name);
+ c.section_name = NULL;
// Find matching section
- ret = ini_parse(c->path, section_pattern_search_handler, NULL);
- if (!c->section_name)
+ ret = ini_parse(c.path, section_pattern_search_handler, NULL);
+ if (!c.section_name)
{
- ret = ini_parse(c->path, section_name_search_handler, NULL);
- if (!c->section_name)
+ ret = ini_parse(c.path, section_name_search_handler, NULL);
+ if (!c.section_name)
{
tio_debug_printf("Unable to match user input to configuration section (%d)", ret);
return;
}
// Parse settings of found section (sub config)
- ret = ini_parse(c->path, data_handler, NULL);
+ ret = ini_parse(c.path, data_handler, NULL);
if (ret < 0)
{
tio_error_printf("Unable to parse configuration file (%d)", ret);
void config_exit(void)
{
- free(c->tty);
- free(c->flow);
- free(c->parity);
- free(c->log_filename);
- free(c->map);
-
- free(c->match);
- free(c->section_name);
- free(c->path);
-
- free(c);
+ free(c.tty);
+ free(c.flow);
+ free(c.parity);
+ free(c.log_filename);
+ free(c.map);
+
+ free(c.match);
+ free(c.section_name);
+ free(c.path);
}
void config_file_print(void)
{
- if (c->path != NULL)
+ if (c.path != NULL)
{
- tio_printf(" Path: %s", c->path);
- if (c->section_name != NULL)
+ tio_printf(" Path: %s", c.path);
+ if (c.section_name != NULL)
{
- tio_printf(" Active sub-configuration: %s", c->section_name);
+ tio_printf(" Active sub-configuration: %s", c.section_name);
}
}
}
#pragma once
-struct config_t
-{
- const char *user;
-
- char *path;
- char *section_name;
- char *match;
-
- char *tty;
- char *flow;
- char *parity;
- char *log_filename;
- char *socket;
- char *map;
-};
-
void config_file_print(void);
void config_file_parse(void);
void config_exit(void);
+void config_file_show_sub_configurations(void);
void error_enter_session_mode(void)
{
- in_session = true;
+ in_session = true;
}
void error_printf_(const char *format, ...)
{
- va_list args;
- char *line;
+ va_list args;
+ char *line;
- va_start(args, format);
- vasprintf(&line, format, args);
+ va_start(args, format);
+ vasprintf(&line, format, args);
- if (in_session)
- {
- if (print_tainted)
+ if (in_session)
{
- putchar('\n');
+ if (print_tainted)
+ {
+ putchar('\n');
+ }
+ ansi_error_printf("[%s] %s", timestamp_current_time(), line);
+ }
+ else
+ {
+ fprintf(stderr, "%s\n", line);
}
- ansi_error_printf("[%s] %s", timestamp_current_time(), line);
- }
- else
- {
- fprintf(stderr, "%s\n", line);
- }
- va_end(args);
+ va_end(args);
- print_tainted = false;
- free(line);
+ print_tainted = false;
+ free(line);
}
void tio_error_printf(const char *format, ...)
{
- va_list args;
+ va_list args;
- va_start(args, format);
- vsnprintf(error[0], 1000, format, args);
- va_end(args);
+ va_start(args, format);
+ vsnprintf(error[0], 1000, format, args);
+ va_end(args);
}
void tio_error_printf_silent(const char *format, ...)
{
- va_list args;
+ va_list args;
- va_start(args, format);
- vsnprintf(error[1], 1000, format, args);
- va_end(args);
+ va_start(args, format);
+ vsnprintf(error[1], 1000, format, args);
+ va_end(args);
}
void error_exit(void)
{
- if (error[0][0] != 0)
- {
- /* Print error */
- error_printf_("Error: %s", error[0]);
- }
- else if ((error[1][0] != 0) && (option.no_autoconnect))
- {
- /* Print silent error */
- error_printf_("Error: %s", error[1]);
- }
+ if (error[0][0] != 0)
+ {
+ /* Print error */
+ error_printf_("Error: %s", error[0]);
+ }
+ else if ((error[1][0] != 0) && (option.no_autoconnect))
+ {
+ /* Print silent error */
+ error_printf_("Error: %s", error[1]);
+ }
}
/* Parse command-line options (1st pass) */
options_parse(argc, argv);
+ if (option.complete_sub_configs)
+ {
+ config_file_show_sub_configurations();
+ return status;
+ }
+
/* Parse configuration file */
config_file_parse();
OPT_RS485,
OPT_RS485_CONFIG,
OPT_ALERT,
+ OPT_COMPLETE_SUB_CONFIGS,
};
/* Default options */
.rs485_delay_rts_before_send = -1,
.rs485_delay_rts_after_send = -1,
.alert = ALERT_NONE,
+ .complete_sub_configs = false,
};
void print_help(char *argv[])
{
- printf("Usage: %s [<options>] <tty-device|sub-config>\n", argv[0]);
+ UNUSED(argv);
+
+ printf("Usage: tio [<options>] <tty-device|sub-config>\n");
printf("\n");
printf("Connect to tty device directly or via sub-configuration.\n");
printf("\n");
{
static struct option long_options[] =
{
- {"baudrate", required_argument, 0, 'b' },
- {"databits", required_argument, 0, 'd' },
- {"flow", required_argument, 0, 'f' },
- {"stopbits", required_argument, 0, 's' },
- {"parity", required_argument, 0, 'p' },
- {"output-delay", required_argument, 0, 'o' },
- {"output-line-delay" , required_argument, 0, 'O' },
- {"line-pulse-duration", required_argument, 0, OPT_LINE_PULSE_DURATION},
- {"no-autoconnect", no_argument, 0, 'n' },
- {"local-echo", no_argument, 0, 'e' },
- {"timestamp", no_argument, 0, 't' },
- {"timestamp-format", required_argument, 0, OPT_TIMESTAMP_FORMAT },
- {"list-devices", no_argument, 0, 'L' },
- {"log", no_argument, 0, 'l' },
- {"log-file", required_argument, 0, OPT_LOG_FILE },
- {"log-strip", no_argument, 0, OPT_LOG_STRIP },
- {"socket", required_argument, 0, 'S' },
- {"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 },
- {"rs-485", no_argument, 0, OPT_RS485 },
- {"rs-485-config", required_argument, 0, OPT_RS485_CONFIG },
- {"alert", required_argument, 0, OPT_ALERT },
- {"version", no_argument, 0, 'v' },
- {"help", no_argument, 0, 'h' },
- {0, 0, 0, 0 }
+ {"baudrate", required_argument, 0, 'b' },
+ {"databits", required_argument, 0, 'd' },
+ {"flow", required_argument, 0, 'f' },
+ {"stopbits", required_argument, 0, 's' },
+ {"parity", required_argument, 0, 'p' },
+ {"output-delay", required_argument, 0, 'o' },
+ {"output-line-delay" , required_argument, 0, 'O' },
+ {"line-pulse-duration", required_argument, 0, OPT_LINE_PULSE_DURATION },
+ {"no-autoconnect", no_argument, 0, 'n' },
+ {"local-echo", no_argument, 0, 'e' },
+ {"timestamp", no_argument, 0, 't' },
+ {"timestamp-format", required_argument, 0, OPT_TIMESTAMP_FORMAT },
+ {"list-devices", no_argument, 0, 'L' },
+ {"log", no_argument, 0, 'l' },
+ {"log-file", required_argument, 0, OPT_LOG_FILE },
+ {"log-strip", no_argument, 0, OPT_LOG_STRIP },
+ {"socket", required_argument, 0, 'S' },
+ {"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 },
+ {"rs-485", no_argument, 0, OPT_RS485 },
+ {"rs-485-config", required_argument, 0, OPT_RS485_CONFIG },
+ {"alert", required_argument, 0, OPT_ALERT },
+ {"version", no_argument, 0, 'v' },
+ {"help", no_argument, 0, 'h' },
+ {"complete-sub-configs", no_argument, 0, OPT_COMPLETE_SUB_CONFIGS},
+ {0, 0, 0, 0 }
};
/* getopt_long stores the option index here */
exit(EXIT_SUCCESS);
break;
+ case OPT_COMPLETE_SUB_CONFIGS:
+ option.complete_sub_configs = true;
+ break;
+
case '?':
/* getopt_long already printed an error message */
exit(EXIT_FAILURE);
else if (optind < argc)
option.tty_device = argv[optind++];
+ if (option.complete_sub_configs)
+ {
+ return;
+ }
+
if (strlen(option.tty_device) == 0)
{
tio_error_printf("Missing tty device or sub-configuration name");
{
fprintf(stderr, "Error: Unknown argument ");
while (optind < argc)
- printf("%s ", argv[optind++]);
+ {
+ fprintf(stderr, "%s ", argv[optind++]);
+ }
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
int32_t rs485_delay_rts_before_send;
int32_t rs485_delay_rts_after_send;
enum alert_t alert;
+ bool complete_sub_configs;
};
extern struct option_t option;
void print_hex(char c)
{
- printf("%02x ", (unsigned char) c);
+ printf("%02x ", (unsigned char) c);
}
void print_normal(char c)
{
- putchar(c);
+ putchar(c);
}
void print_init_ansi_formatting()
{
- if (option.color == 256)
- {
- // Set bold text with no color changes
- sprintf(ansi_format, "\e[1m");
- }
- else
- {
- // Set bold text with user defined ANSI color
- sprintf(ansi_format, "\e[1;38;5;%dm", option.color);
- }
+ if (option.color == 256)
+ {
+ // Set bold text with no color changes
+ sprintf(ansi_format, "\e[1m");
+ }
+ else
+ {
+ // Set bold text with user defined ANSI color
+ sprintf(ansi_format, "\e[1;38;5;%dm", option.color);
+ }
}
void tio_printf_array(const char *array)
{
- int i = 0, j = 0;
+ int i = 0, j = 0;
- tio_printf("");
+ tio_printf("");
- while (array[i])
- {
- if (array[i] == '\n')
+ while (array[i])
{
- const char *line = &array[j];
- char *line_copy = strndup(line, i-j);
- tio_printf_raw("%s\r", line_copy);
- free(line_copy);
- j = i;
+ if (array[i] == '\n')
+ {
+ const char *line = &array[j];
+ char *line_copy = strndup(line, i-j);
+ tio_printf_raw("%s\r", line_copy);
+ free(line_copy);
+ j = i;
+ }
+ i++;
}
- i++;
- }
- tio_printf("");
+ tio_printf("");
}
#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); \
- } \
+ 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); \
- } \
+ 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); \
- } \
+ 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", timestamp_current_time(), ## args); \
- else \
- ansi_printf("[%s] Warning: " format, timestamp_current_time(), ## args); \
- } \
+ if (!option.mute) \
+ { \
+ if (print_tainted) \
+ putchar('\n'); \
+ if (option.color < 0) \
+ fprintf (stdout, "\r[%s] Warning: " format "\r\n", timestamp_current_time(), ## args); \
+ else \
+ ansi_printf("[%s] Warning: " format, timestamp_current_time(), ## args); \
+ } \
}
#define tio_printf(format, args...) \
{ \
- if (!option.mute) { \
- if (print_tainted) \
- putchar('\n'); \
- ansi_printf("[%s] " format, timestamp_current_time(), ## args); \
- print_tainted = false; \
- } \
+ if (!option.mute) \
+ { \
+ if (print_tainted) \
+ putchar('\n'); \
+ ansi_printf("[%s] " format, timestamp_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, timestamp_current_time(), ## args); \
- print_tainted = false; \
- } \
+ if (!option.mute) \
+ { \
+ if (print_tainted) \
+ putchar('\n'); \
+ ansi_printf_raw("[%s] " format, timestamp_current_time(), ## args); \
+ print_tainted = false; \
+ } \
}
#ifdef DEBUG
#define tio_debug_printf(format, args...) \
- fprintf (stdout, "[debug] " format, ## args)
+ fprintf(stdout, "[debug] " format, ## args)
#define tio_debug_printf_raw(format, args...) \
- fprintf (stdout, "" format, ## args)
+ fprintf(stdout, "" format, ## args)
#else
#define tio_debug_printf(format, args...)
#define tio_debug_printf_raw(format, args...)
#define KEY_H 0x68
#define KEY_L 0x6C
#define KEY_SHIFT_L 0x4C
+#define KEY_M 0x6D
#define KEY_P 0x70
#define KEY_Q 0x71
#define KEY_S 0x73
static bool map_o_nl_crnl = false;
static bool map_o_del_bs = false;
static bool map_o_ltu = false;
+static bool map_o_msblsb = false;
static char hex_chars[2];
static unsigned char hex_char_index = 0;
static char tty_buffer[BUFSIZ*2];
}
bytes_written += retval;
- if (option.output_line_delay && *(unsigned char*)buffer == '\r')
+ if (option.output_line_delay && *(unsigned char*)buffer == '\n')
{
delay(option.output_line_delay);
}
{
hex_chars[hex_char_index++] = c;
+ printf("%c", c);
+
if (hex_char_index == 2)
{
+ usleep(100*1000);
+ printf("\b \b");
+ printf("\b \b");
+
unsigned char hex_value = char_to_nibble(hex_chars[0]) << 4 | (char_to_nibble(hex_chars[1]) & 0x0F);
hex_char_index = 0;
{
case KEY_QUESTION:
tio_printf("Key commands:");
- tio_printf(" ctrl-%c ? List available key commands", option.prefix_key);
- tio_printf(" ctrl-%c b Send break", option.prefix_key);
- tio_printf(" ctrl-%c c Show configuration", option.prefix_key);
- tio_printf(" ctrl-%c e Toggle local echo mode", option.prefix_key);
- tio_printf(" ctrl-%c g Toggle serial port line", option.prefix_key);
- tio_printf(" ctrl-%c h Toggle hexadecimal mode", option.prefix_key);
- tio_printf(" ctrl-%c l Clear screen", option.prefix_key);
- tio_printf(" ctrl-%c L Show line states", option.prefix_key);
- tio_printf(" ctrl-%c p Pulse serial port line", option.prefix_key);
- tio_printf(" ctrl-%c q Quit", option.prefix_key);
- tio_printf(" ctrl-%c s Show statistics", option.prefix_key);
- tio_printf(" ctrl-%c t Toggle line timestamp mode", option.prefix_key);
- tio_printf(" ctrl-%c U Toggle conversion to uppercase on output", option.prefix_key);
- tio_printf(" ctrl-%c v Show version", option.prefix_key);
+ tio_printf(" ctrl-%c ? List available key commands", option.prefix_key);
+ tio_printf(" ctrl-%c b Send break", option.prefix_key);
+ tio_printf(" ctrl-%c c Show configuration", option.prefix_key);
+ tio_printf(" ctrl-%c e Toggle local echo mode", option.prefix_key);
+ tio_printf(" ctrl-%c g Toggle serial port line", option.prefix_key);
+ tio_printf(" ctrl-%c h Toggle hexadecimal mode", option.prefix_key);
+ tio_printf(" ctrl-%c l Clear screen", option.prefix_key);
+ tio_printf(" ctrl-%c L Show line states", option.prefix_key);
+ tio_printf(" ctrl-%c m Toggle MSB to LSB bit order", option.prefix_key);
+ tio_printf(" ctrl-%c p Pulse serial port line", option.prefix_key);
+ tio_printf(" ctrl-%c q Quit", option.prefix_key);
+ tio_printf(" ctrl-%c s Show statistics", option.prefix_key);
+ tio_printf(" ctrl-%c t Toggle line timestamp mode", option.prefix_key);
+ tio_printf(" ctrl-%c U Toggle conversion to uppercase on output", option.prefix_key);
+ tio_printf(" ctrl-%c v Show version", option.prefix_key);
+ tio_printf(" ctrl-%c ctrl-%c Send ctrl-%c character", option.prefix_key, option.prefix_key, option.prefix_key);
break;
case KEY_SHIFT_L:
printf("\033c");
break;
+ case KEY_M:
+ /* Toggle bit order */
+ if (!map_o_msblsb)
+ {
+ map_o_msblsb = true;
+ tio_printf("Switched to reverse bit order");
+ }
+ else
+ {
+ map_o_msblsb = false;
+ tio_printf("Switched to normal bit order");
+ }
+ break;
+
case KEY_Q:
/* Exit upon ctrl-t q sequence */
exit(EXIT_SUCCESS);
break;
default:
+ /* Handle double prefix key input case */
+ if (input_char == option.prefix_code)
+ {
+ static int count = 0;
+ if (count++ == 1)
+ {
+ // Do not forward prefix characters excessively
+ count = 0;
+ break;
+ }
+
+ /* Forward prefix character to tty */
+ *output_char = option.prefix_code;
+ *forward = true;
+ break;
+ }
+
/* Ignore unknown ctrl-t escaped keys */
break;
}
{
map_o_ltu = true;
}
+ else if (strcmp(token, "MSB2LSB") == 0)
+ {
+ map_o_msblsb = true;
+ }
else
{
printf("Error: Unknown mapping flag %s\n", token);
input_char = input_buffer[i];
/* Print timestamp on new line if enabled */
- if (next_timestamp && input_char != '\n' && input_char != '\r')
+ if ((next_timestamp && input_char != '\n' && input_char != '\r') && !option.hex_mode)
{
now = timestamp_current_time();
if (now)
}
}
+ /* Convert MSB to LSB bit order */
+ if (map_o_msblsb)
+ {
+ char ch = input_char;
+ input_char = 0;
+ for (int j = 0; j < 8; ++j)
+ {
+ input_char |= ((1 << j) & ch) ? (1 << (7 - j)) : 0;
+ }
+ }
+
/* Map input character */
- if ((input_char == '\n') && (map_i_nl_crnl))
+ if ((input_char == '\n') && (map_i_nl_crnl) && (!map_o_msblsb))
{
print('\r');
print('\n');