Add new ways to manage serial devices

* Rename --list-devices to --list

 * Rename --no-autoconnect to --no-reconnect

 * Switch -l and -L options

   * -l now lists available serial devices

   * -L enables log to file

 * Add option --auto-connect <strategy>

   * Supported strategies:

     * "new" - Waits to connect first new appearing serial device

     * "latest" - Connects to latest registered serial device

     * "direct" - Connect directly to specified serial device (default)

 * Add options to exclude serial devices from auto connect strategy by
   pattern

   * Supported exclude options:

     * --exclude-devices <pattern>

       Example: '--exclude-devices "/dev/ttyUSB2,/dev/ttyS?"'

     * --exclude-drivers <pattern>

       Example: '--exclude-drivers "cdc_acm"'

     * --exclude-tids <pattern>

       Example: '--exclude-tids "yW07,bCC2"'

     * Patterns support '*' and '?'

 * Connect to same port/device combination via unique topology ID (TID)

   * Topology ID is a 4 digit base62 encoded hash of a device topology
     string coming from the Linux kernel. This means that whenever you
     plug in the same e.g. USB serial port device to the same USB hub
     port connected via the exact same hub topology all the way to your
     computer, you will get the same unique TID.

   * Useful for stable reconnections when serial device has no serial
     device by ID

   * For now, only tested on Linux.

 * Reworked and improved listing of serial devices to show serial devices:

   * By device

     * Including TID, uptime, driver, and description.

     * Sorted by uptime (newest device listed last)

   * By unique topology ID

   * By ID

   * By path

 * Add script interface 'list = tty_search()' for searching for serial
   devices.
This commit is contained in:
Martin Lund 2024-04-26 20:34:17 +02:00
parent ae76f8f58d
commit d19ba1c492
22 changed files with 1468 additions and 150 deletions

View file

@ -15,7 +15,7 @@ jobs:
steps: steps:
- checkout - checkout
- run: sudo apt-get -qq update - run: sudo apt-get -qq update
- run: sudo apt-get install -y bash-completion git meson libinih-dev liblua5.2-dev - run: sudo apt-get install -y bash-completion git meson libinih-dev liblua5.2-dev libglib2.0-dev
- run: git clone https://github.com/tio/tio.git - run: git clone https://github.com/tio/tio.git
- run: cd tio && meson build --prefix $HOME/test/tio && ninja -C build install - run: cd tio && meson build --prefix $HOME/test/tio && ninja -C build install

View file

@ -2,6 +2,6 @@
pip3 install meson -U pip3 install meson -U
pip3 install ninja -U pip3 install ninja -U
sudo apt-get install -y liblua5.2-dev sudo apt-get install -y liblua5.2-dev libglib2.0-dev
meson setup build meson setup build
meson compile -C build meson compile -C build

151
README.md
View file

@ -31,40 +31,61 @@ when used in combination with [tmux](https://tmux.github.io).
## 2. Features ## 2. Features
* Easily connect to serial TTY devices * Easily connect to serial TTY devices
* Automatic connect and reconnect
* Sensible defaults (115200 8n1) * Sensible defaults (115200 8n1)
* Support for non-standard baud rates * Support for non-standard baud rates
* Support for mark and space parity * Support for mark and space parity
* Automatic connection management
* Automatic reconnect
* Automatically connect to first new appearing serial device
* Automatically connect to latest registered serial device
* Connect to same port/device combination via unique topology ID (TID)
* Useful for reconnecting when serial device has no serial device by ID
* X-modem (1K/CRC) and Y-modem file upload * X-modem (1K/CRC) and Y-modem file upload
* Support for RS-485 mode * Support for RS-485 mode
* List available serial devices by ID * List available serial devices
* By device
* Including topology ID, uptime, driver, description
* Sorted by uptime (newest device listed last)
* By ID
* By path
* Show RX/TX statistics * Show RX/TX statistics
* Toggle serial lines * Toggle serial lines
* Pulse serial lines with configurable pulse duration * Pulse serial lines with configurable pulse duration
* Local echo support * Local echo support
* Remapping of characters (nl, cr-nl, bs, lowercase to uppercase, etc.) * Remapping of characters (nl, cr-nl, bs, lowercase to uppercase, etc.)
* Switchable independent input and output mode (normal vs hex) * Switchable independent input and output
* Normal mode
* Hex mode
* Line mode (input only)
* Timestamp support * Timestamp support
* Per line in normal output mode * Per line in normal output mode
* Output timeout timestamps in hex output mode * Output timeout timestamps in hex output mode
* Support for delayed output * Support for delayed output
* Per character * Per character
* Per line * Per line
* Log to file with automatic or manual naming of log file * Log to file
* Automatic naming of log file (default)
* Configurable directory for saving automatic named log files
* Manual naming of log file
* Overwrite (default) or append to log file
* Strip control characters and escape sequences
* Configuration file support * Configuration file support
* Support for sub-configurations
* Activate sub-configurations by name or pattern * Activate sub-configurations by name or pattern
* Redirect I/O to UNIX socket or IPv4/v6 network socket for scripting or TTY sharing * Redirect I/O to UNIX socket or IPv4/v6 network socket
* Useful for scripting or TTY sharing
* Pipe input and/or output * Pipe input and/or output
* Bash completion on options, serial device names, and sub-configuration names * Bash completion on options, serial device names, and sub-configuration names
* Configurable text color * Configurable tio message text color
* Supports NO_COLOR env variable as per no-color.org
* Visual or audible alert on connect/disconnect * Visual or audible alert on connect/disconnect
* Remapping of prefix key * Remapping of prefix key
* Support NO_COLOR env variable as per no-color.org
* Lua scripting support for automation * Lua scripting support for automation
* Run script manually or automatically at connect once/always/never * Run script manually or automatically at connect (once/always/never)
* Simple expect/send like functionality with support for regular expressions * Simple expect/send like functionality with support for regular expressions
* Manipulate port control lines (useful for microcontroller reset/boot etc.) * Manipulate port control lines (useful for microcontroller reset/boot etc.)
* Send files via x/y-modem protocol * Send files via x/y-modem protocol
* Search for serial devices
* Man page documentation * Man page documentation
* Plays nicely with [tmux](https://tmux.github.io) * Plays nicely with [tmux](https://tmux.github.io)
@ -78,9 +99,9 @@ For more usage details please see the man page documentation
The command-line interface is straightforward as reflected in the output from The command-line interface is straightforward as reflected in the output from
'tio --help': 'tio --help':
``` ```
Usage: tio [<options>] <tty-device|sub-config> Usage: tio [<options>] <tty-device|sub-config|tid>
Connect to TTY device directly or via sub-configuration. Connect to TTY device directly or via sub-configuration or topology ID.
Options: Options:
-b, --baudrate <bps> Baud rate (default: 115200) -b, --baudrate <bps> Baud rate (default: 115200)
@ -91,15 +112,19 @@ Options:
-o, --output-delay <ms> Output character delay (default: 0) -o, --output-delay <ms> Output character delay (default: 0)
-O, --output-line-delay <ms> Output line delay (default: 0) -O, --output-line-delay <ms> Output line delay (default: 0)
--line-pulse-duration <duration> Set line pulse duration --line-pulse-duration <duration> Set line pulse duration
-n, --no-autoconnect Disable automatic connect -a, --auto-connect new|latest|direct Automatic connect strategy (default: direct)
--exclude-devices <pattern> Exclude devices by pattern
--exclude-drivers <pattern> Exclude drivers by pattern
--exclude-tids <pattern> Exclude topology IDs by pattern
-n, --no-reconnect Do not reconnect
-e, --local-echo Enable local echo -e, --local-echo Enable local echo
--input-mode normal|hex|line Select input mode (default: normal) --input-mode normal|hex|line Select input mode (default: normal)
--output-mode normal|hex Select output mode (default: normal) --output-mode normal|hex Select output mode (default: normal)
-t, --timestamp Enable line timestamp -t, --timestamp Enable line timestamp
--timestamp-format <format> Set timestamp format (default: 24hour) --timestamp-format <format> Set timestamp format (default: 24hour)
--timestamp-timeout <ms> Set timestamp timeout (default: 200) --timestamp-timeout <ms> Set timestamp timeout (default: 200)
-L, --list-devices List available serial devices by ID -l, --list List available serial devices
-l, --log Enable log to file -L, --log Enable log to file
--log-file <filename> Set log filename --log-file <filename> Set log filename
--log-directory <path> Set log directory path for automatic named logs --log-directory <path> Set log directory path for automatic named logs
--log-append Append to log file --log-append Append to log file
@ -125,7 +150,7 @@ See the man page for more details.
By default tio automatically connects to the provided TTY device if present. 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 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 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` device to reappear and then reconnect. However, if the `--no-reconnect`
option is provided, tio will exit if the device is not present or an option is provided, tio will exit if the device is not present or an
established connection is lost. established connection is lost.
@ -150,18 +175,23 @@ $ tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
Using serial devices by ID ensures that tio automatically reconnects to the Using serial devices by ID ensures that tio automatically reconnects to the
correct serial device if it is disconnected and then reconnected. correct serial device if it is disconnected and then reconnected.
List available serial devices by ID: List available serial devices:
``` ```
$ tio --list-devices $ tio --list
``` ```
Note: One can also use tio shell completion on /dev which will automatically Note: One can also use tio bash shell completion on /dev which will
list all available serial TTY devices. automatically list all available serial TTY devices by ID.
Log to file with autogenerated filename: Log to file with autogenerated filename:
``` ```
$ tio --log /dev/ttyUSB0 $ tio --log /dev/ttyUSB0
``` ```
Log to file with filename:
```
$ tio --log --log-file my-log.txt
```
Enable ISO8601 timestamps per line: Enable ISO8601 timestamps per line:
``` ```
$ tio --timestamp --timestamp-format iso8601 /dev/ttyUSB0 $ tio --timestamp --timestamp-format iso8601 /dev/ttyUSB0
@ -183,6 +213,80 @@ $ echo "*IDN?" | tio /dev/ttyACM0 --script "expect('\r\n', 1000)" --mute
KORAD KD3305P V4.2 SN:32475045 KORAD KD3305P V4.2 SN:32475045
``` ```
### 3.1.2 Different ways to connect to serial devices
Using tio there are up to 4 recommended ways to connect to a specific serial
device:
* Connect by ID
* Example: ```tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTCHUV56-if00-port0```
* Connect by topology ID
* Example: ```tio bCC2```
* Connect to enumerated device in /dev
* Example: ```tio /dev/ttyUSB4```
* Connect by path
* Example: ```tio /dev/serial/by-path/pci-0000:00:14.0-usb-0:8.1.3.1.4:1.0-port0```
Which serial device to connect becomes more clear from tio's serial device
listing which provides more information about each serial device. For example:
```
$ tio --list
Device TID Uptime [s] Driver Description
----------------- ---- ------------- ---------------- --------------------------
/dev/ttyS4 8xSh 32532.317 port 16550A UART
/dev/ttyS5 HJhB 32530.578 port 16550A UART
/dev/ttyUSB3 yW07 32464.194 ftdi_sio TTL232RG-VREG3V3
/dev/ttyUSB4 bCC2 26066.573 ftdi_sio TTL232R-3V3
/dev/ttyUSB0 g5q4 136.717 ftdi_sio Flyswatter2
/dev/ttyUSB1 h5q4 136.715 ftdi_sio Flyswatter2
/dev/ttyACM0 EOEs 10.449 cdc_acm ST-Link VCP Ctrl
By-id
--------------------------------------------------------------------------------
/dev/serial/by-id/usb-FTDI_TTL232RG-VREG3V3_FT1NC2D0-if00-port0
/dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTCHUV56-if00-port0
/dev/serial/by-id/usb-TinCanTools_Flyswatter2_FS20000-if00-port0
/dev/serial/by-id/usb-TinCanTools_Flyswatter2_FS20000-if01-port0
/dev/serial/by-id/usb-STMicroelectronics_STLINK-V3_004900343438510234313939-if02
By-path
--------------------------------------------------------------------------------
/dev/serial/by-path/pci-0000:00:14.0-usb-0:8.1.3.2.2:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:8.1.3.2.2:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usb-0:8.1.3.1.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:8.1.3.1.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usb-0:6.3:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:6.3:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usb-0:6.3:1.1-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:6.3:1.1-port0
/dev/serial/by-path/pci-0000:00:14.0-usb-0:6.4:1.2
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:6.4:1.2
```
Note: The topology ID (TID) is a special hash of the full topology path of the
connected device. This means that every time e.g. a USB serial device is
plugged into to the exact same USB hub chain it will get the exact same TID.
This helps solve the problem of reconnecting to serical devices which do not
provide a unique device by ID.
Additonally tio offers two convenient ways of connecting to serial devices:
* Connect automatically to first new appearing serial device
* ```tio --auto-connect new```
* Connect automatically to latest registered serial device
* ```tio --auto-connect latest```
It is also possible to use excludes to affect strategy decisions:
* Exclude devices by pattern
* Example: ```tio --auto-connect new --exclude-devices "/dev/ttyACM?,/dev/ttyUSB2"```
* Exclude drivers by pattern
* Example: ```tio --auto-connect new --exclude-drivers "cdc_acm,ftdi_sio"```
* Exclude topology IDs by pattern
* Example: ```tio --auto-connect new --exclude-tids "EOEs"```
Note: Pattern matching supports '*' and '?'. Use comma separation to define multiple patterns.
### 3.2 Key commands ### 3.2 Key commands
Various in session key commands are supported. When tio is started, press Various in session key commands are supported. When tio is started, press
@ -237,6 +341,15 @@ In addition to the Lua API tio makes the following functions available:
Send file using x/y-modem protocol. Send file using x/y-modem protocol.
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM. Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
tty_search()
Search for serial devices.
Returns a table of number indexed tables, one for each serial device
found. Each of these tables contains the serial device information accessible
via the following string indexed elements "path", "tid", "uptime", "driver",
"description".
Returns nil if no serial devices are found.
exit(code) exit(code)
Exit with code. Exit with code.
@ -299,7 +412,7 @@ color = 10
[rpi3] [rpi3]
device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0 device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
baudrate = 115200 baudrate = 115200
no-autoconnect = enable no-reconnect = enable
log = enable log = enable
log-file = rpi3.log log-file = rpi3.log
line-pulse-duration = DTR=200,RTS=150 line-pulse-duration = DTR=200,RTS=150

View file

@ -18,8 +18,10 @@ parity = none
prefix-ctrl-key = t prefix-ctrl-key = t
output-delay = 0 output-delay = 0
output-line-delay = 0 output-line-delay = 0
no-autoconnect = disable auto-connect = direct
hexadecimal = disable no-reconnect = disable
input-mode = normal
output-mode = normal
timestamp = disable timestamp = disable
log = disable log = disable
log-append = disable log-append = disable

View file

@ -0,0 +1,13 @@
io.write("Searching... ")
local device = tty_search()
io.write("done\r\n")
for i in ipairs(device) do
io.write("\r\n" .. device[i]["path"] .. "\r\n")
io.write(" tid = " .. device[i]["tid"] .. "\r\n")
io.write(" uptime = " .. device[i]["uptime"] .. "\r\n")
io.write(" driver = " .. device[i]["driver"] .. "\r\n")
io.write(" description = " .. device[i]["description"] .. "\r\n")
end

View file

@ -80,17 +80,48 @@ The default pulse duration for each line is 100 ms.
.RE .RE
.TP .TP
.BR \-n ", " \-\-no\-autoconnect .BR "\-a, \-\-auto\-connect new|latest|direct"
Disable automatic connect. Automatically connect to serial device according to one of the following
strategies:
By default tio automatically connects to the provided device if present. If the .RS
device is not present, it will wait for it to appear and then connect. If the .TP 10n
connection is lost (eg. device disconnects), it will wait for the device to .IP "\fBnew"
reappear and then reconnect. Automatically connect to first new appearing serial device.
.IP "\fBlatest"
Automatically connect to latest registered serial device.
.IP "\fBdirect"
Connect directly to specified TTY device.
.P
All the listed strategies automatically reconnects according to strategy if
device is not available or connection is lost.
.P
Default value is "direct".
.RE
However, if the \fB\-\-no\-autoconnect\fR option is provided, tio will exit if .TP
the device is not present or an established connection is lost. .BR " \-\-exclude\-devices \fI<pattern>"
Exclude devices by pattern ('*' and '?' supported).
.TP
.BR " \-\-exclude\-drivers \fI<pattern>"
Exclude drivers by pattern ('*' and '?' supported).
.TP
.BR " \-\-exclude\-tids \fI<pattern>"
Exclude topology IDs by pattern ('*' and '?' supported).
.TP
.BR \-n ", " \-\-no\-reconnect
Do not reconnect.
This means that tio will exit if it fails to connect to device or an
established connection is lost.
.TP .TP
.BR \-e ", " "\-\-local\-echo .BR \-e ", " "\-\-local\-echo
@ -103,7 +134,7 @@ Enable local echo.
Enable line timestamp. Enable line timestamp.
.TP .TP
.BR " \-\-timestamp\-format \fI<format> .BR " \-\-timestamp\-format \fI<format>"
Set timestamp format to any of the following timestamp formats: Set timestamp format to any of the following timestamp formats:
.RS .RS
@ -122,7 +153,7 @@ Default format is \fB24hour\fR
.RE .RE
.TP .TP
.BR " \-\-timestamp\-timeout \fI<ms> .BR " \-\-timestamp\-timeout \fI<ms>"
Set timestamp timeout value in milliseconds. Set timestamp timeout value in milliseconds.
@ -132,12 +163,12 @@ printed after elapsed timeout time of no output activity from tty device.
Default value is 200. Default value is 200.
.TP .TP
.BR \-L ", " \-\-list\-devices .BR \-l ", " \-\-list
List available serial devices by ID. List available serial devices.
.TP .TP
.BR \-l ", " \-\-log .BR \-L ", " \-\-log
Enable log to file. Enable log to file.
@ -401,6 +432,17 @@ Send string.
Send file using x/y-modem protocol. Send file using x/y-modem protocol.
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM. Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
.IP "\fBtty_search()"
Search for serial devices.
Returns a table of number indexed tables, one for each serial device found.
Each of these tables contains the serial device information accessible via the
following string indexed elements "path", "tid", "uptime", "driver",
"description".
Returns nil if no serial devices are found.
.IP "\fBexit(code)" .IP "\fBexit(code)"
Exit with exit code. Exit with exit code.
.IP "\fBhigh(line)" .IP "\fBhigh(line)"
@ -476,8 +518,8 @@ Set output character delay
Set output line delay Set output line delay
.IP "\fBline-pulse-duration" .IP "\fBline-pulse-duration"
Set line pulse duration Set line pulse duration
.IP "\fBno-autoconnect" .IP "\fBno-reconnect"
Disable automatic connect Do not reconnect
.IP "\fBlog" .IP "\fBlog"
Enable log to file Enable log to file
.IP "\fBlog-file" .IP "\fBlog-file"
@ -501,9 +543,9 @@ Map characters on input or output
.IP "\fBcolor" .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 "\fBinput-mode" .IP "\fBinput-mode"
Set input mode. Set input mode
.IP "\fBoutput-mode" .IP "\fBoutput-mode"
Set output mode. Set output mode
.IP "\fBsocket" .IP "\fBsocket"
Set socket to redirect I/O to Set socket to redirect I/O to
.IP "\fBprefix-ctrl-key" .IP "\fBprefix-ctrl-key"
@ -521,7 +563,7 @@ Run script from string
.IP "\fBscript-file" .IP "\fBscript-file"
Run script from file Run script from file
.IP "\fBscript-run" .IP "\fBscript-run"
Run script on connect. Run script on connect
.SH "CONFIGURATION FILE EXAMPLES" .SH "CONFIGURATION FILE EXAMPLES"

View file

@ -62,13 +62,37 @@ OPTIONS
The default pulse duration for each line is 100 ms. The default pulse duration for each line is 100 ms.
-n, --no-autoconnect -a, --auto-connect new|latest|direct
Disable automatic connect. Automatically connect to serial device according to one of the following strategies:
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. new Automatically connect to first new appearing serial device.
However, if the --no-autoconnect option is provided, tio will exit if the device is not present or an established connection is lost. latest Automatically connect to latest registered serial device.
direct Connect directly to specified TTY device.
All the listed strategies automatically reconnects according to strategy if device is not available or connection is lost.
Default value is "direct".
--exclude-devices <pattern>
Exclude devices by pattern ('*' and '?' supported).
--exclude-drivers <pattern>
Exclude drivers by pattern ('*' and '?' supported).
--exclude-tids <pattern>
Exclude topology IDs by pattern ('*' and '?' supported).
-n, --no-reconnect
Do not reconnect.
This means that tio will exit if it fails to connect to device or an established connection is lost.
-e, --local-echo -e, --local-echo
@ -92,11 +116,19 @@ OPTIONS
Default format is 24hour Default format is 24hour
-L, --list-devices --timestamp-timeout <ms>
List available serial devices by ID. Set timestamp timeout value in milliseconds.
-l, --log This value only takes effect in hex output mode where timestamps are only printed after elapsed timeout time of no output activity from tty device.
Default value is 200.
-l, --list
List available serial devices.
-L, --log
Enable log to file. Enable log to file.
@ -148,9 +180,15 @@ OPTIONS
If defining more than one flag, the flags must be comma separated. If defining more than one flag, the flags must be comma separated.
--input-mode normal|hex --input-mode normal|hex|line
Set input mode. In hex input mode bytes can be sent by typing the two-character hexadecimal representation of the 1 byte value, e.g.: to send 0xA you must type 0a or 0A. Set input mode.
In normal mode input characters are sent immediately as they are typed.
In hex input mode bytes can be sent by typing the two-character hexadecimal representation of the 1 byte value, e.g.: to send 0xA you must type 0a or 0A.
In line input mode input characters are sent when you press enter. The only editing feature supported in this mode is backspace.
Default value is "normal". Default value is "normal".
@ -216,6 +254,10 @@ OPTIONS
Default value is "none". Default value is "none".
--mute
Mute tio messages.
--script <string> --script <string>
Run script from string. Run script from string.
@ -293,6 +335,10 @@ SCRIPT API
expect(string, timeout) expect(string, timeout)
Expect string - waits for string to match or timeout before continueing. Supports regular expressions. Special characters must be escaped with '\\'. Timeout is in milliseconds, defaults to 0 meaning it will wait forever. Expect string - waits for string to match or timeout before continueing. Supports regular expressions. Special characters must be escaped with '\\'. Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
Returns 1 on successful match, 0 on timeout, or -1 on invalid regular expression.
On successful match it also returns the match string as second return value.
send(string) send(string)
Send string. Send string.
@ -301,6 +347,13 @@ SCRIPT API
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM. Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
tty_search()
Search for serial devices.
Returns a table of number indexed tables, one for each serial device found. Each of these tables contains the serial device information accessible via the following string indexed elements "path", "tid", "uptime", "driver", "description".
Returns nil if no serial devices are found.
exit(code) exit(code)
Exit with exit code. Exit with exit code.
@ -369,7 +422,7 @@ CONFIGURATION FILE
line-pulse-duration Set line pulse duration line-pulse-duration Set line pulse duration
no-autoconnect Disable automatic connect no-reconnect Do not reconnect
log Enable log to file log Enable log to file
@ -387,13 +440,15 @@ CONFIGURATION FILE
timestamp-format Set timestamp format timestamp-format Set timestamp format
timestamp-timeout Set timestamp timeout
map Map characters on input or output map Map characters on input or output
color Colorize tio text using ANSI color code ranging from 0 to 255 color Colorize tio text using ANSI color code ranging from 0 to 255
input-mode Set input mode. input-mode Set input mode
output-mode Set output mode. output-mode Set output mode
socket Set socket to redirect I/O to socket Set socket to redirect I/O to
@ -405,11 +460,13 @@ CONFIGURATION FILE
alert Set alert action on connect/disconnect alert Set alert action on connect/disconnect
mute Mute tio messages
script Run script from string script Run script from string
script-file Run script from file script-file Run script from file
script-run Run script on connect. script-run Run script on connect
CONFIGURATION FILE EXAMPLES CONFIGURATION FILE EXAMPLES
To change the default configuration simply set options like so: To change the default configuration simply set options like so:
@ -530,6 +587,6 @@ WEBSITE
Visit https://tio.github.io Visit https://tio.github.io
AUTHOR AUTHOR
Created by Martin Lund <martin.lund@keep-it-simple.com>. Maintained by Martin Lund <martin.lund@keep-it-simple.com>.
tio 2.8 2023-09-19 tio(1) tio 2.9 2024-04-14 tio(1)

View file

@ -18,7 +18,11 @@ _tio()
-o --output-delay \ -o --output-delay \
-o --output-line-delay \ -o --output-line-delay \
--line-pulse-duration \ --line-pulse-duration \
-n --no-autoconnect \ -a --auto-connect \
--exclude-devices \
--exclude-drivers \
--exclude-tids \
-n --no-reconnect \
-e --local-echo \ -e --local-echo \
-l --log \ -l --log \
--log-file \ --log-file \
@ -29,7 +33,7 @@ _tio()
-t --timestamp \ -t --timestamp \
--timestamp-format \ --timestamp-format \
--timestamp-timeout \ --timestamp-timeout \
-L --list-devices \ -L --list \
-c --color \ -c --color \
-S --socket \ -S --socket \
--input-mode \ --input-mode \
@ -79,7 +83,11 @@ _tio()
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0 return 0
;; ;;
-n | --no-autoconnect) -a | --auto-connect)
COMPREPLY=( $(compgen -W "new latest none" -- ${cur}) )
return 0
;;
-n | --no-reconnect)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0 return 0
;; ;;
@ -123,7 +131,7 @@ _tio()
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0 return 0
;; ;;
-L | --list-devices) -L | --list)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0 return 0
;; ;;

View file

@ -54,7 +54,7 @@ struct config_t
char *section_name; char *section_name;
char *match; char *match;
char *tty; char *target;
char *flow; char *flow;
char *parity; char *parity;
char *log_filename; char *log_filename;
@ -63,6 +63,9 @@ struct config_t
char *script; char *script;
char *script_filename; char *script_filename;
bool script_run; bool script_run;
char *exclude_devices;
char *exclude_drivers;
char *exclude_tids;
}; };
static struct config_t c; static struct config_t c;
@ -151,8 +154,8 @@ static int data_handler(void *user, const char *section, const char *name,
// Set configuration parameter if found // Set configuration parameter if found
if (!strcmp(name, "device") || !strcmp(name, "tty")) if (!strcmp(name, "device") || !strcmp(name, "tty"))
{ {
asprintf(&c.tty, value, c.match); asprintf(&c.target, value, c.match);
option.tty_device = c.tty; option.target = c.target;
} }
else if (!strcmp(name, "baudrate")) else if (!strcmp(name, "baudrate"))
{ {
@ -188,9 +191,13 @@ static int data_handler(void *user, const char *section, const char *name,
{ {
line_pulse_duration_option_parse(value); line_pulse_duration_option_parse(value);
} }
else if (!strcmp(name, "no-autoconnect")) else if (!strcmp(name, "no-reconnect"))
{ {
option.no_autoconnect = read_boolean(value, name); option.no_reconnect = read_boolean(value, name);
}
else if (!strcmp(name, "auto-connect"))
{
option.auto_connect = auto_connect_option_parse(value);
} }
else if (!strcmp(name, "log")) else if (!strcmp(name, "log"))
{ {
@ -314,6 +321,21 @@ static int data_handler(void *user, const char *section, const char *name,
{ {
option.script_run = script_run_option_parse(value); option.script_run = script_run_option_parse(value);
} }
else if (!strcmp(name, "exclude-devices"))
{
c.exclude_devices = strdup(value);
option.exclude_devices = c.exclude_devices;
}
else if (!strcmp(name, "exclude-drivers"))
{
c.exclude_drivers = strdup(value);
option.exclude_drivers = c.exclude_drivers;
}
else if (!strcmp(name, "exclude-tids"))
{
c.exclude_tids = strdup(value);
option.exclude_tids = c.exclude_tids;
}
else else
{ {
tio_warning_printf("Unknown option '%s' in configuration file, ignored", name); tio_warning_printf("Unknown option '%s' in configuration file, ignored", name);
@ -460,8 +482,8 @@ void config_file_parse(void)
return; return;
} }
// Set user input which may be tty device or sub config // Set user input which may be tty device or sub config or tid
c.user = option.tty_device; c.user = option.target;
if (!c.user) if (!c.user)
{ {
@ -504,7 +526,7 @@ void config_file_parse(void)
void config_exit(void) void config_exit(void)
{ {
free(c.tty); free(c.target);
free(c.flow); free(c.flow);
free(c.parity); free(c.parity);
free(c.log_filename); free(c.log_filename);

View file

@ -93,7 +93,7 @@ void error_exit(void)
/* Print error */ /* Print error */
error_printf_("Error: %s", error[0]); error_printf_("Error: %s", error[0]);
} }
else if ((error[1][0] != 0) && (option.no_autoconnect)) else if ((error[1][0] != 0) && (option.no_reconnect))
{ {
/* Print silent error */ /* Print silent error */
error_printf_("Error: %s", error[1]); error_printf_("Error: %s", error[1]);

205
src/fs.c Normal file
View file

@ -0,0 +1,205 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2024 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#define _GNU_SOURCE // For statx()
#include "config.h"
#include <dirent.h>
#include <fcntl.h>
#include <regex.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>
#include <sys/poll.h>
#include <termios.h>
#include "error.h"
#include "print.h"
#include "options.h"
bool fs_dir_exists(const char *path)
{
struct stat st;
if (stat(path, &st) != 0)
{
return false;
}
else if (!S_ISDIR(st.st_mode))
{
return false;
}
return true;
}
// Function to read the content of a file but stripped of newline
ssize_t fs_read_file_stripped(char *buf, size_t bufsiz, const char *format, ...)
{
char filename[PATH_MAX];
int bytes_printed = 0;
va_list args;
va_start(args, format);
bytes_printed = vsnprintf(filename, sizeof(filename), format, args);
va_end(args);
if (bytes_printed < 0)
{
return -1;
}
FILE *file = fopen(filename, "r");
if (!file)
{
return -1;
}
ssize_t length = fread(buf, 1, bufsiz - 1, file);
if (length == -1)
{
fclose(file);
return -1;
}
// Strip any newline
buf[strcspn(buf, "\n")] = 0;
buf[length] = '\0'; // Make sure to null-terminate the string
fclose(file);
return length;
}
bool fs_file_exists(const char *format, ...)
{
char filename[PATH_MAX];
int bytes_printed = 0;
struct stat st;
va_list args;
va_start(args, format);
bytes_printed = vsnprintf(filename, sizeof(filename), format, args);
va_end(args);
if (bytes_printed < 0)
{
return false;
}
return stat(filename, &st) == 0;
}
char* fs_search_directory(const char *dir_path, const char *dirname)
{
struct dirent *entry;
char path[PATH_MAX];
struct stat st;
DIR *dir;
if ((dir = opendir(dir_path)) == NULL)
{
// Error opening directory
return NULL;
}
while ((entry = readdir(dir)) != NULL)
{
snprintf(path, PATH_MAX, "%s/%s", dir_path, entry->d_name);
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
if (lstat(path, &st) == -1)
{
// Error getting directory status
closedir(dir);
return NULL;
}
if (S_ISLNK(st.st_mode))
{
// Skip symbolic links
continue;
}
if (S_ISDIR(st.st_mode))
{
// If it's a directory, check if it's the one we're looking for
if (strcmp(entry->d_name, dirname) == 0)
{
char* result = malloc(strlen(path) + 1);
if (result == NULL)
{
// Error allocating memory
closedir(dir);
return NULL;
}
strcpy(result, path);
closedir(dir);
return result;
}
else
{
// Recursively search within directories
char* result = fs_search_directory(path, dirname);
if (result != NULL)
{
closedir(dir);
return result;
}
}
}
}
closedir(dir);
return NULL;
}
// Function to return creation time of file
double fs_get_creation_time(const char *path)
{
struct statx stx;
int fd = open(path, O_RDONLY);
if (fd == -1)
{
// Error
return -1;
}
int ret = statx(fd, "", AT_EMPTY_PATH, STATX_ALL, &stx);
if (ret == -1)
{
// Error
close(fd);
return -1;
}
// Close the file
close(fd);
return stx.stx_btime.tv_sec + stx.stx_btime.tv_nsec / 1e9;
}

31
src/fs.h Normal file
View file

@ -0,0 +1,31 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2024 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
#include <stdbool.h>
#include <stdio.h>
bool fs_dir_exists(const char *path);
bool fs_file_exists(const char *format, ...);
char* fs_search_directory(const char *dir_path, const char *dirname);
ssize_t fs_read_file_stripped(char *buf, size_t bufsiz, const char *format, ...);
double fs_get_creation_time(const char *path);

View file

@ -33,7 +33,7 @@
#include "options.h" #include "options.h"
#include "print.h" #include "print.h"
#include "error.h" #include "error.h"
#include "misc.h" #include "fs.h"
#define IS_ESC_CSI_INTERMEDIATE_CHAR(c) ((c >= 0x20) && (c <= 0x3F)) #define IS_ESC_CSI_INTERMEDIATE_CHAR(c) ((c >= 0x20) && (c <= 0x3F))
#define IS_ESC_END_CHAR(c) ((c >= 0x30) && (c <= 0x7E)) #define IS_ESC_END_CHAR(c) ((c >= 0x30) && (c <= 0x7E))
@ -64,8 +64,20 @@ int log_open(const char *filename)
if (filename == NULL) if (filename == NULL)
{ {
// Generate filename if none provided ("tio_DEVICE_YYYY-MM-DDTHH:MM:SS.log") // Generate filename if none provided
asprintf(&automatic_filename, "tio_%s_%s.log", basename((char *)option.tty_device), date_time()); if (option.auto_connect == AUTO_CONNECT_DIRECT)
{
// File name format ("tio_TARGET_YYYY-MM-DDTHH:MM:SS.log")
asprintf(&automatic_filename, "tio_%s_%s.log", basename((char *)option.target), date_time());
}
else
{
// If using 'new' or 'latest' autoconnect strategy we simply use strategy
// name to name autogenerated log name as device names may vary
asprintf(&automatic_filename, "tio_%s_%s.log",
auto_connect_state_to_string(option.auto_connect),
date_time());
}
if (option.log_directory != NULL) if (option.log_directory != NULL)
{ {

View file

@ -121,8 +121,9 @@ int main(int argc, char *argv[])
tty_input_thread_wait_ready(); tty_input_thread_wait_ready();
/* Connect to tty device */ /* Connect to tty device */
if (option.no_autoconnect) if (option.no_reconnect)
{ {
tty_search();
status = tty_connect(); status = tty_connect();
} }
else else
@ -131,7 +132,7 @@ int main(int argc, char *argv[])
while (true) while (true)
{ {
tty_wait_for_device(); tty_wait_for_device();
status = tty_connect(); tty_connect();
} }
} }

View file

@ -19,7 +19,8 @@ tio_sources = [
'timestamp.c', 'timestamp.c',
'alert.c', 'alert.c',
'xymodem.c', 'xymodem.c',
'script.c' 'script.c',
'fs.c'
] ]
@ -35,6 +36,7 @@ endif
tio_dep = [ tio_dep = [
dependency('threads', required: true), dependency('threads', required: true),
dependency('glib-2.0', required: true),
dependency('inih', required: true, dependency('inih', required: true,
fallback : ['libinih', 'inih_dep'], fallback : ['libinih', 'inih_dep'],
default_options: ['default_library=static', 'distro_install=false']), default_options: ['default_library=static', 'distro_install=false']),

View file

@ -20,7 +20,10 @@
*/ */
#include "config.h" #include "config.h"
#include <ctype.h>
#include <dirent.h>
#include <regex.h> #include <regex.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@ -29,6 +32,7 @@
#include <time.h> #include <time.h>
#include <errno.h> #include <errno.h>
#include <sys/poll.h> #include <sys/poll.h>
#include <termios.h>
#include "error.h" #include "error.h"
#include "print.h" #include "print.h"
#include "options.h" #include "options.h"
@ -74,22 +78,6 @@ int ctrl_key_code(unsigned char key)
return -1; return -1;
} }
bool fs_dir_exists(const char *path)
{
struct stat st;
if (stat(path, &st) != 0)
{
return false;
}
else if (!S_ISDIR(st.st_mode))
{
return false;
}
return true;
}
bool regex_match(const char *string, const char *pattern) bool regex_match(const char *string, const char *pattern)
{ {
regex_t regex; regex_t regex;
@ -141,3 +129,116 @@ int read_poll(int fd, void *data, size_t len, int timeout)
/* Timeout */ /* Timeout */
return ret; return ret;
} }
// Function to calculate djb2 hash of string
unsigned long djb2_hash(const unsigned char *str)
{
unsigned long hash = 5381;
int c;
while ((c = *str++))
{
hash = ((hash << 5) + hash) + c; // hash * 33 + c
}
return hash;
}
// Function to encode a number to base62
char *base62_encode(unsigned long num)
{
const char base62_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
char *output = (char *) malloc(5); // 4 characters + null terminator
if (output == NULL)
{
tio_error_print("Memory allocation failed");
exit(EXIT_FAILURE);
}
for (int i = 0; i < 4; ++i)
{
output[i] = base62_chars[num % 62];
num /= 62;
}
output[4] = '\0';
return output;
}
// Function to return current time
double get_current_time(void)
{
struct timespec current_time_ts;
if (clock_gettime(CLOCK_REALTIME, &current_time_ts) == -1)
{
// Error
return -1;
}
return current_time_ts.tv_sec + current_time_ts.tv_nsec / 1e9;
}
// Function to match string with comma separated patterns which supports '*' and '?'
static bool is_match(const char *str, const char *pattern)
{
// If both string and pattern reach end, they match
if (*str == '\0' && *pattern == '\0')
{
return true;
}
// If pattern reaches end but string has characters left, no match
if (*pattern == '\0')
{
return false;
}
// If current characters match or pattern has '?', move to the next character in both
if (*str == *pattern || *pattern == '?')
{
return is_match(str + 1, pattern + 1);
}
// If current pattern character is '*', check for matches by moving string or pattern
if (*pattern == '*')
{
// '*' matches zero or more characters, so try all possibilities
// Move pattern to the next character and check if remaining pattern matches remaining string
// Move string to the next character and check if current pattern matches remaining string
return is_match(str, pattern + 1) || is_match(str + 1, pattern);
}
// No match
return false;
}
bool match_any_pattern(const char *str, const char *patterns)
{
if ((str == NULL) || (patterns == NULL))
{
return false;
}
char *patterns_copy = strdup(patterns);
if (patterns_copy == NULL)
{
tio_error_print("Memory allocation failed");
exit(EXIT_FAILURE);
}
char *token = strtok(patterns_copy, ",");
while (token != NULL)
{
if (is_match(str, token))
{
free(patterns_copy);
return true;
}
token = strtok(NULL, ",");
}
free(patterns_copy);
return false;
}

View file

@ -26,12 +26,14 @@
#define UNUSED(expr) do { (void)(expr); } while (0) #define UNUSED(expr) do { (void)(expr); } while (0)
char * current_time(void);
void delay(long ms); void delay(long ms);
long string_to_long(char *string); long string_to_long(char *string);
int ctrl_key_code(unsigned char key); int ctrl_key_code(unsigned char key);
void alert_connect(void); void alert_connect(void);
void alert_disconnect(void); void alert_disconnect(void);
bool fs_dir_exists(const char *path);
bool regex_match(const char *string, const char *pattern); bool regex_match(const char *string, const char *pattern);
unsigned long djb2_hash(const unsigned char *str);
char *base62_encode(unsigned long num);
int read_poll(int fd, void *data, size_t len, int timeout); int read_poll(int fd, void *data, size_t len, int timeout);
double get_current_time(void);
bool match_any_pattern(const char *str, const char *patterns);

View file

@ -61,12 +61,15 @@ enum opt_t
OPT_SCRIPT_RUN, OPT_SCRIPT_RUN,
OPT_INPUT_MODE, OPT_INPUT_MODE,
OPT_OUTPUT_MODE, OPT_OUTPUT_MODE,
OPT_EXCLUDE_DEVICES,
OPT_EXCLUDE_DRIVERS,
OPT_EXCLUDE_TIDS,
}; };
/* Default options */ /* Default options */
struct option_t option = struct option_t option =
{ {
.tty_device = "", .target = "",
.baudrate = 115200, .baudrate = 115200,
.databits = 8, .databits = 8,
.flow = "none", .flow = "none",
@ -80,7 +83,8 @@ struct option_t option =
.dsr_pulse_duration = 100, .dsr_pulse_duration = 100,
.dcd_pulse_duration = 100, .dcd_pulse_duration = 100,
.ri_pulse_duration = 100, .ri_pulse_duration = 100,
.no_autoconnect = false, .no_reconnect = false,
.auto_connect = AUTO_CONNECT_DIRECT,
.log = false, .log = false,
.log_append = false, .log_append = false,
.log_filename = NULL, .log_filename = NULL,
@ -107,15 +111,18 @@ struct option_t option =
.script_filename = NULL, .script_filename = NULL,
.script_run = SCRIPT_RUN_ALWAYS, .script_run = SCRIPT_RUN_ALWAYS,
.timestamp_timeout = 200, .timestamp_timeout = 200,
.exclude_devices = NULL,
.exclude_drivers = NULL,
.exclude_tids = NULL,
}; };
void print_help(char *argv[]) void print_help(char *argv[])
{ {
UNUSED(argv); UNUSED(argv);
printf("Usage: tio [<options>] <tty-device|sub-config>\n"); printf("Usage: tio [<options>] <tty-device|sub-config|tid>\n");
printf("\n"); printf("\n");
printf("Connect to TTY device directly or via sub-configuration.\n"); printf("Connect to TTY device directly or via sub-configuration or topology ID.\n");
printf("\n"); printf("\n");
printf("Options:\n"); printf("Options:\n");
printf(" -b, --baudrate <bps> Baud rate (default: 115200)\n"); printf(" -b, --baudrate <bps> Baud rate (default: 115200)\n");
@ -126,15 +133,19 @@ void print_help(char *argv[])
printf(" -o, --output-delay <ms> Output character delay (default: 0)\n"); printf(" -o, --output-delay <ms> Output character delay (default: 0)\n");
printf(" -O, --output-line-delay <ms> Output line delay (default: 0)\n"); printf(" -O, --output-line-delay <ms> Output line delay (default: 0)\n");
printf(" --line-pulse-duration <duration> Set line pulse duration\n"); printf(" --line-pulse-duration <duration> Set line pulse duration\n");
printf(" -n, --no-autoconnect Disable automatic connect\n"); printf(" -a, --auto-connect new|latest|direct Automatic connect strategy (default: direct)\n");
printf(" --exclude-devices <pattern> Exclude devices by pattern\n");
printf(" --exclude-drivers <pattern> Exclude drivers by pattern\n");
printf(" --exclude-tids <pattern> Exclude topology IDs by pattern\n");
printf(" -n, --no-reconnect Do not reconnect\n");
printf(" -e, --local-echo Enable local echo\n"); printf(" -e, --local-echo Enable local echo\n");
printf(" --input-mode normal|hex|line Select input mode (default: normal)\n"); printf(" --input-mode normal|hex|line Select input mode (default: normal)\n");
printf(" --output-mode normal|hex Select output mode (default: normal)\n"); printf(" --output-mode normal|hex Select output mode (default: normal)\n");
printf(" -t, --timestamp Enable line timestamp\n"); printf(" -t, --timestamp Enable line timestamp\n");
printf(" --timestamp-format <format> Set timestamp format (default: 24hour)\n"); printf(" --timestamp-format <format> Set timestamp format (default: 24hour)\n");
printf(" --timestamp-timeout <ms> Set timestamp timeout (default: 200)\n"); printf(" --timestamp-timeout <ms> Set timestamp timeout (default: 200)\n");
printf(" -L, --list-devices List available serial devices by ID\n"); printf(" -l, --list List available serial devices\n");
printf(" -l, --log Enable log to file\n"); printf(" -L, --log Enable log to file\n");
printf(" --log-file <filename> Set log filename\n"); printf(" --log-file <filename> Set log filename\n");
printf(" --log-directory <path> Set log directory path for automatic named logs\n"); printf(" --log-directory <path> Set log directory path for automatic named logs\n");
printf(" --log-append Append to log file\n"); printf(" --log-append Append to log file\n");
@ -157,6 +168,44 @@ void print_help(char *argv[])
printf("See the man page for more details.\n"); printf("See the man page for more details.\n");
} }
const char *auto_connect_state_to_string(auto_connect_t strategy)
{
switch (strategy)
{
case AUTO_CONNECT_DIRECT:
return "direct";
case AUTO_CONNECT_NEW:
return "new";
case AUTO_CONNECT_LATEST:
return "latest";
default:
return "Unknown";
}
}
auto_connect_t auto_connect_option_parse(const char *arg)
{
auto_connect_t auto_connect = option.auto_connect; // Default
if (arg != NULL)
{
if (strcmp(arg, "direct") == 0)
{
return AUTO_CONNECT_DIRECT;
}
else if (strcmp(arg, "new") == 0)
{
return AUTO_CONNECT_NEW;
}
else if (strcmp(arg, "latest") == 0)
{
return AUTO_CONNECT_LATEST;
}
}
return auto_connect;
}
void line_pulse_duration_option_parse(const char *arg) void line_pulse_duration_option_parse(const char *arg)
{ {
bool token_found = true; bool token_found = true;
@ -312,7 +361,7 @@ script_run_t script_run_option_parse(const char *arg)
void options_print() void options_print()
{ {
tio_printf(" Device: %s", option.tty_device); tio_printf(" Device: %s", device_name);
tio_printf(" Baudrate: %u", option.baudrate); tio_printf(" Baudrate: %u", option.baudrate);
tio_printf(" Databits: %d", option.databits); tio_printf(" Databits: %d", option.databits);
tio_printf(" Flow: %s", option.flow); tio_printf(" Flow: %s", option.flow);
@ -323,7 +372,8 @@ void options_print()
tio_printf(" Timestamp timeout: %u", option.timestamp_timeout); tio_printf(" Timestamp timeout: %u", option.timestamp_timeout);
tio_printf(" Output delay: %d", option.output_delay); tio_printf(" Output delay: %d", option.output_delay);
tio_printf(" Output line delay: %d", option.output_line_delay); tio_printf(" Output line delay: %d", option.output_line_delay);
tio_printf(" Auto connect: %s", option.no_autoconnect ? "disabled" : "enabled"); tio_printf(" Automatic connect strategy: %s", auto_connect_state_to_string(option.auto_connect));
tio_printf(" Automatic reconnect: %s", option.no_reconnect ? "disabled" : "enabled");
tio_printf(" Pulse duration: DTR=%d RTS=%d CTS=%d DSR=%d DCD=%d RI=%d", option.dtr_pulse_duration, tio_printf(" Pulse duration: DTR=%d RTS=%d CTS=%d DSR=%d DCD=%d RI=%d", option.dtr_pulse_duration,
option.rts_pulse_duration, option.rts_pulse_duration,
option.cts_pulse_duration, option.cts_pulse_duration,
@ -385,13 +435,17 @@ void options_parse(int argc, char *argv[])
{"output-delay", required_argument, 0, 'o' }, {"output-delay", required_argument, 0, 'o' },
{"output-line-delay" , required_argument, 0, 'O' }, {"output-line-delay" , required_argument, 0, 'O' },
{"line-pulse-duration", required_argument, 0, OPT_LINE_PULSE_DURATION }, {"line-pulse-duration", required_argument, 0, OPT_LINE_PULSE_DURATION },
{"no-autoconnect", no_argument, 0, 'n' }, {"auto-connect", required_argument, 0, 'a' },
{"exclude-devices", required_argument, 0, OPT_EXCLUDE_DEVICES },
{"exclude-drivers", required_argument, 0, OPT_EXCLUDE_DRIVERS },
{"exclude-tids", required_argument, 0, OPT_EXCLUDE_TIDS },
{"no-reconnect", no_argument, 0, 'n' },
{"local-echo", no_argument, 0, 'e' }, {"local-echo", no_argument, 0, 'e' },
{"timestamp", no_argument, 0, 't' }, {"timestamp", no_argument, 0, 't' },
{"timestamp-format", required_argument, 0, OPT_TIMESTAMP_FORMAT }, {"timestamp-format", required_argument, 0, OPT_TIMESTAMP_FORMAT },
{"timestamp-timeout", required_argument, 0, OPT_TIMESTAMP_TIMEOUT }, {"timestamp-timeout", required_argument, 0, OPT_TIMESTAMP_TIMEOUT },
{"list-devices", no_argument, 0, 'L' }, {"list", no_argument, 0, 'l' },
{"log", no_argument, 0, 'l' }, {"log", no_argument, 0, 'L' },
{"log-file", required_argument, 0, OPT_LOG_FILE }, {"log-file", required_argument, 0, OPT_LOG_FILE },
{"log-directory", required_argument, 0, OPT_LOG_DIRECTORY }, {"log-directory", required_argument, 0, OPT_LOG_DIRECTORY },
{"log-append", no_argument, 0, OPT_LOG_APPEND }, {"log-append", no_argument, 0, OPT_LOG_APPEND },
@ -418,7 +472,7 @@ void options_parse(int argc, char *argv[])
int option_index = 0; int option_index = 0;
/* Parse argument using getopt_long */ /* Parse argument using getopt_long */
c = getopt_long(argc, argv, "b:d:f:s:p:o:O:netLlS:m:c:xrvh", long_options, &option_index); c = getopt_long(argc, argv, "b:d:f:s:p:o:O:a:netLlS:m:c:xrvh", long_options, &option_index);
/* Detect the end of the options */ /* Detect the end of the options */
if (c == -1) if (c == -1)
@ -468,8 +522,24 @@ void options_parse(int argc, char *argv[])
line_pulse_duration_option_parse(optarg); line_pulse_duration_option_parse(optarg);
break; break;
case 'a':
option.auto_connect = auto_connect_option_parse(optarg);
break;
case OPT_EXCLUDE_DEVICES:
option.exclude_devices = optarg;
break;
case OPT_EXCLUDE_DRIVERS:
option.exclude_drivers = optarg;
break;
case OPT_EXCLUDE_TIDS:
option.exclude_tids = optarg;
break;
case 'n': case 'n':
option.no_autoconnect = true; option.no_reconnect = true;
break; break;
case 'e': case 'e':
@ -489,12 +559,12 @@ void options_parse(int argc, char *argv[])
break; break;
case 'L': case 'L':
list_serial_devices(); option.log = true;
exit(EXIT_SUCCESS);
break; break;
case 'l': case 'l':
option.log = true; list_serial_devices();
exit(EXIT_SUCCESS);
break; break;
case OPT_LOG_FILE: case OPT_LOG_FILE:
@ -611,24 +681,33 @@ void options_parse(int argc, char *argv[])
} }
} }
/* Assume first non-option is the tty device name */ /* Assume first non-option is the target (tty device, sub-config, tid) */
if (strcmp(option.tty_device, "")) if (strcmp(option.target, ""))
{
optind++; optind++;
}
else if (optind < argc) else if (optind < argc)
option.tty_device = argv[optind++]; {
option.target = argv[optind++];
}
if (option.complete_sub_configs) if (option.complete_sub_configs)
{ {
return; return;
} }
if (strlen(option.tty_device) == 0) if (option.auto_connect != AUTO_CONNECT_DIRECT)
{ {
tio_error_printf("Missing tty device or sub-configuration name"); return;
}
if (strlen(option.target) == 0)
{
tio_error_printf("Missing tty device, sub-configuration or topology ID");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/* Print any remaining command line arguments (unknown options) */ /* Print any remaining command line arguments as unknown */
if (optind < argc) if (optind < argc)
{ {
fprintf(stderr, "Error: Unknown argument "); fprintf(stderr, "Error: Unknown argument ");
@ -643,8 +722,8 @@ void options_parse(int argc, char *argv[])
void options_parse_final(int argc, char *argv[]) void options_parse_final(int argc, char *argv[])
{ {
/* Preserve tty device which may have been set by configuration file */ /* Preserve target which may have been set by configuration file */
const char *tty_device = option.tty_device; const char *target = option.target;
/* Do 2nd pass to override settings set by configuration file */ /* Do 2nd pass to override settings set by configuration file */
optind = 1; // Reset option index to restart scanning of argv optind = 1; // Reset option index to restart scanning of argv
@ -653,16 +732,16 @@ void options_parse_final(int argc, char *argv[])
#ifdef __CYGWIN__ #ifdef __CYGWIN__
unsigned char portnum; unsigned char portnum;
char *tty_win; char *tty_win;
if ( ((strncmp("COM", tty_device, 3) == 0) if ( ((strncmp("COM", target, 3) == 0)
|| (strncmp("com", tty_device, 3) == 0) ) || (strncmp("com", target, 3) == 0) )
&& (sscanf(tty_device + 3, "%hhu", &portnum) == 1) && (sscanf(target + 3, "%hhu", &portnum) == 1)
&& (portnum > 0) ) && (portnum > 0) )
{ {
asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1); asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1);
tty_device = tty_win; target = tty_win;
} }
#endif #endif
/* Restore tty device */ /* Restore target */
option.tty_device = tty_device; option.target = target;
} }

View file

@ -29,6 +29,7 @@
#include "script.h" #include "script.h"
#include "timestamp.h" #include "timestamp.h"
#include "alert.h" #include "alert.h"
#include "tty.h"
typedef enum typedef enum
{ {
@ -48,7 +49,7 @@ typedef enum
/* Options */ /* Options */
struct option_t struct option_t
{ {
const char *tty_device; const char *target;
unsigned int baudrate; unsigned int baudrate;
int databits; int databits;
char *flow; char *flow;
@ -62,7 +63,8 @@ struct option_t
unsigned int dsr_pulse_duration; unsigned int dsr_pulse_duration;
unsigned int dcd_pulse_duration; unsigned int dcd_pulse_duration;
unsigned int ri_pulse_duration; unsigned int ri_pulse_duration;
bool no_autoconnect; bool no_reconnect;
auto_connect_t auto_connect;
bool log; bool log;
bool log_append; bool log_append;
bool log_strip; bool log_strip;
@ -89,6 +91,9 @@ struct option_t
const char *script_filename; const char *script_filename;
script_run_t script_run; script_run_t script_run;
unsigned int timestamp_timeout; unsigned int timestamp_timeout;
const char *exclude_devices;
const char *exclude_drivers;
const char *exclude_tids;
}; };
extern struct option_t option; extern struct option_t option;
@ -102,3 +107,5 @@ script_run_t script_run_option_parse(const char *arg);
input_mode_t input_mode_option_parse(const char *arg); input_mode_t input_mode_option_parse(const char *arg);
output_mode_t output_mode_option_parse(const char *arg); output_mode_t output_mode_option_parse(const char *arg);
auto_connect_t auto_connect_option_parse(const char *arg);
const char *auto_connect_state_to_string(auto_connect_t strategy);

View file

@ -36,6 +36,7 @@
#include "xymodem.h" #include "xymodem.h"
#include "log.h" #include "log.h"
#include "script.h" #include "script.h"
#include "fs.h"
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer #define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
@ -44,6 +45,9 @@ static char circular_buffer[MAX_BUFFER_SIZE];
static char match_string[MAX_BUFFER_SIZE]; static char match_string[MAX_BUFFER_SIZE];
static int buffer_size = 0; static int buffer_size = 0;
static char init_script[] =
"\n";
// lua: sleep(seconds) // lua: sleep(seconds)
static int sleep_(lua_State *L) static int sleep_(lua_State *L)
{ {
@ -401,6 +405,60 @@ static int exit_(lua_State *L)
return 0; return 0;
} }
// lua: list = tty_search()
static int tty_search_(lua_State *L)
{
UNUSED(L);
GList *iter;
int i = 1;
GList *device_list = tty_search_for_serial_devices();
if (device_list == NULL)
{
return 0;
}
// Create a new table
lua_newtable(L);
// Iterate through found devices
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
{
device_t *device = (device_t *) iter->data;
// Create a new sub-table for each serial device
lua_newtable(L);
// Add elements to the table
lua_pushstring(L, "path");
lua_pushstring(L, device->path);
lua_settable(L, -3);
lua_pushstring(L, "tid");
lua_pushstring(L, device->tid);
lua_settable(L, -3);
lua_pushstring(L, "uptime");
lua_pushnumber(L, device->uptime);
lua_settable(L, -3);
lua_pushstring(L, "driver");
lua_pushstring(L, device->driver);
lua_settable(L, -3);
lua_pushstring(L, "description");
lua_pushstring(L, device->description);
lua_settable(L, -3);
// Set the sub-table as a row in the main table
lua_rawseti(L, -2, i++);
}
// Return table
return 1;
}
static void script_buffer_run(lua_State *L, const char *script_buffer) static void script_buffer_run(lua_State *L, const char *script_buffer)
{ {
int error; int error;
@ -410,7 +468,7 @@ static void script_buffer_run(lua_State *L, const char *script_buffer)
if (error) if (error)
{ {
tio_warning_printf("lua: %s\n", lua_tostring(L, -1)); tio_warning_printf("lua: %s\n", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */ lua_pop(L, 1); /* Pop error message from the stack */
} }
} }
@ -429,6 +487,7 @@ static const struct luaL_Reg tio_lib[] =
{ "read", read_string}, { "read", read_string},
{ "expect", expect}, { "expect", expect},
{ "exit", exit_}, { "exit", exit_},
{ "tty_search", tty_search_},
{NULL, NULL} {NULL, NULL}
}; };
@ -451,6 +510,18 @@ static void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup)
} }
#endif #endif
static void load_init_script(lua_State *L)
{
int error;
error = luaL_loadbuffer(L, init_script, strlen(init_script), "tio") || lua_pcall(L, 0, 0, 0);
if (error)
{
tio_error_print("%s\n", lua_tostring(L, -1));
lua_pop(L, 1); // Pop error message from the stack
}
}
int lua_register_tio(lua_State *L) int lua_register_tio(lua_State *L)
{ {
// Register lxi functions // Register lxi functions
@ -458,6 +529,8 @@ int lua_register_tio(lua_State *L)
luaL_setfuncs(L, tio_lib, 0); luaL_setfuncs(L, tio_lib, 0);
lua_pop(L, 1); lua_pop(L, 1);
load_init_script(L);
return 0; return 0;
} }

569
src/tty.c
View file

@ -19,7 +19,11 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#if defined(__linux__)
#include <linux/serial.h>
#endif
#include "config.h" #include "config.h"
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/time.h> #include <sys/time.h>
@ -42,6 +46,7 @@
#include <time.h> #include <time.h>
#include <dirent.h> #include <dirent.h>
#include <pthread.h> #include <pthread.h>
#include <glib.h>
#include "config.h" #include "config.h"
#include "configfile.h" #include "configfile.h"
#include "tty.h" #include "tty.h"
@ -58,11 +63,13 @@
#include "misc.h" #include "misc.h"
#include "script.h" #include "script.h"
#include "xymodem.h" #include "xymodem.h"
#include "fs.h"
/* tty device listing configuration */ /* tty device listing configuration */
#if defined(__linux__) #if defined(__linux__)
#define PATH_SERIAL_DEVICES "/dev/serial/by-id/" #define PATH_SERIAL_DEVICES "/dev/serial/by-id/"
#define PATH_SERIAL_DEVICES_BY_PATH "/dev/serial/by-path/"
#define PREFIX_TTY_DEVICES "" #define PREFIX_TTY_DEVICES ""
#elif defined(__FreeBSD__) #elif defined(__FreeBSD__)
#define PATH_SERIAL_DEVICES "/dev/" #define PATH_SERIAL_DEVICES "/dev/"
@ -158,6 +165,8 @@ bool map_ign_cr = false;
char key_hit = 0xff; char key_hit = 0xff;
const char* device_name;
GList *device_list = NULL;
static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old; static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old;
static unsigned long rx_total = 0, tx_total = 0; static unsigned long rx_total = 0, tx_total = 0;
static bool connected = false; static bool connected = false;
@ -1336,6 +1345,539 @@ void tty_configure(void)
free(buffer); free(buffer);
} }
static bool is_serial_device(const char *format, ...)
{
char filename[PATH_MAX];
struct winsize ws;
int bytes_printed;
int status = true;
struct stat st;
va_list args;
int fd = -1;
va_start(args, format);
bytes_printed = vsnprintf(filename, sizeof(filename), format, args);
va_end(args);
if (bytes_printed < 0)
{
return false;
}
if (stat(filename, &st) != 0)
{
return false;
}
// Make sure it is a character device
if ((st.st_mode & S_IFMT) != S_IFCHR)
{
return false;
}
fd = open(filename, O_RDONLY | O_NONBLOCK | O_NOCTTY);
if (fd == -1)
{
return false;
}
// Make sure it is a tty
status = isatty(fd);
if (status == 0)
{
goto error;
}
// Serial devices do not have rows and columns
status = ioctl(fd, TIOCGWINSZ, &ws);
if (status == 0)
{
status = true;
if (ws.ws_row && ws.ws_col)
{
status = false;
goto error;
}
}
error:
close(fd);
return status;
}
static void list_serial_devices_by_id(void)
{
DIR *d = opendir(PATH_SERIAL_DEVICES);
if (d)
{
struct dirent *dir;
printf("By-id\n");
printf("--------------------------------------------------------------------------------\n");
while ((dir = readdir(d)) != NULL)
{
if ((strcmp(dir->d_name, ".")) && (strcmp(dir->d_name, "..")))
{
if (!strncmp(dir->d_name, PREFIX_TTY_DEVICES, sizeof(PREFIX_TTY_DEVICES) - 1))
{
if (is_serial_device("%s%s", PATH_SERIAL_DEVICES, dir->d_name))
{
printf("%s%s\n", PATH_SERIAL_DEVICES, dir->d_name);
}
}
}
}
closedir(d);
}
}
static void list_serial_devices_by_path(void)
{
#ifdef PATH_SERIAL_DEVICES_BY_PATH
DIR *d = opendir(PATH_SERIAL_DEVICES_BY_PATH);
if (d)
{
struct dirent *dir;
printf("\nBy-path\n");
printf("--------------------------------------------------------------------------------\n");
while ((dir = readdir(d)) != NULL)
{
if ((strcmp(dir->d_name, ".")) && (strcmp(dir->d_name, "..")))
{
if (!strncmp(dir->d_name, "", sizeof("") - 1))
{
if (is_serial_device("%s%s", PATH_SERIAL_DEVICES_BY_PATH, dir->d_name))
{
printf("%s%s\n", PATH_SERIAL_DEVICES_BY_PATH, dir->d_name);
}
}
}
}
closedir(d);
}
#endif
}
static gint compare_uptime(gconstpointer a, gconstpointer b)
{
device_t *device_a = (device_t *) a;
device_t *device_b = (device_t *) b;
// Make sure we end up with device with smallest uptime last in list
if (device_a->uptime > device_b->uptime)
return -1;
else if (device_a->uptime < device_b->uptime)
return 1;
else
return 0;
}
#if defined(__linux__)
// Function to get serial port type as a string
const char* get_serial_port_type(const char* port_name)
{
int fd;
static struct serial_struct serial_info;
// Open the serial port
fd = open(port_name, O_RDWR);
if (fd == -1)
{
return "";
}
// Get serial port information
if (ioctl(fd, TIOCGSERIAL, &serial_info) == -1)
{
close(fd);
return "";
}
// Close the serial port
close(fd);
// Return the serial port type as a string
switch (serial_info.type)
{
case PORT_UNKNOWN:
return "Unknown";
case PORT_8250:
return "8250 UART";
case PORT_16450:
return "16450 UART";
case PORT_16550:
return "16550 UART";
case PORT_16550A:
return "16550A UART";
case PORT_16650:
return "16650 UART";
case PORT_16650V2:
return "16650V2 UART";
case PORT_16750:
return "16750 UART";
case PORT_STARTECH:
return "Startech UART";
case PORT_16850:
return "16850 UART";
case PORT_16C950:
return "16C950 UART";
case PORT_16654:
return "16654 UART";
case PORT_RSA:
return "RSA UART";
default:
return "";
}
}
#else
const char* get_serial_port_type(const char* port_name)
{
return "";
}
#endif
static void search_reset(void)
{
GList *iter;
if (g_list_length(device_list) == 0)
{
return;
}
// Free data of all list elements
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
{
device_t *device = (device_t *) iter->data;
g_free(device->tid);
g_free(device->path);
g_free(device->driver);
g_free(device->description);
}
// Free all list elements
g_list_free_full(device_list, g_free);
// Indicate an empty list
device_list = NULL;
}
GList *tty_search_for_serial_devices(void)
{
DIR *dir;
char path[PATH_MAX] = {};
char device_path[PATH_MAX] = {};
char driver_path[PATH_MAX] = {};
double current_time, creation_time;
ssize_t length;
search_reset();
// Open the sysfs directory for the tty subsystem
dir = opendir("/sys/class/tty");
if (!dir)
{
// Error
return NULL;
}
current_time = get_current_time();
// Iterate through each device in the subsystem directory
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
// Skip . and .. entries
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
// Skip non serial devices
if (is_serial_device("/dev/%s", entry->d_name) == false)
{
continue;
}
// Construct the path to the device's device symlink
snprintf(path, sizeof(path), "/sys/class/tty/%s/device", entry->d_name);
// Read the device symlink to get the device path
// Example symlinks:
// /sys/class/tty/ttyUSB0/device -> ../../../ttyUSB0
// /sys/class/tty/ttyACM0/device -> ../../../3-6.4:1.2
length = readlink(path, device_path, sizeof(device_path) - 1);
if (length == -1)
{
continue;
}
// Null-terminate the string
device_path[length] = '\0';
// Extract last part of device path (string after last '/')
// Example resulting device_name:
// "ttyUSB0"
// "3-6.4:1.2"
char *device_name = strrchr(device_path, '/');
device_name++; // Move past the '/'
// Find that part in /sys/devices and return first result string
// Example devices_path:
// "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.3/3-6.3:1.0/ttyUSB0"
// "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.4/3-6.4:1.2"
char *devices_path = fs_search_directory("/sys/devices", device_name);
if (devices_path == NULL)
{
continue;
}
// Remove last part if it contains device short name (e.g ttyUSB0)
// Example resulting devices_path:
// "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.3/3-6.3:1.0"
// "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.4/3-6.4:1.2"
char *last_part = strrchr(devices_path, '/');
last_part++;
if (strcmp(last_part, entry->d_name) == 0)
{
// Remove last part (string after last '/')
char *slash = strrchr(devices_path, '/');
int index = (int) (slash - devices_path);
devices_path[index] = '\0';
}
// Hash remaining string to get unique topology ID
unsigned long hash = djb2_hash((const unsigned char *)devices_path);
char *tid = base62_encode(hash);
free(devices_path);
// Construct the path to the device's driver symlink
snprintf(path, sizeof(path), "/sys/class/tty/%s/device/driver", entry->d_name);
// Read the symlink to get the driver's path
length = readlink(path, driver_path, sizeof(driver_path) - 1);
if (length == -1)
{
continue;
}
// Null-terminate the string
driver_path[length] = '\0';
// Extract the driver name from the path
char *driver = strrchr(driver_path, '/');
if (driver == NULL)
{
continue;
}
driver++; // Move past the last '/'
// Construct the path to the TTY device file
snprintf(path, sizeof(path), "/dev/%s", entry->d_name);
// Calculate uptime
creation_time = fs_get_creation_time(path);
double uptime = current_time - creation_time;
// Read sysfs files to get best possible description of the driver
char description[50] = {};
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/interface", entry->d_name);
if (length == -1)
{
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../interface", entry->d_name);
}
if (length == -1)
{
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../../product", entry->d_name);
}
if (length == -1)
{
snprintf(description, sizeof(description), "%s", get_serial_port_type(path));
}
// Do not add devices excluded by exclude patterns
if (match_any_pattern(path, option.exclude_devices))
{
continue;
}
if (match_any_pattern(driver, option.exclude_drivers))
{
continue;
}
if (match_any_pattern(tid, option.exclude_tids))
{
continue;
}
// Allocate new device item for device list
device_t *device = g_new0(device_t, 1);
if (device == NULL)
{
continue;
}
// Fill in device information
device->path = g_strdup(path);
device->tid = g_strdup(tid);
device->uptime = uptime;
device->driver = g_strdup(driver);
device->description = g_strdup(description);
// Add device information to device list
device_list = g_list_append(device_list, device);
}
if (g_list_length(device_list) == 0)
{
// Return NULL if no serial devices found
return NULL;
}
// Sort device list device with respect to uptime
device_list = g_list_sort(device_list, compare_uptime);
closedir(dir);
return device_list;
}
void list_serial_devices(void)
{
tty_search_for_serial_devices();
if (g_list_length(device_list) > 0)
{
printf("Device TID Uptime [s] Driver Description\n");
printf("----------------- ---- ------------- ---------------- --------------------------\n");
// Iterate through the device list
GList *iter;
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
{
device_t *device = (device_t *) iter->data;
// Print device information
printf("%-17s %4s %13.3f %-16s %s\n", device->path, device->tid, device->uptime, device->driver, device->description);
}
printf("\n");
}
list_serial_devices_by_id();
list_serial_devices_by_path();
}
void tty_search(void)
{
GList *iter;
device_t *device = NULL;
double uptime_minimum = 0;
bool no_new = true;
switch (option.auto_connect)
{
case AUTO_CONNECT_NEW:
tty_search_for_serial_devices();
// Save smallest uptime
if (g_list_length(device_list) > 0)
{
// Get latest registered device (smallest uptime)
GList *last = g_list_last(device_list);
device = last->data;
uptime_minimum = device->uptime;
}
tio_printf("Waiting for tty device..");
while (no_new)
{
tty_search_for_serial_devices();
// Iterate through the device list generated by search
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
{
device = (device_t *) iter->data;
// Find first new device
if (device->uptime < uptime_minimum)
{
// Match found -> update device
device_name = device->path;
no_new = false;
break;
}
}
if (no_new)
{
usleep(500*1000); // Sleep 0.5 s
}
}
return;
case AUTO_CONNECT_LATEST:
tty_search_for_serial_devices();
if (g_list_length(device_list) > 0)
{
// Get latest registered device (smallest uptime)
GList *last = g_list_last(device_list);
device = last->data;
device_name = device->path;
}
return;
case AUTO_CONNECT_DIRECT:
if (strlen(option.target) == TOPOLOGY_ID_SIZE)
{
// Potential topology ID detected -> trigger device search
tty_search_for_serial_devices();
// Iterate through the device list generated by search
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
{
device = (device_t *) iter->data;
if (strcmp(device->tid, option.target) == 0)
{
// Topology ID match found -> use corresponding device name
device_name = device->path;
return;
}
}
}
// Fallback to using tty device provided via cmdline target
device_name = option.target;
break;
default:
// Should never be reached
tio_printf("Unknown connection strategy");
exit(EXIT_FAILURE);
}
}
void tty_wait_for_device(void) void tty_wait_for_device(void)
{ {
fd_set rdfs; fd_set rdfs;
@ -1349,6 +1891,8 @@ void tty_wait_for_device(void)
/* Loop until device pops up */ /* Loop until device pops up */
while (true) while (true)
{ {
tty_search();
if (interactive_mode) if (interactive_mode)
{ {
/* In interactive mode, while waiting for tty device, we need to /* In interactive mode, while waiting for tty device, we need to
@ -1400,7 +1944,7 @@ void tty_wait_for_device(void)
} }
/* Test for accessible device file */ /* Test for accessible device file */
status = access(option.tty_device, R_OK); status = access(device_name, R_OK);
if (status == 0) if (status == 0)
{ {
last_errno = 0; last_errno = 0;
@ -1549,7 +2093,7 @@ int tty_connect(void)
struct timeval tval_before = {}, tval_now, tval_result; struct timeval tval_before = {}, tval_now, tval_result;
/* Open tty device */ /* Open tty device */
device_fd = open(option.tty_device, O_RDWR | O_NOCTTY | O_NONBLOCK); device_fd = open(device_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (device_fd < 0) if (device_fd < 0)
{ {
tio_error_printf_silent("Could not open tty device (%s)", strerror(errno)); tio_error_printf_silent("Could not open tty device (%s)", strerror(errno));
@ -1575,7 +2119,7 @@ int tty_connect(void)
tcflush(device_fd, TCIOFLUSH); tcflush(device_fd, TCIOFLUSH);
/* Print connect status */ /* Print connect status */
tio_printf("Connected"); tio_printf("Connected to %s", device_name);
connected = true; connected = true;
print_tainted = false; print_tainted = false;
@ -2019,22 +2563,3 @@ error_open:
return TIO_ERROR; return TIO_ERROR;
} }
void list_serial_devices(void)
{
DIR *d = opendir(PATH_SERIAL_DEVICES);
if (d)
{
struct dirent *dir;
while ((dir = readdir(d)) != NULL)
{
if ((strcmp(dir->d_name, ".")) && (strcmp(dir->d_name, "..")))
{
if (!strncmp(dir->d_name, PREFIX_TTY_DEVICES, sizeof(PREFIX_TTY_DEVICES) - 1))
{
printf("%s%s\n", PATH_SERIAL_DEVICES, dir->d_name);
}
}
}
closedir(d);
}
}

View file

@ -22,10 +22,31 @@
#pragma once #pragma once
#include <stdbool.h> #include <stdbool.h>
#include <glib.h>
#define LINE_HIGH true #define LINE_HIGH true
#define LINE_LOW false #define LINE_LOW false
#define TOPOLOGY_ID_SIZE 4
typedef enum
{
AUTO_CONNECT_DIRECT,
AUTO_CONNECT_NEW,
AUTO_CONNECT_LATEST,
AUTO_CONNECT_END,
} auto_connect_t;
typedef struct
{
char *tid;
double uptime;
char *path;
char *driver;
char *description;
} device_t;
extern const char *device_name;
extern bool interactive_mode; extern bool interactive_mode;
extern bool map_i_nl_cr; extern bool map_i_nl_cr;
extern bool map_i_cr_nl; extern bool map_i_cr_nl;
@ -43,3 +64,5 @@ void tty_line_set(int fd, int mask, bool value);
void tty_line_toggle(int fd, int mask); void tty_line_toggle(int fd, int mask);
void tty_line_config(int mask, bool value); void tty_line_config(int mask, bool value);
void tty_line_config_apply(void); void tty_line_config_apply(void);
void tty_search(void);
GList *tty_search_for_serial_devices(void);