--- /dev/null
+custom: ["https://www.paypal.me/lundmar"]
Yin Fengwei <fengwei.yin@intel.com>
Liam Beguin <liambeguin@gmail.com>
Peter Collingbourne <pcc@google.com>
+g0mb4 <gomba007@gmail.com>
Thanks to everyone who has contributed to this project.
-=== tio v1.38 ===
+=== tio v1.40 ===
+
+
+
+Changes since tio v1.39:
+
+ * Add config support for log-strip
+
+ * Add config support for hex-mode
+
+ * Rename --hex to --hex-mode
+
+ * Fix completion for -e, --local-echo
+
+ * Ignore newlines in hex output
+
+ * Fix newline in warning_printf()
+
+ * Fix ansi_printf_raw() in no color mode
+
+ * Enter non-interactive mode when piping to tio
+
+ Add support for a non interactive mode which allows other application to
+ pipe data to tio which then forwards the data to the connected serial
+ device.
+
+ Non ineractive means that tio does not react to interactive key commands
+ in the incoming stream. This allows users to pipe binary data directly
+ to the connected serial device.
+
+ Example use:
+
+ $ cat commands.txt | tio /dev/ttyUSB0
+
+ * Also strip backspace from log
+
+ To make log strip feature consistent so that we remove all unprintable
+ control characters and escape sequences.
+
+ * Socket code cleanup
+
+ * Cleanup man page
+
+ * Rename --log-filename to --log-file
+
+Yin Fengwei:
+
+ * Allow strip escape sequence characters from log file
+
+ The log without escape key stripped is like:
+
+ ^M[12:47:17] ACRN:\>
+ ^M[12:47:17] ACRN:\>lasdfjklsdjf
+ ^M
+ ^M[12:47:18] Error: Invalid command.
+ ^M[12:47:19] ACRN:\>
+ ^M[12:47:26] ACRN:\>
+ ^M[12:47:26] ACRN:\>sdafkljsdkaljfklsadjflksdjafjsda^H ^H^H...
+ ^M
+ ^M[12:47:31] Error: Invalid command.
+
+ After strip escape key, the log is like:
+
+ [12:49:18] ACRN:\>
+ [12:49:19] ACRN:\>
+ [12:49:19] ACRN:\>ls
+
+ [12:49:19] Error: Invalid command.
+ [12:49:19] ACRN:\>
+ [12:49:19] ACRN:\>dfaslhj
+
+ [12:49:24] Error: Invalid command.
+
+ Beside escape key, it also handle backspace key as well.
+
+
+
+Changes since tio v1.38:
+
+ * Improve key command response for local echo and timestamp
+
+ * Fix invalid hex character error message
+
+ * Make sure only matched config section is parsed
+
+ * Add support for "disable" keyword in config file
+
+ * Unify error message formating
+
+ * Cleanup list devices code
+
+ * Fix command-line tty-device|config parsing
+
+ Allow user to add options on both sides of the provided config argument.
+
+ For example:
+
+ $ tio -b 9600 am64-evm -e
+
+ Before, tio only allowed adding arguments after the config argument.
+
+ Implemented as simple as possible by introducing two stage option parsing.
+
+ * Update bash completion
+
+ * Add support for IPv4 and IPv6 network sockets
+
+ Add support for IPv4 and IPv6 network sockets via socket syntax
+ "inet:<port>" and "inet6:<port>" respectively.
+
+ For example, to listen and redirect serial device I/O to a host bound
+ IPv4 socket simply do:
+
+ $ tio /dev/ttyUSB0 --socket inet:4444
+
+ To connect do e.g.:
+
+ $ nc 127.0.0.1 4444
+
+ Likewise, for IPv6 do:
+
+ $ tio /dev/ttyUSB0 --socket inet6:4444
+
+ To connect do e.g.:
+
+ $ nc ::1 4444
+
+ If port is 0 or no port is provided default port 3333 is used.
+
+ * Fix tio deleting unix socket file
+
+ If tio has a unix file socket open, a second tio instance of tio may
+ delete the socket file. This change fixes so that it will not be deleted
+ and tio will instead error and complain about conflicting socket file.
+
+ * Rework color option
+
+ Rework the color option to support setting ANSI color code values
+ ranging from 0..255 or "none" for no color or "list" to print a list of
+ available ANSI colors codes.
+
+ Also, disables color when piping.
+
+ * Remove print of hex mode status at startup
+
+ * Remove newline option in hex mode
+
+ * Fix configfile memory leaks
+
+ * Remove command-line option inconsistencies
+
+ Optional arguments, as parsed by the getopt_long mechanism, are
+ inherently inconsistent with how you define required arguments.
+
+ To avoid confusion we decide to avoid this inconsistency by replacing
+ optional options with additional options with required argmuments.
+
+ * Replace '1' with 'enable' in config files
+
+ * Convert errors to warnings
+
+g0mb4:
+
+ * Extended hexadecimal mode.
+
+ While in hex mode (ctrl-t h) you can output hexadecimal values.
+ E.g.: to send 0x0A you have to type 0A (always 2 characters).
+
+ Added option -x, --hex to start in hexadecimal mode.
+
+ Added option --newline-in-hex to interpret newline characters in hex mode.
+ This is disabled by default, because, in my opinion, hex stream is
+ fundamentally different from text, so a "new line" is meaningless in this
+ context.
-# tio - a simple serial terminal I/O tool
+# 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)
## 1. Introduction
-tio is a simple serial terminal tool which features a straightforward
-command-line interface to easily connect to TTY devices for basic I/O
-operations.
+tio is a simple serial device tool which features a straightforward
+command-line and configuration file interface to easily connect to serial TTY
+devices for basic I/O operations.
<p align="center">
<img src="images/tio-demo.gif">
### 1.1 Motivation
-To make a simpler serial terminal tool for talking with TTY devices with less
+To make a simpler serial device tool for talking with TTY devices with less
focus on classic terminal/modem features and more focus on the needs of
embedded developers and hackers.
+tio was originally created to replace
+[screen](https://www.gnu.org/software/screen) for connecting to serial devices
+when used in combination with [tmux](https://tmux.github.io).
+
## 2. Usage
+### 2.1 Command-line
+
The command-line interface is straightforward as reflected in the output from
'tio --help':
```
Usage: tio [<options>] <tty-device|config>
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 Parity (default: none)
- -o, --output-delay <ms> Output delay (default: 0)
- -n, --no-autoconnect Disable automatic connect
- -e, --local-echo Enable local echo
- -t, --timestamp[=<format>] Enable timestamp (default: 24hour)
- -L, --list-devices List available serial devices
- -l, --log <filename> Log to file
- -m, --map <flags> Map special characters
- -c, --color <0..255> Colorize tio text
- -S, --socket <socket> Listen on socket
- -v, --version Display version
- -h, --help Display help
+ -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 Parity (default: none)
+ -o, --output-delay <ms> Character output delay (default: 0)
+ -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 logging to file
+ --log-file <filename> Set log filename
+ --log-strip Strip control characters and escape sequences
+ -m, --map <flags> Map special characters
+ -c, --color 0..255|none|list Colorize tio text (default: 15)
+ -S, --socket <socket> Redirect I/O to socket
+ -x, --hex Enable hexadecimal mode
+ -v, --version Display version
+ -h, --help Display help
Options may be set via configuration file.
See the man page for more details.
```
-The only option which requires a bit of elaboration is perhaps the
-`--no-autoconnect` option.
+By default tio automatically connects to the provided TTY 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 is unplugged), 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.
+
+tio features full bash autocompletion.
+
+### 2.2 Key commands
+
+Various in session key commands are supported. When tio is started, press
+ctrl-t ? to list the available key commands.
+
+```
+[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 d Toggle DTR line
+[20:19:12.040] ctrl-t e Toggle local echo mode
+[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 q Quit
+[20:19:12.040] ctrl-t r Toggle RTS line
+[20:19:12.041] ctrl-t s Show statistics
+[20:19:12.041] ctrl-t t Send ctrl-t key code
+[20:19:12.041] ctrl-t T Toggle line timestamp mode
+[20:19:12.041] ctrl-t v Show version
+```
+
+### 2.3 Configuration file
+
+Options can be set via the configuration file first found in any of the
+following locations in the order listed:
+ - $XDG_CONFIG_HOME/tio/tiorc
+ - $HOME/.config/tio/tiorc
+ - $HOME/.tiorc
-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 is unplugged), 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.
+The configuration file supports sub-configurations using named sections which can
+be activated via the command-line by name or pattern.
-Tio supports various in session key commands. Press ctrl-t ? to list the
-available key commands.
+Example configuration file:
-Tio also features full bash autocompletion support and configuration via ~/.tiorc.
+```
+# Defaults
+baudrate = 115200
+databits = 8
+parity = none
+stopbits = 1
+color = 10
+
+[ftdi]
+tty = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
+baudrate = 9600
+no-autoconnect = enable
+log = enable
+log-file = ftdi.log
+color = 12
+
+[usb devices]
+pattern = usb([0-9]*)
+tty = /dev/ttyUSB%s
+color = 13
+```
-See the tio man page for more details.
+To use a specific sub-configuration by name simply start tio like so:
+```
+$ tio ftdi
+```
+Or by pattern match:
+```
+$ tio usb12
+```
## 3. Installation
### 3.1 Installation using package manager
-tio comes prepackaged for various GNU/Linux distributions. Please consult your package manager tool to find and install tio.
+Packages for various GNU/Linux distributions are available. Please consult your
+package manager tool to find and install tio.
### 3.2 Installation using snap
## 7. License
-Tio is GPLv2+. See LICENSE file for more details.
+tio is GPLv2+. See LICENSE file for more details.
## 8. Authors
--- /dev/null
+
+ * Improve error/warning messaging when parsing configuration file
+
+ * Add support for piping to tio
.TH "tio" "1" "@version_date@" "tio @version@" "User Commands"
.SH "NAME"
-tio \- a simple serial terminal I/O tool
+tio \- a simple serial device I/O tool
.SH "SYNOPSIS"
.PP
.SH "DESCRIPTION"
.PP
.B tio
-is a simple serial terminal tool which features a straightforward command-line
-interface to easily connect to serial TTY devices for basic I/O operations.
+is a simple serial device tool which features a straightforward command-line
+and configuration file interface to easily connect to serial TTY devices for
+basic I/O operations.
.SH "OPTIONS"
Enable local echo.
.TP
-.BR \-t ", " \-\-timestamp[=\fI<format>\fR\fB]
+.BR \-t ", " \-\-timestamp
-Enable timestamp. Optionally you can specify any of the following timestamp formats:
+Enable line timestamp.
+
+.TP
+.BR " \-\-timestamp-format \fI<format>
+
+Set timestamp format to any of the following timestamp formats:
.RS
.TP 16n
.IP "\fB24hour"
List available serial devices.
.TP
-.BR \-l ", " \-\-log[=\fI<filename>\fR\fB]
+.BR \-l ", " \-\-log
-Log to file. If no filename is provided the filename will be automatically generated.
+Enable logging to file. If no filename is provided the filename will be automatically generated.
+
+.TP
+.BR " \-\-log-file \fI<filename>
+
+Set log filename.
+
+.TP
+.BR " \-\-log-strip
+
+Strip control characters and escape sequences from log.
.TP
.BR \-m ", " "\-\-map " \fI<flags>
.RE
.TP
-.BR \-c ", " "\-\-color " \fI<code>
+.BR \-x ", " \-\-hex\-mode
+
+Enable hexadecimal mode.
+
+.TP
+.BR \-c ", " "\-\-color " \fI0..255|none|list
+
+Colorize tio text using ANSI color code value ranging from 0 to 255 or use "none" for no color.
-Colorize tio text using ANSI color code ranging from 0 to 255.
+Use "list" to print a list of available ANSI color codes.
-If color code is negative a list of available ANSI colors will be printed.
+Default value is 15.
.TP
.BR \-S ", " "\-\-socket \fI<socket>\fR\fB
-Listen on 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
+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
.B 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.
-Two socket types are supported using different prefixes in the socket field:
-
-unix:<filename> - Unix Domain Socket (file)
-
-inet:<IP>:<port> - Internet Socket (network) (NOT YET SUPPORTED)
+Various socket types are supported using the following prefixes in the socket field:
+.RS
+.TP 20n
+.IP "\fBunix:<filename>"
+Unix Domain Socket (file)
+.IP "\fBinet:<port>"
+Internet Socket (network)
+.IP "\fBinet6:<port>"
+Internet IPv6 Socket (network)
+.P
+If port is 0 or no port is provided default port 3333 is used.
+.P
At present there is a hardcoded limit of 16 clients connected at one time.
+.RE
.TP
.BR \-v ", " \-\-version
.IP "\fBctrl-t v"
Show version
+.SH "HEXADECIMAL MODE"
+.TP
+In hexadecimal mode each incoming byte is printed out as a hexadecimal value.
+.TP
+Bytes can be sent in this mode by typing the \fBtwo-character hexadecimal\fR representation of the value, e.g.: to send \fI0xA\fR you must type \fI0a\fR or \fI0A\fR.
+
.SH "CONFIGURATION"
.PP
.TP 16n
.TP
The following configuration file options are available:
+.TP 20n
.IP "\fBpattern"
-pattern matching user input. This pattern can be an extended regular expression with a single group.
+Pattern matching user input. This pattern can be an extended regular expression with a single group.
.IP "\fBtty"
tty device to open. If tty contains a "%s" it will be substituted with the first group match.
.IP "\fBbaudrate"
.IP "\fBno-autoconnect"
Disable automatic connect
.IP "\fBlog"
-Log to file
+Enable logging to file
+.IP "\fBlog-file"
+Set log filename
+.IP "\fBlog-strip"
+Enable strip of control and escape sequences from log
.IP "\fBlocal-echo"
Enable local echo
.IP "\fBtimestamp"
-Prefix each new line with a timestamp
-.IP "\fBlog-filename"
-Set log filename
+Enable line timestamp
+.IP "\fBtimestamp-format"
+Set timestamp format
.IP "\fBmap"
Map special characters on input or output
.IP "\fBcolor"
-Colorize tio text using ANSI color code ranging from 0 to 255.
+Colorize tio text using ANSI color code ranging from 0 to 255
+.IP "\fBhex-mode"
+Enable hexadecimal mode
.IP "\fBsocket"
-Set socket path (must include
-.BR unix: ).
+Set socket to redirect I/O to
.SH "CONFIGURATION EXAMPLES"
.TP
With this config section defined in the configuration file the following commands would be equivalent:
-tio ftdi
+$ tio ftdi
-tio -b 115200 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
+$ tio -b 115200 -c 11 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
.TP
A config section can also be matched by its pattern which supports regular expressions:
.TP
Making the following commands equivalent:
-tio usb12
+$ tio usb12
-tio -b 115200 /dev/ttyUSB12
+$ tio -b 115200 /dev/ttyUSB12
.SH "EXAMPLES"
.TP
Typical use is without options. For example:
-tio /dev/ttyUSB0
+$ tio /dev/ttyUSB0
.TP
Which corresponds to the commonly used options:
-tio \-b 115200 \-d 8 \-f none \-s 1 \-p none /dev/ttyUSB0
+$ tio \-b 115200 \-d 8 \-f none \-s 1 \-p none /dev/ttyUSB0
.TP
It is recommended to connect serial tty devices by ID. For example:
-tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
+$ tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
.PP
Using serial devices by ID ensures that tio automatically reconnects to the
correct serial device if the device is disconnected and then reconnected.
+.TP
+To listen and redirect serial device I/O to network socket on port 4444 do:
+
+$ tio -S inet:4444 /dev/ttyUSB0
.SH "WEBSITE"
.PP
project('tio', 'c',
- version : '1.38',
+ version : '1.40',
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-06-02'
+version_date = '2022-06-17'
# Test for dynamic baudrate configuration interface
compiler = meson.get_compiler('c')
-p --parity \
-o --output-delay \
-n --no-autoconnect \
+ -e --local-echo \
-l --log \
+ --log-file \
+ --log-strip \
-m --map \
- -v --version \
-t --timestamp \
+ --timestamp-format \
-L --list-devices \
-c --color \
+ -S --socket \
+ -x --hex-mode \
+ -v --version \
-h --help"
# Complete the arguments to the options.
COMPREPLY=( $(compgen -W "5 6 7 8" -- ${cur}) )
return 0
;;
- -h | --local-echo)
- COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
- return 0
- ;;
-f | --flow)
COMPREPLY=( $(compgen -W "hard soft none" -- ${cur}) )
return 0
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
+ -e | --local-echo)
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
-l | --log)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
+ -l | --log-file)
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+ -l | --log-strip)
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
-m | --map)
COMPREPLY=( $(compgen -W "ICRNL IGNCR INLCR INLCRNL OCRNL ODELBS ONLCRNL" -- ${cur}) )
return 0
;;
-t | --timestamp)
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
+ --timestamp-format)
COMPREPLY=( $(compgen -W "24hour 24hour-start iso8601" -- ${cur}) )
return 0
;;
return 0
;;
-c | --color)
- COMPREPLY=( $(compgen -W "$(seq 0 255)" -- ${cur}) )
+ COMPREPLY=( $(compgen -W "$(seq 0 255) none list" -- ${cur}) )
return 0
;;
-S | --socket)
+ COMPREPLY=( $(compgen -W "unix: inet: inet6:" -- ${cur}) )
+ return 0
+ ;;
+ -x | --hex-mode)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
{
UNUSED(user);
- // If section matches user input or unnamed section
- if ((!strcmp(section, c->section_name)) || (!strcmp(section, "")))
+ // If section matches current section being parsed
+ if (!strcmp(section, c->section_name))
{
// Set configuration parameter if found
if (!strcmp(name, "tty"))
}
else if (!strcmp(name, "no-autoconnect"))
{
- option.no_autoconnect = atoi(value);
+ if (!strcmp(value, "enable"))
+ {
+ option.no_autoconnect = true;
+ }
+ else if (!strcmp(value, "disable"))
+ {
+ option.no_autoconnect = false;
+ }
}
else if (!strcmp(name, "log"))
{
- option.log = atoi(value);
+ if (!strcmp(value, "enable"))
+ {
+ option.log = true;
+ }
+ else if (!strcmp(value, "disable"))
+ {
+ option.log = false;
+ }
+ }
+ else if (!strcmp(name, "log-file"))
+ {
+ asprintf(&c->log_filename, "%s", value);
+ option.log_filename = c->log_filename;
+ }
+ else if (!strcmp(name, "log-strip"))
+ {
+ if (!strcmp(value, "enable"))
+ {
+ option.log_strip = true;
+ }
+ else if (!strcmp(value, "disable"))
+ {
+ option.log_strip = false;
+ }
}
else if (!strcmp(name, "local-echo"))
{
- option.local_echo = atoi(value);
+ if (!strcmp(value, "enable"))
+ {
+ option.local_echo = true;
+ }
+ else if (!strcmp(value, "disable"))
+ {
+ option.local_echo = false;
+ }
+ }
+ else if (!strcmp(name, "hex-mode"))
+ {
+ if (!strcmp(value, "enable"))
+ {
+ option.hex_mode = true;
+ }
+ else if (!strcmp(value, "disable"))
+ {
+ option.hex_mode = false;
+ }
}
else if (!strcmp(name, "timestamp"))
{
- option.timestamp = timestamp_option_parse(value);
+ if (!strcmp(value, "enable"))
+ {
+ option.timestamp = TIMESTAMP_24HOUR;
+ }
+ else if (!strcmp(value, "disable"))
+ {
+ option.timestamp = TIMESTAMP_NONE;
+ }
}
- else if (!strcmp(name, "log-filename"))
+ else if (!strcmp(name, "timestamp-format"))
{
- asprintf(&c->log_filename, "%s", value);
- option.log_filename = c->log_filename;
+ option.timestamp = timestamp_option_parse(value);
}
else if (!strcmp(name, "map"))
{
}
else if (!strcmp(name, "color"))
{
+ if (!strcmp(value, "list"))
+ {
+ // Ignore
+ return 0;
+ }
+
+ if (!strcmp(value, "none"))
+ {
+ option.color = -1; // No color
+ return 0;
+ }
+
option.color = atoi(value);
+ if ((option.color < 0) || (option.color > 255))
+ {
+ option.color = -1; // No color
+ }
}
else if (!strcmp(name, "socket"))
{
return 0;
}
+ free(c->path);
+
asprintf(&c->path, "%s/.config/tio/tiorc", getenv("HOME"));
if (!access(c->path, F_OK))
{
return 0;
}
+ free(c->path);
+
asprintf(&c->path, "%s/.tiorc", getenv("HOME"));
if (!access(c->path, F_OK))
{
return 0;
}
+ free(c->path);
+
+ c->path = NULL;
+
return -EINVAL;
}
-void config_file_parse(const int argc, char *argv[])
+void config_file_parse(void)
{
int ret;
- int i;
c = malloc(sizeof(struct config_t));
memset(c, 0, sizeof(struct config_t));
return;
}
- for (i = 1; i < argc; i++)
- {
- if (argv[i][0] != '-')
- {
- c->user = argv[i];
- break;
- }
- }
+ // Set user input which may be tty device or sub config
+ c->user = option.tty_device;
if (!c->user)
{
fprintf(stderr, "Error: Unable to parse configuration file (%d)", ret);
exit(EXIT_FAILURE);
}
+ free(c->section_name);
c->section_name = NULL;
// Find matching section
}
}
- // Parse settings of found section
+ // Parse settings of found section (sub config)
ret = ini_parse(c->path, data_handler, NULL);
if (ret < 0)
{
fprintf(stderr, "Error: Unable to parse configuration file (%d)", ret);
exit(EXIT_FAILURE);
}
+
+ atexit(&config_exit);
}
void config_exit(void)
free(c);
}
-void config_file_print()
+void config_file_print(void)
{
if (c->path != NULL)
{
tio_printf(" Path: %s", c->path);
if (c->section_name != NULL)
{
- tio_printf(" Active config section: %s", c->section_name);
+ tio_printf(" Active sub-configuration: %s", c->section_name);
}
}
}
char *map;
};
-void config_file_print();
-void config_file_parse(const int argc, char *argv[]);
+void config_file_print(void);
+void config_file_parse(void);
void config_exit(void);
#include "print.h"
#include "error.h"
+#define IS_ESC_CSI_INTERMEDIATE_CHAR(c) ((c >= 0x20) && (c <= 0x3F))
+#define IS_ESC_END_CHAR(c) ((c >= 0x30) && (c <= 0x7E))
+#define IS_CTRL_CHAR(c) ((c >= 0x00) && (c <= 0x1F))
+
static FILE *fp;
static bool log_error = false;
setvbuf(fp, NULL, _IONBF, 0);
}
+bool log_strip(char c)
+{
+ static char previous_char = 0;
+ static bool esc_sequence = false;
+ bool strip = false;
+
+ /* Detect if character should be stripped or not */
+ switch (c)
+ {
+ case 0xa:
+ /* Line feed / new line */
+ /* Reset ESC sequence just in case something went wrong with the
+ * escape sequence parsing. */
+ esc_sequence = false;
+ break;
+
+ case 0x1b:
+ /* Escape */
+ strip = true;
+ break;
+
+ case 0x5b:
+ /* Left bracket */
+ if (previous_char == 0x1b)
+ {
+ // Start of ESC sequence
+ esc_sequence = true;
+ strip = true;
+ }
+ break;
+
+ default:
+ if (IS_CTRL_CHAR(c))
+ {
+ /* Strip ASCII control characters */
+ strip = true;
+ break;
+ }
+ else
+ if ((esc_sequence) && (IS_ESC_CSI_INTERMEDIATE_CHAR(c)))
+ {
+ strip = true;
+ break;
+ }
+ else
+ if ((esc_sequence) && (IS_ESC_END_CHAR(c)))
+ {
+ esc_sequence = false;
+ strip = true;
+ break;
+ }
+ break;
+ }
+
+ previous_char = c;
+
+ return strip;
+}
+
void log_write(char c)
{
if (fp != NULL)
{
- fputc(c, fp);
+ if (option.log_strip)
+ {
+ if (!log_strip(c))
+ {
+ fputc(c, fp);
+ }
+ }
+ else
+ {
+ fputc(c, fp);
+ }
}
}
/* Add error exit handler */
atexit(&error_exit);
- /* Parse configuration file */
- config_file_parse(argc, argv);
- atexit(&config_exit);
-
- /* Parse command-line options */
+ /* Parse command-line options (1st pass) */
options_parse(argc, argv);
- /* List available serial devices */
- if (option.list_devices)
- {
- list_serial_devices();
- return status;
- }
+ /* Parse configuration file */
+ config_file_parse();
+
+ /* Parse command-line options (2nd pass) */
+ options_parse_final(argc, argv);
/* Configure tty device */
tty_configure();
/* Configure input terminal */
- stdin_configure();
+ if (isatty(fileno(stdin)))
+ {
+ stdin_configure();
+ }
+ else
+ {
+ // Enter non interactive mode
+ interactive_mode = false;
+ }
/* Configure output terminal */
- stdout_configure();
+ if (isatty(fileno(stdout)))
+ {
+ stdout_configure();
+ }
+ else
+ {
+ // No color when piping
+ option.color = -1;
+ }
/* Add log exit handler */
atexit(&log_exit);
/* Create log file */
if (option.log)
+ {
log_open(option.log_filename);
+ }
- /* Enable ANSI text formatting (colors etc.) */
- print_enable_ansi_formatting();
+ /* Initialize ANSI text formatting (colors etc.) */
+ print_init_ansi_formatting();
/* Print launch hints */
tio_printf("tio v%s", VERSION);
- tio_printf("Press ctrl-t q to quit");
+ if (interactive_mode)
+ {
+ tio_printf("Press ctrl-t q to quit");
+ }
/* Open socket */
- socket_configure();
+ if (option.socket)
+ {
+ socket_configure();
+ }
/* Connect to tty device */
- if (option.no_autoconnect)
+ if ((option.no_autoconnect) || (!interactive_mode))
+ {
status = tty_connect();
+ }
else
{
/* Enter connect loop */
#include "error.h"
#include "misc.h"
#include "print.h"
+#include "tty.h"
+
+enum opt_t
+{
+ OPT_NONE,
+ OPT_TIMESTAMP_FORMAT,
+ OPT_LOG_FILE,
+ OPT_LOG_STRIP,
+};
/* Default options */
struct option_t option =
.output_delay = 0,
.no_autoconnect = false,
.log = false,
+ .log_filename = NULL,
+ .log_strip = false,
.local_echo = false,
.timestamp = TIMESTAMP_NONE,
- .list_devices = false,
- .log_filename = NULL,
.socket = NULL,
.map = "",
- .color = -1,
+ .color = 15,
+ .hex_mode = false,
};
void print_help(char *argv[])
printf("Usage: %s [<options>] <tty-device|config>\n", argv[0]);
printf("\n");
printf("Options:\n");
- printf(" -b, --baudrate <bps> Baud rate (default: 115200)\n");
- printf(" -d, --databits 5|6|7|8 Data bits (default: 8)\n");
- printf(" -f, --flow hard|soft|none Flow control (default: none)\n");
- printf(" -s, --stopbits 1|2 Stop bits (default: 1)\n");
- printf(" -p, --parity odd|even|none Parity (default: none)\n");
- printf(" -o, --output-delay <ms> Output delay (default: 0)\n");
- printf(" -n, --no-autoconnect Disable automatic connect\n");
- printf(" -e, --local-echo Enable local echo\n");
- printf(" -t, --timestamp[=<format>] Enable timestamp (default: 24hour)\n");
- printf(" -L, --list-devices List available serial devices\n");
- printf(" -l, --log[=<filename>] Log to file\n");
- printf(" -m, --map <flags> Map special characters\n");
- printf(" -c, --color <code> Colorize tio text\n");
- printf(" -S, --socket <socket> Listen on socket\n");
- printf(" -v, --version Display version\n");
- printf(" -h, --help Display help\n");
+ printf(" -b, --baudrate <bps> Baud rate (default: 115200)\n");
+ printf(" -d, --databits 5|6|7|8 Data bits (default: 8)\n");
+ printf(" -f, --flow hard|soft|none Flow control (default: none)\n");
+ printf(" -s, --stopbits 1|2 Stop bits (default: 1)\n");
+ printf(" -p, --parity odd|even|none Parity (default: none)\n");
+ printf(" -o, --output-delay <ms> Output delay (default: 0)\n");
+ printf(" -n, --no-autoconnect Disable automatic connect\n");
+ printf(" -e, --local-echo Enable local echo\n");
+ printf(" -t, --timestamp Enable line timestamp\n");
+ printf(" --timestamp-format <format> Set timestamp format (default: 24hour)\n");
+ printf(" -L, --list-devices List available serial devices\n");
+ printf(" -l, --log Enable log to file\n");
+ printf(" --log-file <filename> Set log filename\n");
+ printf(" --log-strip Strip control characters and escape sequences\n");
+ printf(" -m, --map <flags> Map special characters\n");
+ printf(" -c, --color 0..255|none|list Colorize tio text (default: 15)\n");
+ printf(" -S, --socket <socket> Redirect I/O to socket\n");
+ printf(" -x, --hex-mode Enable hexadecimal mode\n");
+ printf(" -v, --version Display version\n");
+ printf(" -h, --help Display help\n");
printf("\n");
printf("Options may be set via configuration file.\n");
printf("\n");
printf("In session, press ctrl-t q to quit.\n");
printf("\n");
printf("See the man page for more details.\n");
- printf("\n");
-
}
const char* timestamp_token(enum timestamp_t timestamp)
}
else
{
- printf("Warning: Unknown timestamp type, falling back to '24hour' default format\n");
+ warning_printf("Unknown timestamp type, falling back to '24hour' default format");
}
}
{
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'},
- {"no-autoconnect", no_argument, 0, 'n'},
- {"local-echo", no_argument, 0, 'e'},
- {"timestamp", optional_argument, 0, 't'},
- {"list-devices", no_argument, 0, 'L'},
- {"log", optional_argument, 0, 'l'},
- {"socket", required_argument, 0, 'S'},
- {"map", required_argument, 0, 'm'},
- {"color", required_argument, 0, 'c'},
- {"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' },
+ {"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' },
+ {"hex-mode", no_argument, 0, 'x' },
+ {"version", no_argument, 0, 'v' },
+ {"help", no_argument, 0, 'h' },
+ {0, 0, 0, 0 }
};
/* getopt_long stores the option index here */
int option_index = 0;
/* Parse argument using getopt_long */
- c = getopt_long(argc, argv, "b:d:f:s:p:o:net::Ll::S:m:c:vh", long_options, &option_index);
+ c = getopt_long(argc, argv, "b:d:f:s:p:o:netLlS:m:c:xvh", long_options, &option_index);
/* Detect the end of the options */
if (c == -1)
break;
case 't':
+ option.timestamp = TIMESTAMP_24HOUR;
+ break;
+
+ case OPT_TIMESTAMP_FORMAT:
option.timestamp = timestamp_option_parse(optarg);
break;
case 'L':
- option.list_devices = true;
+ list_serial_devices();
+ exit(EXIT_SUCCESS);
break;
case 'l':
option.log = true;
+ break;
+
+ case OPT_LOG_FILE:
option.log_filename = optarg;
break;
+ case OPT_LOG_STRIP:
+ option.log_strip = true;
+ break;
+
case 'S':
option.socket = optarg;
break;
break;
case 'c':
- option.color = string_to_long(optarg);
- if (option.color > 255)
- {
- printf("Error: Invalid color code\n");
- exit(EXIT_FAILURE);
- }
- if (option.color < 0)
+ if (!strcmp(optarg, "list"))
{
// Print available color codes
printf("Available color codes:\n");
}
exit(EXIT_SUCCESS);
}
+
+ if (!strcmp(optarg, "none"))
+ {
+ option.color = -1;
+ break;
+ }
+
+ option.color = string_to_long(optarg);
+ if ((option.color < 0) || (option.color > 255))
+ {
+ printf("Error: Invalid color code\n");
+ exit(EXIT_FAILURE);
+ }
+ break;
+
+ case 'x':
+ option.hex_mode = true;
break;
case 'v':
}
}
- if (option.list_devices)
- {
- return;
- }
-
/* Assume first non-option is the tty device name */
if (strcmp(option.tty_device, ""))
optind++;
if (strlen(option.tty_device) == 0)
{
- printf("Error: Missing device name\n");
+ printf("Error: Missing device or config name\n");
exit(EXIT_FAILURE);
}
exit(EXIT_FAILURE);
}
}
+
+void options_parse_final(int argc, char *argv[])
+{
+ /* Preserve tty device which may have been set by configuration file */
+ const char *tty_device = option.tty_device;
+
+ /* Do 2nd pass to override settings set by configuration file */
+ optind = 1; // Reset option index to restart scanning of argv
+ options_parse(argc, argv);
+
+ /* Restore tty device */
+ option.tty_device = tty_device;
+}
TIMESTAMP_24HOUR,
TIMESTAMP_24HOUR_START,
TIMESTAMP_ISO8601,
+ TIMESTAMP_END,
};
+
const char* timestamp_token(enum timestamp_t timestamp);
enum timestamp_t timestamp_option_parse(const char *arg);
int output_delay;
bool no_autoconnect;
bool log;
+ bool log_strip;
bool local_echo;
enum timestamp_t timestamp;
- bool list_devices;
const char *log_filename;
const char *map;
const char *socket;
int color;
+ bool hex_mode;
};
extern struct option_t option;
void options_print();
void options_parse(int argc, char *argv[]);
+void options_parse_final(int argc, char *argv[]);
void print_hex(char c)
{
- if ((c == '\n') || (c == '\r'))
- {
- printf("%c", c);
- }
- else
- {
- printf("%02x ", (unsigned char) c);
- }
+ printf("%02x ", (unsigned char) c);
fflush(stdout);
}
fflush(stdout);
}
-void print_enable_ansi_formatting()
+void print_init_ansi_formatting()
{
- if (option.color < 0)
- {
- // Enable bold text
- sprintf(ansi_format, "\e[1m");
- }
- else
- {
- // Enable bold text with user defined ANSI color
- sprintf(ansi_format, "\e[1;38;5;%dm", option.color);
- }
+ // Set bold text with user defined ANSI color
+ sprintf(ansi_format, "\e[1;38;5;%dm", option.color);
}
#include <stdbool.h>
#include "misc.h"
#include "error.h"
+#include "options.h"
extern bool print_tainted;
extern char ansi_format[];
#define ansi_printf(format, args...) \
{ \
- fprintf (stdout, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \
+ if (option.color < 0) \
+ fprintf (stdout, "\r" format "\r\n", ## args); \
+ else \
+ fprintf (stdout, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \
fflush(stdout); \
}
#define ansi_error_printf(format, args...) \
{ \
- fprintf (stderr, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \
+ if (option.color < 0) \
+ fprintf (stdout, "\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...) \
{ \
- fprintf (stdout, "%s" format ANSI_RESET, ansi_format, ## args); \
+ if (option.color < 0) \
+ fprintf (stdout, format, ## args); \
+ else \
+ fprintf (stdout, "%s" format ANSI_RESET, ansi_format, ## args); \
fflush(stdout); \
}
#define warning_printf(format, args...) \
{ \
- ansi_printf("[%s] Warning: " format, current_time(), ## args); \
+ 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); \
fflush(stdout); \
}
void print_hex(char c);
void print_normal(char c);
-void print_enable_ansi_formatting(void);
+void print_init_ansi_formatting(void);
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
+#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <netinet/in.h>
#include <unistd.h>
#define MAX_SOCKET_CLIENTS 16
+#define SOCKET_PORT_DEFAULT 3333
static int sockfd;
static int clientfds[MAX_SOCKET_CLIENTS];
+static int socket_family = AF_UNSPEC;
+static int port_number = SOCKET_PORT_DEFAULT;
static const char *socket_filename(void)
{
return option.socket + 5;
}
+static int socket_inet_port(void)
+{
+ /* skip 'inet:' */
+ int port_number = atoi(option.socket + 5);
+ if (port_number == 0)
+ {
+ port_number = SOCKET_PORT_DEFAULT;
+ }
+ return port_number;
+}
+
+static int socket_inet6_port(void)
+{
+ /* skip 'inet6:' */
+ int port_number = atoi(option.socket + 6);
+ if (port_number == 0)
+ {
+ port_number = SOCKET_PORT_DEFAULT;
+ }
+ return port_number;
+}
+
static void socket_exit(void)
{
- unlink(socket_filename());
+ if (socket_family == AF_UNIX)
+ {
+ unlink(socket_filename());
+ }
}
void socket_configure(void)
{
- if (!option.socket)
+ struct sockaddr_un sockaddr_unix = {};
+ struct sockaddr_in sockaddr_inet = {};
+ struct sockaddr_in6 sockaddr_inet6 = {};
+ struct sockaddr *sockaddr_p;
+ socklen_t socklen;
+
+ /* Parse socket string */
+
+ if (strncmp(option.socket, "unix:", 5) == 0)
{
- return;
+ socket_family = AF_UNIX;
+
+ if (strlen(socket_filename()) == 0)
+ {
+ error_printf("Missing socket filename");
+ exit(EXIT_FAILURE);
+ }
+
+ if (strlen(socket_filename()) > sizeof(sockaddr_unix.sun_path) - 1)
+ {
+ error_printf("Socket file path %s too long", option.socket);
+ exit(EXIT_FAILURE);
+ }
}
- if (strncmp(option.socket, "unix:", 5) != 0)
+ if (strncmp(option.socket, "inet:", 5) == 0)
{
- error_printf("%s: Invalid socket scheme, must be 'unix:'", option.socket);
- exit(EXIT_FAILURE);
+ socket_family = AF_INET;
+
+ port_number = socket_inet_port();
+
+ if (port_number < 0)
+ {
+ error_printf("Invalid port number: %d", port_number);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (strncmp(option.socket, "inet6:", 6) == 0)
+ {
+ socket_family = AF_INET6;
+
+ port_number = socket_inet6_port();
+
+ if (port_number < 0)
+ {
+ error_printf("Invalid port number: %d", port_number);
+ exit(EXIT_FAILURE);
+ }
}
- struct sockaddr_un sockaddr = {};
- if (strlen(socket_filename()) > sizeof(sockaddr.sun_path) - 1)
+ if (socket_family == AF_UNSPEC)
{
- error_printf("Socket file path %s too long", option.socket);
+ error_printf("%s: Invalid socket scheme, must be prefixed with 'unix:', 'inet:', or 'inet6:'", option.socket);
exit(EXIT_FAILURE);
}
- sockaddr.sun_family = AF_UNIX;
- strncpy(sockaddr.sun_path, socket_filename(), sizeof(sockaddr.sun_path) - 1);
+ /* Configure socket */
+
+ switch (socket_family)
+ {
+ case AF_UNIX:
+ sockaddr_unix.sun_family = AF_UNIX;
+ strncpy(sockaddr_unix.sun_path, socket_filename(), sizeof(sockaddr_unix.sun_path) - 1);
+ sockaddr_p = (struct sockaddr *) &sockaddr_unix;
+ socklen = sizeof(sockaddr_unix);
+ break;
- sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ case AF_INET:
+ sockaddr_inet.sin_family = AF_INET;
+ sockaddr_inet.sin_addr.s_addr = INADDR_ANY;
+ sockaddr_inet.sin_port = htons(port_number);
+ sockaddr_p = (struct sockaddr *) &sockaddr_inet;
+ socklen = sizeof(sockaddr_inet);
+ break;
+
+ case AF_INET6:
+ sockaddr_inet6.sin6_family = AF_INET6;
+ sockaddr_inet6.sin6_addr = in6addr_any;
+ sockaddr_inet6.sin6_port = htons(port_number);
+ sockaddr_p = (struct sockaddr *) &sockaddr_inet6;
+ socklen = sizeof(sockaddr_inet6);
+ break;
+
+ default:
+ error_printf("Invalid socket family (%d)", socket_family);
+ exit(EXIT_FAILURE);
+ break;
+ }
+
+ /* Create socket */
+ sockfd = socket(socket_family, SOCK_STREAM, 0);
if (sockfd < 0)
{
- error_printf("Failed to create socket: %s", strerror(errno));
+ error_printf("Failed to create socket (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
- unlink(socket_filename());
- if (bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0)
+ /* Bind */
+ if (bind(sockfd, sockaddr_p, socklen) < 0)
{
- error_printf("Failed to bind to socket %s: %s", socket_filename(), strerror(errno));
+ error_printf("Failed to bind to socket (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
+ /* Listen */
if (listen(sockfd, MAX_SOCKET_CLIENTS) < 0)
{
- error_printf("Failed to listen on socket %s: %s", socket_filename(), strerror(errno));
+ error_printf("Failed to listen on socket (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
memset(clientfds, -1, sizeof(clientfds));
atexit(socket_exit);
- tio_printf("Listening on socket %s", socket_filename());
+ if (socket_family == AF_UNIX)
+ {
+ tio_printf("Listening on socket %s", socket_filename());
+ }
+ else
+ {
+ tio_printf("Listening on socket port %d", port_number);
+ }
}
void socket_write(char input_char)
{
if (write(clientfds[i], &input_char, 1) <= 0)
{
- error_printf_silent("Failed to write to socket: %s", strerror(errno));
+ error_printf_silent("Failed to write to socket (%s)", strerror(errno));
close(clientfds[i]);
clientfds[i] = -1;
}
}
if (status < 0)
{
- error_printf_silent("Failed to read from socket: %s", strerror(errno));
+ error_printf_silent("Failed to read from socket (%s)", strerror(errno));
close(clientfds[i]);
clientfds[i] = -1;
continue;
#define PATH_SERIAL_DEVICES "/dev/serial/by-id/"
#endif
+bool interactive_mode = true;
+
static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old;
static unsigned long rx_total = 0, tx_total = 0;
static bool connected = false;
static bool map_o_cr_nl = false;
static bool map_o_nl_crnl = false;
static bool map_o_del_bs = false;
+static char hex_chars[2];
+static unsigned char hex_char_index = 0;
+
+static void optional_local_echo(char c)
+{
+ if (!option.local_echo)
+ return;
+ print(c);
+ fflush(stdout);
+ if (option.log)
+ log_write(c);
+}
+
+inline static bool is_valid_hex(char c)
+{
+ return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+}
+
+inline static unsigned char char_to_nibble(char c)
+{
+ if(c >= '0' && c <= '9'){
+ return c - '0';
+ } else if(c >= 'a' && c <= 'f'){
+ return c - 'a' + 10;
+ } else if(c >= 'A' && c <= 'F'){
+ return c - 'A' + 10;
+ } else {
+ return 0;
+ }
+}
+
+static void output_hex(char c)
+{
+ hex_chars[hex_char_index++] = c;
+
+ if (hex_char_index == 2)
+ {
+ unsigned char hex_value = char_to_nibble(hex_chars[0]) << 4 | (char_to_nibble(hex_chars[1]) & 0x0F);
+ hex_char_index = 0;
+
+ optional_local_echo(hex_value);
+
+ ssize_t status = write(fd, &hex_value, 1);
+ if (status < 0)
+ {
+ warning_printf("Could not write to tty device");
+ } else
+ {
+ tx_total++;
+ }
+ fsync(fd);
+ }
+}
static void toggle_line(const char *line_name, int mask)
{
if (ioctl(fd, TIOCMGET, &state) < 0)
{
- error_printf("Could not get line state: %s", strerror(errno));
+ warning_printf("Could not get line state (%s)", strerror(errno));
}
else
{
tio_printf("set %s to HIGH", line_name);
}
if (ioctl(fd, TIOCMSET, &state) < 0)
- error_printf("Could not set line state: %s", strerror(errno));
+ warning_printf("Could not set line state (%s)", strerror(errno));
}
}
case KEY_SHIFT_L:
if (ioctl(fd, TIOCMGET, &state) < 0)
{
- error_printf("Could not get line state: %s", strerror(errno));
+ warning_printf("Could not get line state (%s)", strerror(errno));
break;
}
tio_printf("Line states:");
case KEY_E:
option.local_echo = !option.local_echo;
+ tio_printf("Switched local echo %s", option.local_echo ? "on" : "off");
break;
case KEY_H:
break;
case KEY_SHIFT_T:
- option.timestamp = !option.timestamp;
+ option.timestamp += 1;
+ switch (option.timestamp)
+ {
+ case TIMESTAMP_NONE:
+ break;
+ case TIMESTAMP_24HOUR:
+ tio_printf("Switched to 24hour timestamp mode");
+ break;
+ case TIMESTAMP_24HOUR_START:
+ tio_printf("Switched to 24hour-start timestamp mode");
+ break;
+ case TIMESTAMP_ISO8601:
+ tio_printf("Switched to iso8601 timestamp mode");
+ break;
+ case TIMESTAMP_END:
+ option.timestamp = TIMESTAMP_NONE;
+ tio_printf("Switched timestamp off");
+ break;
+ }
break;
case KEY_V:
/* Set speed */
switch (option.baudrate)
{
- /* The macro below expands into switch cases autogenerated by the
- * configure script. Each switch case verifies and configures the baud
+ /* The macro below expands into switch cases autogenerated by meson
+ * configure. Each switch case verifies and configures the baud
* rate and is of the form:
*
* case $baudrate: baudrate = B$baudrate; break;
tty_disconnect();
}
-static void optional_local_echo(char c)
-{
- if (!option.local_echo)
- return;
- print(c);
- fflush(stdout);
- if (option.log)
- log_write(c);
-}
-
int tty_connect(void)
{
fd_set rdfs; /* Read file descriptor set */
if (option.timestamp)
next_timestamp = true;
+ /* Manage print output mode */
+ if (option.hex_mode)
+ {
+ print = print_hex;
+ print_mode = HEX;
+ }
+ else
+ {
+ print = print_normal;
+ print_mode = NORMAL;
+ }
+
/* Save current port settings */
if (tcgetattr(fd, &tio_old) < 0)
goto error_tcgetattr;
goto error_read;
}
- /* Forward input to output except ctrl-t key */
+ /* Forward input to output */
output_char = input_char;
- if (input_char == KEY_CTRL_T)
- forward = false;
- /* Handle commands */
- handle_command_sequence(input_char, previous_char, &output_char, &forward);
+ if (interactive_mode)
+ {
+ /* Do not forward ctrl-t key */
+ if (input_char == KEY_CTRL_T)
+ forward = false;
- /* Save previous key */
- previous_char = input_char;
+ /* Handle commands */
+ handle_command_sequence(input_char, previous_char, &output_char, &forward);
+ /* Save previous key */
+ previous_char = input_char;
+ }
}
else
{
if (forward)
{
+ if (print_mode == HEX)
+ {
+ if (!is_valid_hex(input_char))
+ {
+ warning_printf("Invalid hex character: '%d' (0x%02x)", input_char, input_char);
+ continue;
+ }
+ }
+
/* Map output character */
if ((output_char == 127) && (map_o_del_bs))
output_char = '\b';
delay(option.output_delay);
} else
{
- /* Send output to tty device */
- optional_local_echo(output_char);
- status = write(fd, &output_char, 1);
- if (status < 0)
- warning_printf("Could not write to tty device");
- fsync(fd);
-
- /* Update transmit statistics */
- tx_total++;
+ if (print_mode == HEX)
+ {
+ output_hex(output_char);
+ }
+ else
+ {
+ /* Send output to tty device */
+ optional_local_echo(output_char);
+ status = write(fd, &output_char, 1);
+ if (status < 0)
+ warning_printf("Could not write to tty device");
+ fsync(fd);
+
+ /* Update transmit statistics */
+ tx_total++;
+ }
/* Insert output delay */
delay(option.output_delay);
#pragma once
+#include <stdbool.h>
+
#define KEY_QUESTION 0x3f
#define KEY_B 0x62
#define KEY_C 0x63
int tty_connect(void);
void tty_wait_for_device(void);
void list_serial_devices(void);
+
+extern bool interactive_mode;