This commit is contained in:
YABU Hidefumi 2026-04-25 01:29:41 +00:00 committed by GitHub
commit 76f1eb3f29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 4039 additions and 874 deletions

View file

@ -3,3 +3,7 @@ IndentWidth: 4
AllowShortFunctionsOnASingleLine: None AllowShortFunctionsOnASingleLine: None
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
BreakBeforeBraces: Allman BreakBeforeBraces: Allman
IndentCaseLabels: true
ColumnLimit: 144
SortIncludes: false
SkipMacroDefinitionBody: true

105
README.md
View file

@ -43,8 +43,9 @@ when used in combination with [tmux](https://tmux.github.io).
* Useful for reconnecting when serial device has no serial device by ID * Useful for reconnecting when serial device has no serial device by ID
* Support for non-standard baud rates * Support for non-standard baud rates
* Support for mark and space parity * Support for mark and space parity
* X-modem (1K/CRC) and Y-modem file upload * X-modem (1K/CRC/Checksum) and Y-modem file upload
* Support for RS-485 mode * Support for RS-485 mode
* Support for raw mode and switching by key operations
* List available serial devices * List available serial devices
* By device * By device
* Including topology ID, uptime, driver, description * Including topology ID, uptime, driver, description
@ -59,7 +60,7 @@ when used in combination with [tmux](https://tmux.github.io).
* Switchable independent input and output * Switchable independent input and output
* Normal mode * Normal mode
* Hex mode (output supports variable width) * Hex mode (output supports variable width)
* Line mode (input only) * Line mode (input only, it supports line-editing and history)
* 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
@ -87,6 +88,7 @@ when used in combination with [tmux](https://tmux.github.io).
* Remapping of prefix key * Remapping of prefix key
* 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)
* Run script and preload functions and variables when tio starts up
* Simple expect/send like functionality with support for regular expressions * Simple expect/send like functionality with support for regular expressions
* Manipulate port modem lines (useful for microcontroller reset/boot etc.) * Manipulate port modem lines (useful for microcontroller reset/boot etc.)
* Send files via x/y-modem protocol * Send files via x/y-modem protocol
@ -116,12 +118,14 @@ Options:
-p, --parity odd|even|none|mark|space Parity (default: none) -p, --parity odd|even|none|mark|space Parity (default: none)
-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)
--output-line-delay-char cr|lf Output line delay trigger character (default: lf)
--line-pulse-duration <duration> Set line pulse duration --line-pulse-duration <duration> Set line pulse duration
-a, --auto-connect new|latest|direct Automatic connect strategy (default: direct) -a, --auto-connect new|latest|direct Automatic connect strategy (default: direct)
--exclude-devices <pattern> Exclude devices by pattern --exclude-devices <pattern> Exclude devices by pattern
--exclude-drivers <pattern> Exclude drivers by pattern --exclude-drivers <pattern> Exclude drivers by pattern
--exclude-tids <pattern> Exclude topology IDs by pattern --exclude-tids <pattern> Exclude topology IDs by pattern
-n, --no-reconnect Do not reconnect -n, --no-reconnect Do not reconnect
-N, --no-tty-restore Do not restore initial TTY device settings
-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|hexN Select output mode (default: normal) --output-mode normal|hex|hexN Select output mode (default: normal)
@ -135,16 +139,21 @@ Options:
--log-append Append to log file --log-append Append to log file
--log-strip Strip control characters and escape sequences --log-strip Strip control characters and escape sequences
-m, --map <flags> Map characters -m, --map <flags> Map characters
--keymap <keymaps> Set key-script mappings
-c, --color 0..255|bold|none|list Colorize tio text (default: bold) -c, --color 0..255|bold|none|list Colorize tio text (default: bold)
-S, --socket <socket> Redirect I/O to socket -S, --socket <socket> Redirect I/O to socket
--raw off|on|on-nodelay Select raw mode for non-interactive use (default: on)
--raw-interactive off|on|on-nodelay Select raw mode for interactive use (default: off)
--rs-485 Enable RS-485 mode --rs-485 Enable RS-485 mode
--rs-485-config <config> Set RS-485 configuration --rs-485-config <config> Set RS-485 configuration
--alert bell|blink|none Alert on connect/disconnect (default: none) --alert bell|blink|none Alert on connect/disconnect (default: none)
--mute Mute tio messages --mute Mute tio messages
--script-init-file <filename> Set initial script file to run at startup
--script <string> Run script from string --script <string> Run script from string
--script-file <filename> Run script from file --script-file <filename> Run script from file
--script-run once|always|never Run script on connect (default: always) --script-run once|always|never Run script on connect (default: always)
--exec <command> Execute shell command with I/O redirected to device --exec <command> Execute shell command with I/O redirected to device
--complete-profiles Prints profiles (for shell completion)
-v, --version Display version -v, --version Display version
-h, --help Display help -h, --help Display help
@ -312,6 +321,9 @@ ctrl-t ? to list the available key commands.
[15:02:53.269] ctrl-t F Flush data I/O buffers [15:02:53.269] ctrl-t F Flush data I/O buffers
[15:02:53.269] ctrl-t g Toggle serial port line [15:02:53.269] ctrl-t g Toggle serial port line
[15:02:53.269] ctrl-t i Toggle input mode [15:02:53.269] ctrl-t i Toggle input mode
[15:02:53.269] ctrl-t j Toggle raw mode for non-interactive use
[15:02:53.269] ctrl-t J Toggle raw mode for interactive use
[15:02:53.269] ctrl-t k Set key-script mappings
[15:02:53.269] ctrl-t l Clear screen [15:02:53.269] ctrl-t l Clear screen
[15:02:53.269] ctrl-t L Show line states [15:02:53.269] ctrl-t L Show line states
[15:02:53.269] ctrl-t m Change mapping of characters on input or output [15:02:53.269] ctrl-t m Change mapping of characters on input or output
@ -329,6 +341,10 @@ ctrl-t ? to list the available key commands.
``` ```
If needed, the prefix key (ctrl-t) can be remapped via configuration file. If needed, the prefix key (ctrl-t) can be remapped via configuration file.
And you can also map scripts as user key commands using keymap entries in the configuration file.
When key commands request line input, you can edit the line and call the history by using the cursor keys and backspace key.
The history is maintained while tio is running.
### 3.3 Configuration file ### 3.3 Configuration file
@ -353,6 +369,9 @@ databits = 8
parity = none parity = none
stopbits = 1 stopbits = 1
color = 10 color = 10
script-init-file = /home/user/.tio-init-script.lua
# ctrl-t 1 runs setup-script.lua and ctrl-t 9 runs the tio file transfer built-in with xmodem-checksum.
keymap = @1=setup-device.lua @9=!tio.send("firmfile", tio.C.XM_SUM)
[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
@ -395,20 +414,28 @@ Another more elaborate configuration file example is available [here](examples/c
Tio suppots Lua scripting to easily automate interaction with the tty device. Tio suppots Lua scripting to easily automate interaction with the tty device.
In addition to the standard Lua API tio makes the following functions In addition to the standard Lua API tio makes the functions and variables available
and variables available: The following are representative. See the man page for the complete list.:
#### `tio.expect(pattern, timeout)` #### `tio.expect(pattern, timeout)`
Waits for the Lua pattern to match or timeout before continuing. Waits for the Lua pattern to match or timeout before continuing.
Timeout is in milliseconds, defaults to 0 meaning it will wait forever. Timeout is in milliseconds, defaults to tio.C.WAIT_FOREVER(==0) meaning it will wait forever.
Returns the captures from the pattern or `nil` on timeout. Returns the captures from the pattern and all received data if matched.
Or return nil and all received data if timeout.
#### `tio.expects(pattern-table, timeout)`
Waits for any of the multiple Lua pattens to match or timeout before continuing.
Timeout is in milliseconds, defaults to tio.C.WAIT_FOREVER(==0) meaning it will wait forever.
Returns the index of the matched pattern, the captures from it and all recieved data.
Or return nil, nil and all received data if timeout.
#### `tio.read(size, timeout)` #### `tio.read(size, timeout)`
Read up to `size` bytes from serial device. If timeout is 0 or not provided it Read up to `size` bytes from serial device. If timeout is tio.C.WAIT_FOREVER(==0) or not provided it
will wait forever until data is ready to read. will wait forever until data is ready to read.
Returns a string up to `size` bytes long on success and `nil` on timeout. Returns a string up to `size` bytes long on success and `nil` on timeout.
@ -423,15 +450,29 @@ line may be returned as a second return value.
#### `tio.write(string)` #### `tio.write(string)`
Write string to serial device. Write string to serial device without any of input-editing, output-mapping or output-delay.
Returns the `tio` table. Returns the tio table on success or nil on error.
#### `tio.twrite(string)`
Write string to serial device with input-editing, output-mapping and output-delay.
Returns the tio table on success or nil on error.
#### `tio.send(file, protocol)` #### `tio.send(file, protocol)`
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`, `XMODEM_SUM`, `YMODEM`.
Alternatively, it can be one of tio.C.XM_1K, tio.C.XM_CRC, tio.C.XM_SUM, or tio.C.YM_NORMAL.
#### `tio.receive(file, protocol)`
eceive a file using the XMODEM protocol.
protocol can be one of XMODEM_CRC or XMODEM_SUM.
Alternatively, use tio.C.XM_CRC or tio.C.XM_SUM.
#### `tio.ttysearch()` #### `tio.ttysearch()`
@ -450,6 +491,7 @@ Set state of one or multiple tty modem lines.
Line can be any of `DTR`, `RTS`, `CTS`, `DSR`, `CD`, `RI` Line can be any of `DTR`, `RTS`, `CTS`, `DSR`, `CD`, `RI`
State is `high`, `low`, or `toggle`. State is `high`, `low`, or `toggle`.
Alternatively, it can be one of tio.C.LN_HIGH, tio.C.LN_LOW, tio.C.LN_TOGGLE.
#### `tio.sleep(seconds)` #### `tio.sleep(seconds)`
@ -459,6 +501,45 @@ Sleep for seconds.
Sleep for milliseconds. Sleep for milliseconds.
#### `tio.inkey(timeout)`
Read a key press as a string.
timeout is specified in milliseconds.
Defaults to tio.C.NOWAIT (==-1), meaning the call returns immediately.
If set to tio.C.WAIT_FOREVER (==0), the function blocks until a key is pressed.
Returns nil on timeout.
#### `tio.input(prompt)`
Display a prompt and read user input until Enter is pressed.
Basic editing is supported (Backspace key).
If prompt is omitted, no prompt is displayed.
Returns the entered string.
#### `tio.inputline(prompt)`
Display a prompt and read a line of input until Enter is pressed.
Supports line editing (cursor keys, Backspace) and command history.
Returns the entered string.
#### `tio.set_keymap(keymaps)`
Add, update, or remove key mappings.
#### `tio.subcmd_println(fmt, ...)`
Print a formatted line using sub-command style output
(e.g. [<timestamp>] <message>).
#### `tio.subcmd_puts(string)`
Print string using sub-command style output.
(e.g. [<time-stamp>] <string>).
#### `tio.alwaysecho` #### `tio.alwaysecho`
A boolean value, defaults to `true`. A boolean value, defaults to `true`.
@ -530,7 +611,7 @@ Note: The meson install steps may differ depending on your specific system.
Getting permission access errors trying to open your serial device? Getting permission access errors trying to open your serial device?
Add your user to the group which allows serial device access permanently. For example, to add your user to the 'dialout' group do: Add your user to the group which allows serial device access permanently. For example, to add your user to the 'dialout' group do:
```bash ppp```bash
sudo usermod -a -G dialout <username> sudo usermod -a -G dialout <username>
``` ```
Switch to the "dialout" group, temporary but immediately for this session. Switch to the "dialout" group, temporary but immediately for this session.

View file

@ -0,0 +1,216 @@
# Interactive Lua Scripting
## Introduction
Tio provides an **interactive Lua scripting interface** that allows scripts and Lua commands to be executed while Tio is running.
In earlier versions, the script interpreter was restarted for each script execution.
The interpreter now **remains active**, preserving its state across executions.
This makes it possible to:
* run Lua commands interactively
* reuse functions defined in previously loaded scripts
* experiment with Lua code without restarting the interpreter
* avoid repeated initialization overhead when working with large scripts
---
## Starting Interactive Script Mode
To start interactive script mode, press:
**Ctrl-t r**
Tio displays the script prompt:
```
[08:45:46.514] Run Lua script
[08:45:46.514] Enter file name or "!" Lua commands or "@" interpreter directive:
>>
```
At this prompt you can:
| Input | Description |
| ------------ | ---------------------------------------- |
| `filename` | Execute a Lua script file |
| `!commands` | Execute Lua commands |
| `@directive` | Execute an interpreter control directive |
You can use the cursor keys and backspace key to edit the line and recall command history.
The history is maintained while tio is running.
---
## Running a Script File
To execute a Lua script file, enter the filename at the prompt.
Example script (`banner.tio`):
```lua
function tio.banner()
local banner = [[
_ _ _ _ __
| |_ (_) ___ ___ | |_ __ _ _ __ | |_ _ \ \
| __|| | / _ \ / __|| __|/ _` || '__|| __| (_)_____ | |
| |_ | || (_) | \__ \| |_| (_| || | | |_ _ _|_____|| |
\__||_| \___/ |___/ \__|\__,_||_| \__|(_) (_) | |
/_/
]]
tio.echo(string.gsub(banner, "\n", "\r\n"))
end
tio.banner()
```
Run the script:
```
>> banner.tio
[09:08:13.786] Running script banner.tio
_ _ _ _ __
| |_ (_) ___ ___ | |_ __ _ _ __ | |_ _ \ \
| __|| | / _ \ / __|| __|/ _` || '__|| __| (_)_____ | |
| |_ | || (_) | \__ \| |_| (_| || | | |_ _ _|_____|| |
\__||_| \___/ |___/ \__|\__,_||_| \__|(_) (_) | |
/_/
```
The script is loaded and executed immediately.
Any functions defined in the script remain available in the interpreter.
---
## Executing Lua Commands
Lua commands can be executed directly by prefixing them with `!`.
This allows you to interact with previously defined functions or variables.
Example script (`calc_sum.tio`):
```lua
function tio.calc_sum(t)
local sum = 0
for _, v in ipairs(t) do
sum = sum + v
end
return sum
end
function tio.disp_sum(t)
local sum = tio.calc_sum(t)
tio.echo(sum)
tio.echo("\r\n")
end
```
Example session:
```
>> calc_sum.tio
[09:15:22.372] Running script calc_sum.tio
<<press Ctrl-t r again>>
>> !t = {1,2,3,4,5,6,7,8,9,10}
<<press Ctrl-t r again>>
>> !tio.disp_sum(t)
55
>> !sum = tio.calc_sum(t); tio.echo(sum); tio.echo("\r\n")
55
```
---
## Interpreter Directives
Interpreter behavior can be controlled using directives prefixed with `@`.
### `@new`
Restart the script interpreter.
All interpreter state is cleared and the interpreter is reinitialized.
If a `script-init-file` is configured, it is executed again.
---
### `@doopt`
Execute the script specified by the `script` or `script-file` option, if present.
---
### `@nuldo=opt`
If **Ctrl-t r** is pressed and **Enter** is pressed without entering a command, `@doopt` is executed.
This is the default behavior for compatibility with Tio v3.9.
---
### `@nuldo=none`
If **Ctrl-t r** is pressed and **Enter** is pressed without entering a command, no action is performed.
This helps prevent accidental script execution.
---
### `@repl`
Start **REPL (ReadEvalPrint Loop)** mode.
---
## REPL Mode
Enter:
```
@repl
```
to start REPL mode.
In REPL mode:
* Lua commands can be entered continuously
* each line is executed immediately
* multi-line statements can be continued using a trailing `\`
* `@exit` leaves REPL mode
Example:
```
[10:17:30.215] Run Lua script
[10:17:30.215] Enter file name or "!" Lua commands or "@" interpreter directive:
>> @repl
[10:17:31.956] Enter Lua REPL mode (@exit to exit)
-> p=1
-> for i=1,10 do\
-> p = p * i\
-> end
-> print(p, "\r")
3628800
-> @exit
```
---
## Summary
The interactive Lua scripting interface provides:
* persistent interpreter state
* interactive Lua command execution
* script reuse without interpreter restart
* an integrated REPL environment
These capabilities make it easier to develop, test, and experiment with Tio scripts while the program is running.
Enjoy using Tio's Interactive Lua Scripting.

View file

@ -17,8 +17,10 @@ stopbits = 1
parity = none parity = none
output-delay = 0 output-delay = 0
output-line-delay = 0 output-line-delay = 0
output-line-delay-char = lf
auto-connect = direct auto-connect = direct
no-reconnect = false no-reconnect = false
no-tty-restore = false
local-echo = false local-echo = false
input-mode = normal input-mode = normal
output-mode = normal output-mode = normal

21
examples/lua/expects.lua Normal file
View file

@ -0,0 +1,21 @@
--
-- example of intaction with AT modem.
--
tio.write("AT\r")
local matches, all = tio.expect("OK", 1000)
if matches == nil then
tio.echo("no response 1\r\n")
os.exit(0)
end
msleep(200)
tio.read(1000, tio.C.NOWAIT)
tio.write("ATFANTASYCMD\r")
local idx, matches, all = tio.expects({"OK", "ERROR", "BUSY"}, 1000)
if idx == nil then
tio.echo("no response 2\r\n")
os.exit(0)
end
-- this display 2, ERROR
print(idx, matches[1])
os.exit(0)

View file

@ -0,0 +1,41 @@
--
-- tio's script-init file
--
-- Please add following to your tio config file.
--
-- script-init-file = /<<your-full-path>>/.tio-scipt-init.lua
--
-- note: tio module is already loaded.
--
function tio.conv_lf_to_crlf(str)
local new_str = str:gsub("\n", "\r\n")
return new_str
end
function tio.lprint(...)
local args = {...}
local argN = #args
for i = 1, argN - 1 do
io.write(tio.conv_lf_to_crlf(tostring(args[i])))
io.write("\t")
end
if argN > 0 then
io.write(tio.conv_lf_to_crlf(tostring(args[argN])))
end
io.write("\r\n")
end
function tio.banner()
local banner = [[
_ _ _ _ __
| |_ (_) ___ ___ | |_ __ _ _ __ | |_ _ \ \
| __|| | / _ \ / __|| __|/ _` || '__|| __| (_)_____ | |
| |_ | || (_) | \__ \| |_| (_| || | | |_ _ _|_____|| |
\__||_| \___/ |___/ \__|\__,_||_| \__|(_) (_) | |
/_/]]
tio.lprint(banner)
end
tio.banner()

22
examples/lua/twrite.lua Normal file
View file

@ -0,0 +1,22 @@
--
-- tio.twrite() uses output-mapping, output-delay and input-mode.
--
-- Input-mode HEX is very slow, so it has limited use.
--
-- This script sends
-- Hello.
-- This is Hex mode.
-- Bye.
--
tio.set_input_mode(tio.C.IM_NORMAL)
tio.twrite("Hello.\r\n")
tio.set_input_mode(tio.C.IM_HEX)
tio.twrite("5468697320697320486578206d6f64652e0d0a") -- "This is Hex mode."
tio.set_input_mode(tio.C.IM_LINE)
tio.twrite("Bye.\r\n")
tio.set_input_mode(tio.C.IM_NORMAL)

View file

@ -0,0 +1,94 @@
#!/usr/bin/python3
import sys
import subprocess
import shlex
import os
# netcat (openbsd)
NC_CMD = "nc"
NC_OPT = "-UN"
def connect_and_execute_bidirectional_command(socket_path, command):
TIMEOUT_SEC = 600
# Sanitize user input (command stays as a string, but avoid passing raw input directly)
safe_cmd = " ".join(shlex.split(command))
# Launch nc process (connect to the Unix domain socket)
p_nc = subprocess.Popen(
[NC_CMD, NC_OPT, socket_path],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
text=False, # binary transfer
)
# Launch the bidirectional command connected to nc
p_bidir_cmd = subprocess.Popen(
safe_cmd, # keep string command for shell=True
stdin=p_nc.stdout,
stdout=p_nc.stdin,
stderr=None,
shell=True,
text=False,
)
# Close unused pipe ends in the parent process to propagate SIGPIPE properly
p_nc.stdout.close()
p_nc.stdin.close()
try:
p_bidir_cmd.communicate(timeout=TIMEOUT_SEC)
except subprocess.TimeoutExpired:
# Ensure the command is killed on timeout
p_bidir_cmd.kill()
except Exception as e:
# Print unexpected exceptions for debugging
print(type(e), file=sys.stderr)
# Check bidirectional command exit code
if p_bidir_cmd.returncode != 0:
print(f"command exited with {p_bidir_cmd.returncode}", file=sys.stderr)
# Ensure nc is terminated regardless of exceptions
try:
p_nc.terminate()
p_nc.wait(timeout=5)
except subprocess.TimeoutExpired:
# Force kill if graceful shutdown fails
p_nc.kill()
except Exception as e:
print(type(e), file=sys.stderr)
# Read and report stderr of nc
nc_stderr = p_nc.stderr.read()
if nc_stderr:
print("nc stderr:", nc_stderr.decode(errors='ignore'), file=sys.stderr)
return p_bidir_cmd.returncode
def main():
script = os.path.basename(__file__)
usage = f"Usage: {script} <socket-path> <bidirectional-command>"
example = f"Example: {script} /tmp/tio-socket0 \"sz -b -p sample.bin\""
note = f"Note: Please run \"tio -S <socket-path> <your-serial-device>\" beforehand."
if len(sys.argv) != 3:
print(usage, file=sys.stderr)
print(example, file=sys.stderr)
print(note, file=sys.stderr)
return 1
socket_path = sys.argv[1]
bidirectional_command = sys.argv[2]
return connect_and_execute_bidirectional_command(socket_path, bidirectional_command)
if __name__ == '__main__':
exit_code = main()
sys.exit(exit_code)

View file

@ -0,0 +1,15 @@
#!/bin/sh
if [ $# -lt 2 ]; then
echo "Usage: $0 <socket-path> <bidirectional-command>"
echo "Example: $0 /tmp/tio-socket0 \"sz -b -p sample.bin\""
echo "Note: Please run \"tio -S <socket-path> <your-serial-device>\" beforehand."
exit 1
fi
SOCKET_PATH=$1
BIDIR_CMD=$2
socat EXEC:"$BIDIR_CMD" $SOCKET_PATH
exit $?

22
examples/socket/pexpect-ping.py Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/python3
# In MSYS2, use /mingw64/bin/python3
#
# Send a "Ping" and wait for a "Pong".
# Repeat this process.
#
import pexpect
from pexpect import popen_spawn
child = pexpect.popen_spawn.PopenSpawn("nc -UN /tmp/tio-socket0")
cnt = 0
while True:
try:
child.sendline(f"Ping {cnt:d}")
cnt += 1
child.expect(r'Pong \d+[\r\n]+', timeout = 10)
except Exception as e:
print(type(e))
break

22
examples/socket/pexpect-pong.py Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/python3
# In MSYS2, use /mingw64/bin/python3
#
# wait for a "Ping" and Send a "Pong"
# Repeat this process.
#
import pexpect
from pexpect import popen_spawn
child = pexpect.popen_spawn.PopenSpawn("nc -UN /tmp/tio-socket1")
cnt = 0
while True:
try:
child.expect(r'Ping \d+[\r\n]+', timeout = 10)
child.sendline(f"Pong {cnt:d}")
cnt += 1
except Exception as e:
print(type(e))
break

View file

@ -51,6 +51,11 @@ Set output delay [ms] inserted between each sent character (default: 0).
Set output delay [ms] inserted between each sent line (default: 0). Set output delay [ms] inserted between each sent line (default: 0).
.TP
.BR " \-\-output\-line\-delay\-char " cr | lf
Set trigger character of output line delay (default: lf).
.TP .TP
.BR " \-\-line\-pulse\-duration " \fI<duration> .BR " \-\-line\-pulse\-duration " \fI<duration>
@ -123,6 +128,14 @@ Do not reconnect.
This means that tio will exit if it fails to connect to device or an This means that tio will exit if it fails to connect to device or an
established connection is lost. established connection is lost.
.TP
.BR \-N ", " \-\-no\-tty\-restore
Do not restore initial TTY device settings.
This means that tio will exit without trying to restore TTY device
settings that existed when tio was started.
.TP .TP
.BR \-e ", " "\-\-local\-echo .BR \-e ", " "\-\-local\-echo
@ -205,8 +218,8 @@ Strip control characters and escape sequences from log.
.TP .TP
.BR \-m ", " "\-\-map " \fI<flags> .BR \-m ", " "\-\-map " \fI<flags>
Map (replace, translate) characters on input to the serial device or output Map (replace, translate) characters on input from the serial device or output
from the serial device. The following mapping flags are supported: to the serial device. The following mapping flags are supported:
.RS .RS
.TP 12n .TP 12n
@ -236,7 +249,7 @@ Map lowercase characters to uppercase on output
Map nul (zero) to send break signal on output Map nul (zero) to send break signal on output
.IP "\fBOIGNCR" .IP "\fBOIGNCR"
Ignore CR on output Ignore CR on output
.P .PP
If defining more than one flag, the flags must be comma separated. If defining more than one flag, the flags must be comma separated.
.RE .RE
@ -251,8 +264,9 @@ In hex input mode bytes can be sent by typing the \fBtwo-character
hexadecimal\fR representation of the 1 byte value, e.g.: to send \fI0xA\fR you hexadecimal\fR representation of the 1 byte value, e.g.: to send \fI0xA\fR you
must type \fI0a\fR or \fI0A\fR. must type \fI0a\fR or \fI0A\fR.
In line input mode input characters are sent when you press enter. The only In line input mode input characters are sent when you press enter.
editing feature supported in this mode is backspace. You can use the cursor keys and backspace key to edit the line and recall command
history. The history is maintained while tio is running.
Default value is "normal". Default value is "normal".
@ -301,12 +315,42 @@ Unix Domain Socket (file)
Internet Socket (network) Internet Socket (network)
.IP "\fBinet6:<port>" .IP "\fBinet6:<port>"
Internet IPv6 Socket (network) Internet IPv6 Socket (network)
.P .PP
If port is 0 or no port is provided default port 3333 is used. If port is 0 or no port is provided default port 3333 is used.
.P .PP
At present there is a hardcoded limit of 16 clients connected at one time. At present there is a hardcoded limit of 16 clients connected at one time.
.RE .RE
.TP
.BR " \-\-raw " off|on|on-nodelay
nSet raw mode for non-interactive use.
Non-interactive use is Piped-input / Shell command execution / XYMODEM transferring.
The raw option can be set to one of the following:
.RS
.TP 20n
.IP "\fBoff"
flow control, character mapping and output delay are enabled
.IP "\fBon"
software flow control and character mapping are disabled; output delay remains enabled
.IP "\fBon-nodelay"
software flow control, character mapping and output delay is disabled
.PP
Default value is "on".
.RE
.TP
.BR " \-\-raw-interactive " off|on|on-nodelay
Set raw mode for interactive use.
Interactive use is normal terminal input/output and socket redirection.
This is useful when transferring files through socket redirection.
Default value is "off".
.TP .TP
.BR " \-\-rs\-485" .BR " \-\-rs\-485"
@ -330,7 +374,7 @@ Set RTS delay (ms) before sending
Set RTS delay (ms) after sending Set RTS delay (ms) after sending
.IP \fBRX_DURING_TX .IP \fBRX_DURING_TX
Receive data even while sending data Receive data even while sending data
.P .PP
If defining more than one key or key value pair, they must be comma separated. If defining more than one key or key value pair, they must be comma separated.
.RE .RE
@ -349,15 +393,24 @@ Default value is "none".
Mute tio messages. Mute tio messages.
.TP
.BR "\-\-script\-init\-file \fI<filename>"
Run script from file with filename on tio's startup.
Execution occurs before connecting to the device, and loaded functions and variables are preserved.
The default is <<not\-specified>>, which only loads built\-in functions and variables.
.TP .TP
.BR "\-\-script \fI<string> .BR "\-\-script \fI<string>
Run script from string. Run script from string on connect.
.TP .TP
.BR "\-\-script\-file \fI<filename> .BR "\-\-script\-file \fI<filename>
Run script from file with filename. Run script from file with filename on connect.
.TP .TP
.BR "\-\-script\-run once|always|never" .BR "\-\-script\-run once|always|never"
@ -371,6 +424,11 @@ Default value is "always".
Execute shell command with I/O redirected to device Execute shell command with I/O redirected to device
The standard output and standard error of a shell command are redirected to the device through tio's output filters (with output mapping and output delay enabled), and input from the device is redirected to
the standard input of the shell command.
If you specify '?' as a shell commands prefix, standard error output will not be redirected. This allows you to use some communication commands such as sz/rz.
.TP .TP
.BR "\-\-complete-profiles .BR "\-\-complete-profiles
@ -403,6 +461,10 @@ Flush data I/O buffers (discard data written but not transmitted and data receiv
Toggle serial port line Toggle serial port line
.IP "\fBctrl-t i" .IP "\fBctrl-t i"
Toggle input mode Toggle input mode
.IP "\fBctrl-t j"
Toggle raw mode for non-interactive use
.IP "\fBctrl-t J"
Toggle raw mode for interactive use
.IP "\fBctrl-t l" .IP "\fBctrl-t l"
Clear screen Clear screen
.IP "\fBctrl-t L" .IP "\fBctrl-t L"
@ -426,7 +488,7 @@ Toggle line timestamp mode
.IP "\fBctrl-t v" .IP "\fBctrl-t v"
Show version Show version
.IP "\fBctrl-t x" .IP "\fBctrl-t x"
Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol) Send file using the XMODEM-1K, XMODEM-CRC or XMODEM-SUM protocol (prompts for file name and protocol)
.IP "\fBctrl-t y" .IP "\fBctrl-t y"
Send file using the YMODEM protocol (prompts for file name) Send file using the YMODEM protocol (prompts for file name)
.IP "\fBctrl-t ctrl-t" .IP "\fBctrl-t ctrl-t"
@ -434,7 +496,7 @@ Send ctrl-t character
.SH "SCRIPT API" .SH "SCRIPT API"
.PP .PP
Tio suppots Lua scripting to easily automate interaction with the tty device. Tio supports Lua scripting to easily automate interaction with the tty device.
In addition to the standard Lua API tio makes the following functions In addition to the standard Lua API tio makes the following functions
and variables available: and variables available:
@ -442,33 +504,56 @@ and variables available:
.TP 6n .TP 6n
.IP "\fBtio.expect(pattern, timeout)" .IP "\fBtio.expect(pattern, timeout)"
Waits for the Lua pattern to match or timeout before continuing. Waits for the Lua pattern to match or timeout before continuing. Special characters must be escaped with '\\' or '%'.
Timeout is in milliseconds, defaults to 0 meaning it will wait forever. Timeout is in milliseconds, defaults to tio.C.WAIT_FOREVER(==0) meaning it will wait forever.
Returns the captures from the pattern or nil on timeout. On success, returns the captures from the pattern and all received data.
On timeout, returns nil and all received data.
.IP "\fBtio.expects(pattern-table, timeout)"
Waits for any of the multiple Lua patterns to match or timeout before continuing. Special characters must be escaped with '\\' or '%'.
Timeout is in milliseconds, defaults to tio.C.WAIT_FOREVER(==0) meaning it will wait forever.
On success, returns the index of the matched pattern, the captures from it and all received data.
On timeout, returns nil, nil and all received data.
.IP "\fBtio.read(size, timeout)" .IP "\fBtio.read(size, timeout)"
Read up to size bytes from serial device. If timeout is 0 or not provided it Read up to size bytes from serial device. If timeout is tio.C.WAIT_FOREVER(==0) or not provided it will wait forever until data is ready to read.
will wait forever until data is ready to read. If the timeout is tio.C.NOWAIT (==-1), the function immediately reads as much data as possible from the serial device's RX buffer, up to a maximum of <size> bytes, and returns.
Returns a string up to size bytes long on success and nil on timeout. On success, returns read data as string. Also emits a single timestamp to stdout and log file per options.timestamp and options.log.
On timeout, returns nil and received data.
.IP "\fBtio.readline(timeout)" .IP "\fBtio.readline(timeout)"
Read line from serial device. If timeout is 0 or not provided it will wait Read line from serial device. If timeout is tio.C.WAIT_FOREVER(==0) or not provided it will wait forever until data is ready to read.
forever until data is ready to read. The line separater is LF (0x0a).
Returns a string on success and nil on timeout. On timeout a partially read On success, returns received line as string. Also emits a single timestamp to stdout and log file per options.timestamp and options.log.
line may be returned as a second return value. On timeout, returns nil and received data.
.IP "\fBtio.write(string)" .IP "\fBtio.write(string)"
Write string to serial device. Write string to serial device without input-editing, output-mapping, or output-delay.
Returns the tio table. Returns the tio table on success or nil on error.
.IP "\fBtio.twrite(string)"
Write string to serial device with input-editing, output-mapping and output-delay.
Returns tio table on success or nil on error.
.IP "\fBtio.send(file, protocol)" .IP "\fBtio.send(file, protocol)"
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, XMODEM_SUM, YMODEM.
It can alternatively be one of tio.C.XM_1K, tio.C.XM_CRC, tio.C.XM_SUM, tio.C.YM_NORMAL.
.IP "\fBtio.receive(file, protocol)"
Receive a file using the XMODEM protocol.
Protocol can be any of XMODEM_CRC or XMODEM_SUM.
It can alternatively be one of tio.C.XM_CRC or tio.C.XM_SUM.
Returns the tio table on success or nil on error.
.IP "\fBtio.ttysearch()" .IP "\fBtio.ttysearch()"
Search for serial devices. Search for serial devices.
@ -489,9 +574,159 @@ State is high, low, or toggle.
.IP "\fBtio.sleep(seconds)" .IP "\fBtio.sleep(seconds)"
Sleep for seconds. Sleep for seconds.
.IP "\fBtio.msleep(ms)" .IP "\fBtio.msleep(ms)"
Sleep for milliseconds. Sleep for milliseconds.
.IP "\fBtio.send_break()"
Send break signal.
It is equivalent to the key command ctrl-t b.
.IP "\fBtio.line_get()"
Get state of multiple tty modem lines.
It is equivalent to the key command ctrl-t L.
Return 6 values DTR, RTS, CTS, DSR, CD, RI.
Each return value is high (==tio.C.LN_HIGH) or low (==tio.C.LN_LOW).
.IP "\fBtio.set_local_echo(on_off)"
Change the local echo setting.
It is equivalent to the key command ctrl-t e.
The argument on_off is a boolean value. true means on and false means off. If omitted, it is set to true.
.IP "\fBtio.set_log(on_off)"
Change the log-file setting.
It is equivalent to the key command ctrl-t f.
The argument on_off is a boolean value. true means on and false means off. If omitted, it is set to true.
.IP "\fBtio.flush_data_io_buffer()"
Flush read/write data in I/O buffers.
It is equivalent to the key command ctrl-t F.
.IP "\fBtio.set_input_mode(input_mode)"
Change the input mode.
It is equivalent to the key command ctrl-t i.
The argument input_mode is one of tio.C.IM_NORMAL, tio.C.IM_HEX, tio.C.IM_LINE.
.IP "\fBtio.set_output_mode(output_mode)"
Change the output mode.
It is equivalent to the key command ctrl-t o.
The argument output_mode is one of tio.C.OM_NORMAL, tio.C.OM_HEX.
.IP "\fBtio.set_raw_mode(raw_mode)"
Change the raw mode for non-interactive use.
It is equivalent to the key command ctrl-t j.
The argument raw_mode is one of tio.C.RAW_OFF, tio.C.RAW_ON, tio.C.RAW_ON_NODELAY.
.IP "\fBtio.set_raw_mode_interactive(raw_mode)"
Change the raw mode for interactive use.
It is equivalent to the key command ctrl-t J.
The argument raw_mode is one of tio.C.RAW_OFF, tio.C.RAW_ON, tio.C.RAW_ON_NODELAY.
.IP "\fBtio.set_timestamp_mode(timestamp_mode)"
Change the timestamp mode.
It is equivalent to the key command ctrl-t t.
The argument timestamp_mode is one of tio.C.TS_OFF, tio.C.TS_24HOUR, tio.C.TS_24HOUR_START, tio.C.TS_24HOUR_DELTA, tio.C.TS_ISO861, tio.C.TS_EPOCH, tio.C.TS_EPOCH_USEC.
.IP "\fBtio.exec_shell_command(shell_commands)"
Execute /bin/sh -c <<shell_commands>>.
Normally, standard output / standard error is forwarded to tio's output filter which do output mapping and output delay.
If the shell commands starts with '?', '?' is removed and standard error is not forwarded.
It is equivalent to the key command ctrl-t R.
The argument shell_commands is string.
.IP "\fBtio.get_state()"
Return the main state of tio as a integer.
Return value is one of tio.C.SA_INTERACTIVE, tio.C.SA_STARTING, tio.C.SA_PIPED_INPUT, tio.C.SA_PIPED_INPUT, tio.C.SA_EXEC_SHELL_COMMAND, tio.C.SA_XYMODEM.
.IP "\fBtio.get_version()"
Return the version of tio as a string.
It is equivalent to the key command ctrl-t v.
.IP "\fBtio.inkey(timeout)"
Read a key press and return it as a string.
Timeout is in milliseconds. If timeout is tio.C.WAIT_FOREVER(==0),
the function blocks until a key is pressed. If timeout is
tio.C.NOWAIT (==-1) or not provided, the function returns
immediately.
Returns the key as a string on success, or nil on timeout.
.IP "\fBtio.input(prompt)"
Display a prompt and read user input until Enter is pressed.
Basic line editing is supported (Backspace key).
If prompt is not provided, no prompt is displayed.
Returns the entered string.
.IP "\fBtio.inputline(prompt)"
Display a prompt and read a line of input until Enter is pressed.
Supports line editing (cursor keys, Backspace) and command history.
Returns the entered string.
.IP "\fBtio.set_keymap(keymaps)"
Add, update, or remove key mappings.
The keymaps argument uses the same syntax as the --keymap option:
@<key-1>=<script-description-1>
@<key-2>=<script-description-2>
...
@<key-N>=<script-description-N>
Each <script-description> must be either a script filename or an
inline script prefixed with '!'.
When a mapping is defined, pressing Ctrl-t followed by <key-n>
executes the corresponding script.
If a key already has a mapping, it is updated. If
<script-description> is empty, the mapping is removed.
User-defined key mappings take precedence over default key bindings,
except for "Ctrl-t q", which is always reserved.
This function allows key mappings to be modified at runtime after tio
has started.
.IP "\fBtio.subcmd_println(fmt, ...)"
Print a formatted line using sub-command style output.
The output format is:
[<timestamp>] <formatted message>
.IP "\fBtio.subcmd_warning_println(fmt, ...)"
Print a formatted warning line using sub-command style output.
.IP "\fBtio.subcmd_error_println(fmt, ...)"
Print a formatted error line using sub-command style output.
.IP "\fBtio.subcmd_error_println(fmt, ...)"
.IP "\fBtio.subcmd_puts(string)"
Print a string using sub-command style output.
The output format is:
[<timestamp>] <string>
.IP "\fBtio.subcmd_warning_puts(string)"
Print a warning string using sub-command style output.
.IP "\fBtio.subcmd_error_puts(string)"
Print an error string using sub-command style output.
.IP "\fBtio.alwaysecho" .IP "\fBtio.alwaysecho"
A boolean value, defaults to true. A boolean value, defaults to true.
@ -501,6 +736,10 @@ will only be returned from the function and not logged or printed.
If tio.alwaysecho is set to true, reading functions also emit a single If tio.alwaysecho is set to true, reading functions also emit a single
timestamp to stdout and log file per options.timestamp and options.log. timestamp to stdout and log file per options.timestamp and options.log.
.IP "\fBos.exit(code)"
Exit tio process with exit code (like ctrl-t q).
.SH "CONFIGURATION FILE" .SH "CONFIGURATION FILE"
.PP .PP
Options can be set via configuration file using the INI format. \fBtio\fR uses Options can be set via configuration file using the INI format. \fBtio\fR uses
@ -550,6 +789,8 @@ Set parity
Set output character delay Set output character delay
.IP "\fBoutput-line-delay" .IP "\fBoutput-line-delay"
Set output line delay Set output line delay
.IP "\fBoutput-line-delay-char"
Set trigger character of output line delay
.IP "\fBline-pulse-duration" .IP "\fBline-pulse-duration"
Set line pulse duration Set line pulse duration
.IP "\fBno-reconnect" .IP "\fBno-reconnect"
@ -574,6 +815,8 @@ Set timestamp format
Set timestamp timeout Set timestamp timeout
.IP "\fBmap" .IP "\fBmap"
Map characters on input or output Map characters on input or output
.IP "\fBkeymap"
Set key-script mappings
.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"
@ -592,12 +835,14 @@ Set RS-485 configuration
Set alert action on connect/disconnect Set alert action on connect/disconnect
.IP "\fBmute" .IP "\fBmute"
Mute tio messages Mute tio messages
.IP "\fBscript-init-file"
Run script from file on tio's startup
.IP "\fBscript" .IP "\fBscript"
Run script from string Run script from string on connect
.IP "\fBscript-file" .IP "\fBscript-file"
Run script from file Run script from file on connect
.IP "\fBscript-run" .IP "\fBscript-run"
Run script on connect Set condition to run script on connect
.IP "\fBexec" .IP "\fBexec"
Execute shell command with I/O redirected to device Execute shell command with I/O redirected to device
@ -772,6 +1017,31 @@ Manipulate DTR and RTS lines upon first connect to reset connected microcontroll
$ tio --script "tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{RTS=toggle}" --script-run once /dev/ttyUSB0 $ tio --script "tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{RTS=toggle}" --script-run once /dev/ttyUSB0
.TP
Manipulate DTR and RTS lines by pressing ctrl-t 1:
$ tio --keymap '@1=!tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{RTS=toggle}' /dev/ttyUSB0
.TP
Send file to device by sz command:
$ tio --exec '?sz -b file' /dev/ttyUSB0
.TP
Receive file from device by rz command:
$ tio --exec '?rz -b' /dev/ttyUSB0
.TP
Send file to device by gkermit command:
$ tio --exec '?gkermit -XSs file' /dev/ttyUSB0
.TP
Receive file from device by gkermit command:
$ tio --exec '?gkermit -XSr' /dev/ttyUSB0
.SH "WEBSITE" .SH "WEBSITE"
.PP .PP
Visit https://tio.github.io Visit https://tio.github.io

View file

@ -40,6 +40,10 @@ OPTIONS
Set output delay [ms] inserted between each sent line (default: 0). Set output delay [ms] inserted between each sent line (default: 0).
--output-line-delay-char cr|lf
Set trigger character of output line delay (default: lf).
--line-pulse-duration <duration> --line-pulse-duration <duration>
Set the pulse duration [ms] of each serial port line using the following key value pair format in the duration field: <key>=<value> Set the pulse duration [ms] of each serial port line using the following key value pair format in the duration field: <key>=<value>
@ -94,6 +98,12 @@ OPTIONS
This means that tio will exit if it fails to connect to device or an established connection is lost. This means that tio will exit if it fails to connect to device or an established connection is lost.
-N, --no-tty-restore
Do not restore initial TTY device settings.
This means that tio will exit without trying to restore TTY device settings that existed when tio was started.
-e, --local-echo -e, --local-echo
Enable local echo. Enable local echo.
@ -116,7 +126,7 @@ OPTIONS
epoch Seconds since Unix epoch (1970-01-01) epoch Seconds since Unix epoch (1970-01-01)
epoch-usec Seconds since Unix epoch (1970-01-01) with subdivision microseconds epoch-usec Seconds since Unix epoch (1970-01-01) with subdivision in microseconds
Default format is 24hour Default format is 24hour
@ -158,7 +168,7 @@ OPTIONS
-m, --map <flags> -m, --map <flags>
Map (replace, translate) characters on input to the serial device or output from the serial device. The following mapping flags are supported: Map (replace, translate) characters on input from the serial device or output to the serial device. The following mapping flags are supported:
ICRNL Map CR to NL on input (unless IGNCR is set) ICRNL Map CR to NL on input (unless IGNCR is set)
@ -188,6 +198,12 @@ 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.
--keymap <keymaps>
Specify the mappings as @<key-1>=<script-description-1> @<key-2>=<script-description-2>... @<key-N>=<script-description-N>.
Script-description is script-filename or '!'script-commands.
--input-mode normal|hex|line --input-mode normal|hex|line
Set input mode. Set input mode.
@ -196,7 +212,7 @@ OPTIONS
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 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. In line input mode input characters are sent when you press enter. You can use the cursor keys and backspace key to edit the line and recall command history. The history is maintained while tio is running.
Default value is "normal". Default value is "normal".
@ -239,6 +255,30 @@ OPTIONS
At present there is a hardcoded limit of 16 clients connected at one time. At present there is a hardcoded limit of 16 clients connected at one time.
--raw off|on|on-nodelay
Set raw mode for non-interactive use.
Non-interactive use is Piped-input / Shell command execution / XYMODEM transfering.
The raw option can be set to one of the following:
off flow control, character mapping and output delay are enabled
on software flow control and character mapping are disabled; output delay remains enabled
on-nodelay software flow control, character mapping and output delay is disabled
Default value is "on".
--raw-interactive off|on|on-nodelay
Set raw mode for interactive use.
Interactive use is normal terminal input/output and socket redirection.
This is useful when transferring files through socket redirection.
Default value is "off".
--rs-485 --rs-485
Enable RS-485 mode. Enable RS-485 mode.
@ -271,13 +311,21 @@ OPTIONS
Mute tio messages. Mute tio messages.
--script-init-file <filename>
Run script from file with filename on tio's startup.
Execution occurs before connecting to the device, and loaded functions and variables are preserved.
The default is <<not-specified>>, which only loads built-in functions and variables.
--script <string> --script <string>
Run script from string. Run script from string on connect.
--script-file <filename> --script-file <filename>
Run script from file with filename. Run script from file with filename on connect.
--script-run once|always|never --script-run once|always|never
@ -289,6 +337,11 @@ OPTIONS
Execute shell command with I/O redirected to device Execute shell command with I/O redirected to device
The standard output and standard error of a shell command are redirected to the device through tio's output filters (with output mapping and output delay enabled), and input from the device is redirected to
the standard input of the shell command.
If you specify '?' as a shell commands prefix, standard error output will not be redirected. This allows you to use some communication commands such as sz/rz.
--complete-profiles --complete-profiles
Prints profiles (for shell completion) Prints profiles (for shell completion)
@ -320,6 +373,10 @@ KEY COMMANDS
ctrl-t i Toggle input mode ctrl-t i Toggle input mode
ctrl-t j Toggle raw mode for non-interactive use
ctrl-t J Toggle raw mode for interactive use
ctrl-t l Clear screen ctrl-t l Clear screen
ctrl-t L Show line states (DTR, RTS, CTS, DSR, DCD, RI) ctrl-t L Show line states (DTR, RTS, CTS, DSR, DCD, RI)
@ -342,49 +399,70 @@ KEY COMMANDS
ctrl-t v Show version ctrl-t v Show version
ctrl-t x Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol) ctrl-t x Send file using the XMODEM-1K, XMODEM-CRC or XMODEM-SUM protocol (prompts for file name and protocol)
ctrl-t y Send file using the YMODEM protocol (prompts for file name) ctrl-t y Send file using the YMODEM protocol (prompts for file name)
ctrl-t ctrl-t Send ctrl-t character ctrl-t ctrl-t Send ctrl-t character
SCRIPT API SCRIPT API
Tio suppots Lua scripting to easily automate interaction with the tty device. Tio supports Lua scripting to easily automate interaction with the tty device.
In addition to the standard Lua API tio makes the following functions available: In addition to the standard Lua API tio makes the following functions available:
expect(string, timeout) tio.expect(pattern, timeout)
Expect string - waits for string to match or timeout before continuing. Supports regular expressions. Special characters must be escaped with '\\'. Timeout is in milliseconds, defaults to 0 meaning it will wait forever. Waits for the Lua pattern to match or timeout before continuing. Special characters must be escaped with '\\' or '%'.
Timeout is in milliseconds, defaults to tio.C.WAIT_FOREVER(==0) meaning it will wait forever.
Returns 1 on successful match, 0 on timeout, or -1 on error. On success, returns the captures from the pattern and all received data.
On timeout, returns nil and all received data.
On successful match it also returns the match string as second return value. tio.expects(pattern-table, timeout)
Waits for any of the multiple Lua patterns to match or timeout before continuing. Special characters must be escaped with '\\' or '%'.
Timeout is in milliseconds, defaults to tio.C.WAIT_FOREVER(==0) meaning it will wait forever.
read(size, timeout) On success, returns the index of the matched pattern, the captures from it and all received data.
Read up to size bytes from serial device. If timeout is 0 or not provided it will wait forever until data is ready to read. On timeout, returns nil, nil and all received data.
Returns number of bytes read on success, 0 on timeout, or -1 on error. tio.read(size, timeout)
Read up to size bytes from serial device. If timeout is tio.C.WAIT_FOREVER(==0) or not provided it will wait forever until data is ready to read.
If the timeout is tio.C.NOWAIT (==-1), the function immediately reads as much data as possible from the serial device's RX buffer, up to a maximum of <size> bytes, and returns.
On success, returns read string as second return value. Also emits a single timestamp to stdout and log file per options.timestamp and options.log. On success, returns read data as string. Also emits a single timestamp to stdout and log file per options.timestamp and options.log.
On timeout, returns nil and received data.
read_line(timeout) tio.readline(timeout)
Read line from serial device. If timeout is 0 or not provided it will wait forever until data is ready to read. Read line from serial device. If timeout is tio.C.WAIT_FOREVER(==0) or not provided it will wait forever until data is ready to read.
The line separater is LF (0x0a).
Returns number of bytes read on success, 0 on timeout, or -1 on error. On success, returns received line as string. Also emits a single timestamp to stdout and log file per options.timestamp and options.log.
On timeout, returns nil and received data.
On success, returns the string that was read as second return value. Also emits a single timestamp to stdout and log file per options.timestamp and options.log. tio.write(string)
Write string to serial device without input-editing, output-mapping nor output-delay.
write(string) Returns the tio table on success or nil on error.
Write string to serial device.
Returns number of bytes written on success or -1 on error. tio.twrite(string)
Write string to serial device with input-editing, output-mapping and output-delay.
send(file, protocol) Returns the tio table on success or nil on error.
tio.send(file, protocol)
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, XMODEM_SUM, YMODEM.
It can alternatively be one of tio.C.XM_1K, tio.C.XM_CRC, tio.C.XM_SUM, tio.C.YM_NORMAL.
tty_search() tio.receive(file, protocol)
Receive a file using the XMODEM protocol.
Protocol can be any of XMODEM_CRC or XMODEM_SUM.
It can alternatively be one of tio.C.XM_CRC or tio.C.XM_SUM.
Returns the tio table on success or nil on error.
tio.ttysearch()
Search for serial devices. 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", "dri 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", "dri
@ -392,21 +470,172 @@ SCRIPT API
Returns nil if no serial devices are found. Returns nil if no serial devices are found.
set{line=state, ...} tio.set{line=state, ...}
Set state of one or multiple tty modem lines. Set state of one or multiple tty modem lines.
Line can be any of DTR, RTS, CTS, DSR, CD, RI Line can be any of DTR, RTS, CTS, DSR, CD, RI
State is high, low, or toggle. State is high (==tio.C.LN_HIGH), low (==tio.C.LN_LOW), or toggle (==tio.C.LN_TOGGLE).
sleep(seconds) tio.sleep(seconds)
Sleep for seconds. Sleep for seconds.
msleep(ms) tio.msleep(ms)
Sleep for milliseconds. Sleep for milliseconds.
exit(code) tio.send_break()
Exit with exit code. Send break signal.
It is equivalent to the key command ctrl-t b.
tio.line_get()
Get state of multiple tty modem lines.
It is equivalent to the key command ctrl-t L.
Return 6 values DTR, RTS, CTS, DSR, CD, RI.
Each return value is high (==tio.C.LN_HIGH) or low (==tio.C.LN_LOW).
tio.set_local_echo(on_off)
Change the local echo setting.
It is equivalent to the key command ctrl-t e.
The argument on_off is a boolean value. true means on and false means off. If omitted, it is set to true.
tio.set_log(on_off)
Change the log-file setting.
It is equivalent to the key command ctrl-t f.
The argument on_off is a boolean value. true means on and false means off. If omitted, it is set to true.
tio.flush_data_io_buffer()
Flush read/write data in I/O buffers.
It is equivalent to the key command ctrl-t F.
tio.set_input_mode(input_mode)
Change the input mode.
It is equivalent to the key command ctrl-t i.
The argument input_mode is one of tio.C.IM_NORMAL, tio.C.IM_HEX, tio.C.IM_LINE.
tio.set_output_mode(output_mode)
Change the output mode.
It is equivalent to the key command ctrl-t o.
The argument output_mode is one of tio.C.OM_NORMAL, tio.C.OM_HEX.
tio.set_raw_mode(raw_mode)
Change the raw mode for non-interactive use.
It is equivalent to the key command ctrl-t j.
The argument raw_mode is one of tio.C.RAW_OFF, tio.C.RAW_ON, tio.C.RAW_ON_NODELAY.
tio.set_raw_mode_interactive(raw_mode)
Change the raw mode for interactive use.
It is equivalent to the key command ctrl-t J.
The argument raw_mode is one of tio.C.RAW_OFF, tio.C.RAW_ON, tio.C.RAW_ON_NODELAY.
tio.set_timestamp_mode(timestamp_mode)
Change the timestamp mode.
It is equivalent to the key command ctrl-t t.
The argument timestamp_mode is one of tio.C.TS_OFF, tio.C.TS_24HOUR, tio.C.TS_24HOUR_START, tio.C.TS_24HOUR_DELTA, tio.C.TS_ISO861, tio.C.TS_EPOCH, tio.C.TS_EPOCH_USEC.
tio.exec_shell_command(shell_commands)
Execute /bin/sh -c <<shell_commands>>.
Normally, standard output / standard error is forwarded to tio's output filter which do output mapping and output delay.
If the shell commands starts with '?', '?' is removed and standard error is not forwarded.
It is equivalent to the key command ctrl-t R.
The argument shell_commands is string.
tio.get_state()
Return the main state of tio as a integer.
Return value is one of tio.C.SA_INTERACTIVE, tio.C.SA_STARTING, tio.C.SA_PIPED_INPUT, tio.C.SA_PIPED_INPUT, tio.C.SA_EXEC_SHELL_COMMAND, tio.C.SA_XYMODEM.
tio.get_version()
Return the version of tio as a string.
It is equivalent to the key command ctrl-t v.
tio.inkey(timeout)
Read a key press and return it as a string.
Timeout is in milliseconds. If timeout is tio.C.WAIT_FOREVER(==0),
the function blocks until a key is pressed. If timeout is
tio.C.NOWAIT (==-1) or not provided, the function returns
immediately.
Returns the key as a string on success, or nil on timeout.
tio.input(prompt)
Display a prompt and read user input until Enter is pressed.
Basic line editing is supported (Backspace key).
If prompt is not provided, no prompt is displayed.
Returns the entered string.
tio.inputline(prompt)
Display a prompt and read a line of input until Enter is pressed.
Supports line editing (cursor keys, Backspace) and command
history.
Returns the entered string.
tio.set_keymap(keymaps)
Add, update, or remove key mappings.
The <keymaps> argument uses the same syntax as the --keymap option:
@<key-1>=<script-description-1>
@<key-2>=<script-description-2>
...
@<key-N>=<script-description-N>
Each <script-description> must be either a script filename or an inline script prefixed with '!'.
When a mapping is defined, pressing Ctrl-T followed by <key-n> executes the corresponding script.
If a key already has a mapping, it will be updated. If <script-description> is empty, the mapping is removed.
User-defined key mappings take precedence over default key bindings, except for "Ctrl-T q", which is always reserved.
This function can be used to dynamically modify key mappings at runtime after tio has started.
tio.subcmd_println(fmt, ...)
Print a formatted line using sub-command style output.
The output format is:
[<timestamp>] <formatted message>
tio.subcmd_warning_println(fmt, ...)
Print a formatted warning line using sub-command style output.
tio.subcmd_error_println(fmt, ...)
Print a formatted error line using sub-command style output.
tio.subcmd_puts(string)
Print a string using sub-command style output.
The output format is:
[<timestamp>] <string>
tio.subcmd_warning_puts(string)
Print a warning string using sub-command style output.
tio.subcmd_error_puts(string)
Print an error string using sub-command style output.
tio.alwaysecho
A boolean value, defaults to true.
If tio.alwaysecho is false, the result of tio.read, tio.readline or tio.expect will only be returned from the function and not logged or printed.
If tio.alwaysecho is set to true, reading functions also emit a single timestamp to stdout and log file per options.timestamp and options.log.
os.exit(code)
Exit tio process with exit code (like ctrl-t q).
CONFIGURATION FILE CONFIGURATION FILE
Options can be set via configuration file using the INI format. tio uses the configuration file first found in the following locations in the order listed: Options can be set via configuration file using the INI format. tio uses the configuration file first found in the following locations in the order listed:
@ -445,6 +674,8 @@ CONFIGURATION FILE
output-line-delay Set output line delay output-line-delay Set output line delay
output-line-delay-char Set trigger character of output line delay
line-pulse-duration Set line pulse duration line-pulse-duration Set line pulse duration
no-reconnect Do not reconnect no-reconnect Do not reconnect
@ -469,6 +700,8 @@ CONFIGURATION FILE
map Map characters on input or output map Map characters on input or output
keymap Set key-script mappings
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
@ -487,11 +720,13 @@ CONFIGURATION FILE
mute Mute tio messages mute Mute tio messages
script Run script from string script-init-file Run script from file on tio's startup
script-file Run script from file script Run script from string on connect
script-run Run script on connect script-file Run script from file on connect
script-run Set condition to run script on connect
exec Execute shell command with I/O redirected to device exec Execute shell command with I/O redirected to device
@ -582,7 +817,7 @@ EXAMPLES
It is also possible to use tio's own simpler expect/send script functionality to e.g. automate logins: It is also possible to use tio's own simpler expect/send script functionality to e.g. automate logins:
$ tio --script 'expect("login: "); write("root\n"); expect("Password: "); write("root\n")' /dev/ttyUSB0 $ tio --script 'tio.expect("login: "); tio.write("root\n"); tio.expect("Password: "); tio.write("root\n")' /dev/ttyUSB0
Redirect device I/O to network file socket for remote TTY sharing: Redirect device I/O to network file socket for remote TTY sharing:
@ -614,7 +849,27 @@ EXAMPLES
Manipulate DTR and RTS lines upon first connect to reset connected microcontroller: Manipulate DTR and RTS lines upon first connect to reset connected microcontroller:
$ tio --script "set{DTR=high,RTS=low}; msleep(100); set{RTS=toggle}" --script-run once /dev/ttyUSB0 $ tio --script "tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{RTS=toggle}" --script-run once /dev/ttyUSB0
Manipulate DTR and RTS lines by pressing ctrl-t 1:
$ tio --keymap '@1=!tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{RTS=toggle}' /dev/ttyUSB0
Send file to device by sz command:
$ tio --exec '?sz -b file' /dev/ttyUSB0
Receive file from device by rz command:
$ tio --exec '?rz -b' /dev/ttyUSB0
Send file to device by gkermit command:
$ tio --exec '?gkermit -XSs file' /dev/ttyUSB0
Receive file from device by gkermit command:
$ tio --exec '?gkermit -XSr' /dev/ttyUSB0
WEBSITE WEBSITE
Visit https://tio.github.io Visit https://tio.github.io

View file

@ -23,6 +23,7 @@ _tio()
--exclude-drivers \ --exclude-drivers \
--exclude-tids \ --exclude-tids \
-n --no-reconnect \ -n --no-reconnect \
-N --no-tty-restore \
-e --local-echo \ -e --local-echo \
-l --log \ -l --log \
--log-file \ --log-file \

View file

@ -171,6 +171,13 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
config_get_integer(key_file, group, "output-delay", &option.output_delay, 0, INT_MAX); config_get_integer(key_file, group, "output-delay", &option.output_delay, 0, INT_MAX);
config_get_integer(key_file, group, "output-line-delay", &option.output_line_delay, 0, INT_MAX); config_get_integer(key_file, group, "output-line-delay", &option.output_line_delay, 0, INT_MAX);
config_get_string(key_file, group, "output-line-delay-char", &string, NULL);
if (string != NULL)
{
option_parse_output_line_delay_char(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "line-pulse-duration", &string, NULL); config_get_string(key_file, group, "line-pulse-duration", &string, NULL);
if (string != NULL) if (string != NULL)
{ {
@ -189,6 +196,7 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
config_get_string(key_file, group, "exclude-drivers", &option.exclude_devices, NULL); config_get_string(key_file, group, "exclude-drivers", &option.exclude_devices, NULL);
config_get_string(key_file, group, "exclude-tids", &option.exclude_devices, NULL); config_get_string(key_file, group, "exclude-tids", &option.exclude_devices, NULL);
config_get_bool(key_file, group, "no-reconnect", &option.no_reconnect); config_get_bool(key_file, group, "no-reconnect", &option.no_reconnect);
config_get_bool(key_file, group, "no-tty-restore", &option.no_tty_restore);
config_get_bool(key_file, group, "local-echo", &option.local_echo); config_get_bool(key_file, group, "local-echo", &option.local_echo);
config_get_string(key_file, group, "input-mode", &string, "normal", "hex", "line", NULL); config_get_string(key_file, group, "input-mode", &string, "normal", "hex", "line", NULL);
if (string != NULL) if (string != NULL)
@ -204,6 +212,20 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
g_free((void *)string); g_free((void *)string);
string = NULL; string = NULL;
} }
config_get_string(key_file, group, "raw", &string, NULL);
if (string != NULL)
{
option_parse_raw(string, &option.raw);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "raw-interactive", &string, NULL);
if (string != NULL)
{
option_parse_raw(string, &option.raw_interactive);
g_free((void *)string);
string = NULL;
}
config_get_bool(key_file, group, "timestamp", (bool*) &option.timestamp); config_get_bool(key_file, group, "timestamp", (bool*) &option.timestamp);
if (option.timestamp != TIMESTAMP_NONE) if (option.timestamp != TIMESTAMP_NONE)
{ {
@ -228,6 +250,13 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
g_free((void *)string); g_free((void *)string);
string = NULL; string = NULL;
} }
config_get_string(key_file, group, "keymap", &string, NULL);
if (string != NULL)
{
option_parse_key_mappings(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "color", &string, NULL); config_get_string(key_file, group, "color", &string, NULL);
if (string != NULL) if (string != NULL)
{ {
@ -272,6 +301,7 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
string = NULL; string = NULL;
} }
config_get_bool(key_file, group, "mute", &option.mute); config_get_bool(key_file, group, "mute", &option.mute);
config_get_string(key_file, group, "script-init-file", &option.script_init_filename, NULL);
config_get_string(key_file, group, "script", &option.script, NULL); config_get_string(key_file, group, "script", &option.script, NULL);
config_get_string(key_file, group, "script-file", &option.script_filename, NULL); config_get_string(key_file, group, "script-file", &option.script_filename, NULL);
config_get_string(key_file, group, "script-run", &string, NULL); config_get_string(key_file, group, "script-run", &string, NULL);

View file

@ -68,7 +68,7 @@ int main(int argc, char *argv[])
/* Configure input terminal */ /* Configure input terminal */
if (isatty(fileno(stdin))) if (isatty(fileno(stdin)))
{ {
stdin_configure(); stdin_configure();
} }
else else
{ {
@ -110,7 +110,8 @@ int main(int argc, char *argv[])
if (interactive_mode) if (interactive_mode)
{ {
tio_printf("Press ctrl-%c q to quit", option.prefix_key); tio_printf("Press ctrl-%c q to quit", option.prefix_key);
} else }
else
{ {
tio_printf("Non-interactive mode enabled"); tio_printf("Non-interactive mode enabled");
tio_printf("Press ctrl-c to quit"); tio_printf("Press ctrl-c to quit");
@ -122,6 +123,12 @@ int main(int argc, char *argv[])
socket_configure(); socket_configure();
} }
/* Script interpreter init */
script_interp_init();
/* Initialize tty module once on program start */
tty_init();
/* Spawn input handling into separate thread */ /* Spawn input handling into separate thread */
tty_input_thread_create(); tty_input_thread_create();

View file

@ -53,6 +53,11 @@ if host_machine.system() == 'darwin'
tio_dep += [iokit_dep, corefoundation_dep] tio_dep += [iokit_dep, corefoundation_dep]
endif endif
if host_machine.system() == 'haiku'
network_dep = meson.get_compiler('c').find_library('network', required:true)
tio_dep += [network_dep]
endif
tio_c_args = ['-Wshadow','-Wno-unused-result'] tio_c_args = ['-Wshadow','-Wno-unused-result']
if enable_setspeed2 if enable_setspeed2

View file

@ -28,6 +28,9 @@
#include <regex.h> #include <regex.h>
#include <errno.h> #include <errno.h>
#include "print.h" #include "print.h"
#include "misc.h"
static pid_t shell_command_pid = 0;
void delay(long ms) void delay(long ms)
{ {
@ -54,6 +57,16 @@ int ctrl_key_code(unsigned char key)
return -1; return -1;
} }
int ctrl_key_char(int key_code)
{
if (key_code >= ('a' & ~0x60) && key_code <= ('z' & ~0x60))
{
return key_code | 0x60;
}
return -1;
}
bool regex_match(const char *string, const char *pattern) bool regex_match(const char *string, const char *pattern)
{ {
regex_t regex; regex_t regex;
@ -98,14 +111,53 @@ int read_poll(int fd, void *data, size_t len, int timeout)
if (fds.revents & POLLIN) if (fds.revents & POLLIN)
{ {
// Read ready data // Read ready data
// return value should not be 0
return read(fd, data, len); return read(fd, data, len);
} }
else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */
{
return -1;
}
} }
/* Timeout */ /* Timeout */
return ret; return 0;
} }
ssize_t write_poll(int fd, const void *data, size_t len, int timeout)
{
struct pollfd fds;
ssize_t ret = 0;
fds.events = POLLOUT;
fds.fd = fd;
/* Wait data available */
ret = poll(&fds, 1, timeout);
if (ret < 0)
{
tio_error_print("%s", strerror(errno));
return ret;
}
else if (ret > 0)
{
if (fds.revents & POLLOUT)
{
// Ready to write
// return value should not be 0
return write(fd, data, len);
}
else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */
{
return -1;
}
}
/* Timeout */
return 0;
}
// Function to calculate djb2 hash of string // Function to calculate djb2 hash of string
unsigned long djb2_hash(const unsigned char *str) unsigned long djb2_hash(const unsigned char *str)
{ {
@ -170,6 +222,7 @@ bool match_patterns(const char *string, const char *patterns)
pattern = strtok(patterns_copy, ","); pattern = strtok(patterns_copy, ",");
while (pattern != NULL) while (pattern != NULL)
{ {
// clang-format off
// Check if the string matches the current pattern // Check if the string matches the current pattern
#ifdef FNM_EXTMATCH #ifdef FNM_EXTMATCH
if (fnmatch(pattern, string, FNM_EXTMATCH) == 0) if (fnmatch(pattern, string, FNM_EXTMATCH) == 0)
@ -180,6 +233,7 @@ bool match_patterns(const char *string, const char *patterns)
free(patterns_copy); free(patterns_copy);
return true; return true;
} }
// clang-format on
// Move to the next pattern // Move to the next pattern
pattern = strtok(NULL, ","); pattern = strtok(NULL, ",");
@ -189,38 +243,76 @@ bool match_patterns(const char *string, const char *patterns)
return false; return false;
} }
// Function that forks subprocess, redirects its stdin and stdout to the // Function that forks subprocess, redirects its stdout and stderr to the
// specified filedescriptor, and runs command. // specified filedescriptor, and runs command.
int execute_shell_command(int fd, const char *command) int execute_shell_command(int fd, const char *command)
{ {
pid_t pid; #define READ_END 0
#define WRITE_END 1
int status; int status;
int pipefd_c2p[2];
int pipefd_p2c[2];
#if defined(__linux__)
static bool done_once = false;
if (!done_once)
{
atexit(&terminate_shell_command);
done_once = true;
}
#endif
// Create Pipes
if (pipe(pipefd_c2p) == -1 || pipe(pipefd_p2c) == -1)
{
tio_error_print("pipe() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
// Fork a child process // Fork a child process
pid = fork(); shell_command_pid = fork();
if (pid == -1) if (shell_command_pid == -1)
{ {
// Error occurred // Error occurred
tio_error_print("fork() failed (%s)", strerror(errno)); tio_error_print("fork() failed (%s)", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
else if (pid == 0) else if (shell_command_pid == 0)
{ {
// Child process // Child process
close(pipefd_c2p[READ_END]);
close(pipefd_p2c[WRITE_END]);
tio_printf("Executing shell command '%s'", command); tio_printf("Executing shell command '%s'", command);
// Redirect stdout and stderr to the file descriptor // Redirect stdin and stdout to the parent-pipe
if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) if (dup2(pipefd_c2p[WRITE_END], STDOUT_FILENO) == -1 ||
dup2(pipefd_p2c[READ_END], STDIN_FILENO) == -1)
{ {
tio_error_print("dup2() failed (%s)", strerror(errno)); tio_error_print("dup2() failed (%s)", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// command prefix '?' excludes stderr from redirection
if (command[0] == '?')
{
command += 1;
}
else
{
if (dup2(pipefd_c2p[WRITE_END], STDERR_FILENO) == -1)
{
tio_error_print("dup2() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
}
// Execute the shell command // Execute the shell command
execl("/bin/sh", "sh", "-c", command, (char *)NULL); execl("/bin/sh", "sh", "-c", command, (char *)NULL);
// If execlp() returns, it means an error occurred // If execlp() returns, it means an error occurred
close(pipefd_c2p[WRITE_END]);
close(pipefd_p2c[READ_END]);
perror("execlp"); perror("execlp");
tio_error_print("execlp() failed (%s)", strerror(errno)); tio_error_print("execlp() failed (%s)", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@ -228,9 +320,69 @@ int execute_shell_command(int fd, const char *command)
else else
{ {
// Parent process // Parent process
fd_set rdfs;
int maxfd;
char buf[BUFSIZ];
int bytes;
close(pipefd_c2p[WRITE_END]);
close(pipefd_p2c[READ_END]);
while (true)
{
FD_ZERO(&rdfs);
FD_SET(fd, &rdfs);
FD_SET(pipefd_c2p[READ_END], &rdfs);
maxfd = MAX(fd, pipefd_c2p[READ_END]);
/* Block until input becomes available or timeout */
status = select(maxfd + 1, &rdfs, NULL, NULL, NULL);
if (status < 0)
{
tio_warning_printf("select() failed(%s)", strerror(errno));
break;
}
if (FD_ISSET(fd, &rdfs))
{
bytes = read(fd, buf, sizeof(buf));
if (bytes <= 0)
{
tio_warning_printf("Could not read from tty device");
break;
}
rx_total += bytes;
write(pipefd_p2c[WRITE_END], buf, bytes);
}
if (FD_ISSET(pipefd_c2p[READ_END], &rdfs))
{
// Read pipe and transfer to tty device.
bytes = read(pipefd_c2p[READ_END], buf, sizeof(buf));
if (bytes < 0)
{
tio_warning_printf("Could not write to tty device");
}
else if (bytes == 0)
{
// Shell command has finished.
break;
}
if (tty_write(fd, buf, bytes) < 0)
{
tio_warning_printf("Could not write to tty device");
}
tty_sync(fd);
}
}
close(pipefd_p2c[WRITE_END]);
close(pipefd_c2p[READ_END]);
// Wait for the child process to finish // Wait for the child process to finish
waitpid(pid, &status, 0); waitpid(shell_command_pid, &status, 0);
shell_command_pid = 0;
if (WIFEXITED(status)) if (WIFEXITED(status))
{ {
@ -243,11 +395,30 @@ int execute_shell_command(int fd, const char *command)
return -1; return -1;
} }
} }
return 0; return 0;
} }
#if defined(__linux__)
void terminate_shell_command(void)
{
// If previous shell command pid is remain, terminate it.
if (shell_command_pid != 0)
{
#define PKILL_BUFSIZ 80
char pkill_buf[PKILL_BUFSIZ] = {0};
int bytes;
bytes = snprintf(pkill_buf, PKILL_BUFSIZ, "/usr/bin/pkill -P %d", shell_command_pid);
if (bytes > 0 && bytes < PKILL_BUFSIZ)
{
system(pkill_buf);
}
}
}
#endif
void clear_line() void clear_line()
{ {
print("\r\033[K"); printf("\r\033[K");
} }

View file

@ -24,15 +24,27 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#define POLL_NOWAIT (0)
#define POLL_FOREVER (-1)
#define TOSTRING_(x) #x
#define TOSTR(x) TOSTRING_(x)
#define UNUSED(expr) do { (void)(expr); } while (0) #define UNUSED(expr) do { (void)(expr); } while (0)
void delay(long ms); void delay(long ms);
int ctrl_key_code(unsigned char key); int ctrl_key_code(unsigned char key);
int ctrl_key_char(int key_code);
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); unsigned long djb2_hash(const unsigned char *str);
void *base62_encode(unsigned long num, char *output); void *base62_encode(unsigned long num, char *output);
int read_poll(int fd, void *data, size_t len, int timeout); int read_poll(int fd, void *data, size_t len, int timeout);
ssize_t write_poll(int fd, const void *data, size_t len, int timeout);
double get_current_time(void); double get_current_time(void);
bool match_patterns(const char *string, const char *patterns); bool match_patterns(const char *string, const char *patterns);
int execute_shell_command(int fd, const char *command); int execute_shell_command(int fd, const char *command);
void clear_line(); void clear_line();
#if defined(__linux__)
void terminate_shell_command(void);
#endif

View file

@ -44,12 +44,14 @@ enum opt_t
OPT_LOG_DIRECTORY, OPT_LOG_DIRECTORY,
OPT_LOG_STRIP, OPT_LOG_STRIP,
OPT_LOG_APPEND, OPT_LOG_APPEND,
OPT_OUTPUT_LINE_DELAY_CHAR,
OPT_LINE_PULSE_DURATION, OPT_LINE_PULSE_DURATION,
OPT_RS485, OPT_RS485,
OPT_RS485_CONFIG, OPT_RS485_CONFIG,
OPT_ALERT, OPT_ALERT,
OPT_COMPLETE_PROFILES, OPT_COMPLETE_PROFILES,
OPT_MUTE, OPT_MUTE,
OPT_SCRIPT_INIT_FILE,
OPT_SCRIPT, OPT_SCRIPT,
OPT_SCRIPT_FILE, OPT_SCRIPT_FILE,
OPT_SCRIPT_RUN, OPT_SCRIPT_RUN,
@ -59,8 +61,12 @@ enum opt_t
OPT_EXCLUDE_DRIVERS, OPT_EXCLUDE_DRIVERS,
OPT_EXCLUDE_TIDS, OPT_EXCLUDE_TIDS,
OPT_EXEC, OPT_EXEC,
OPT_RAW,
OPT_RAW_INTERACTIVE,
OPT_KEYMAP,
}; };
// clang-format off
/* Default options */ /* Default options */
struct option_t option = struct option_t option =
{ {
@ -72,6 +78,7 @@ struct option_t option =
.parity = PARITY_NONE, .parity = PARITY_NONE,
.output_delay = 0, .output_delay = 0,
.output_line_delay = 0, .output_line_delay = 0,
.output_line_delay_char = '\n',
.dtr_pulse_duration = 100, .dtr_pulse_duration = 100,
.rts_pulse_duration = 100, .rts_pulse_duration = 100,
.cts_pulse_duration = 100, .cts_pulse_duration = 100,
@ -79,6 +86,7 @@ struct option_t option =
.dcd_pulse_duration = 100, .dcd_pulse_duration = 100,
.ri_pulse_duration = 100, .ri_pulse_duration = 100,
.no_reconnect = false, .no_reconnect = false,
.no_tty_restore = false,
.auto_connect = AUTO_CONNECT_DIRECT, .auto_connect = AUTO_CONNECT_DIRECT,
.log = false, .log = false,
.log_append = false, .log_append = false,
@ -101,6 +109,7 @@ struct option_t option =
.rs485_delay_rts_after_send = -1, .rs485_delay_rts_after_send = -1,
.alert = ALERT_NONE, .alert = ALERT_NONE,
.complete_profiles = false, .complete_profiles = false,
.script_init_filename = NULL,
.script = NULL, .script = NULL,
.script_filename = NULL, .script_filename = NULL,
.script_run = SCRIPT_RUN_ALWAYS, .script_run = SCRIPT_RUN_ALWAYS,
@ -124,7 +133,13 @@ struct option_t option =
.map_o_nulbrk = false, .map_o_nulbrk = false,
.map_i_msb2lsb = false, .map_i_msb2lsb = false,
.map_o_ign_cr = false, .map_o_ign_cr = false,
.raw = RAW_ON_DELAY,
.raw_interactive = RAW_OFF,
.keymap = NULL,
}; };
// clang-format on
struct keymap_t keymaps[KEYMAP_MAX] = {0};
void option_print_help(char *argv[]) void option_print_help(char *argv[])
{ {
@ -142,12 +157,14 @@ void option_print_help(char *argv[])
printf(" -p, --parity odd|even|none|mark|space Parity (default: none)\n"); printf(" -p, --parity odd|even|none|mark|space Parity (default: none)\n");
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(" --output-line-delay-char cr|lf Output line delay trigger character (default: lf)\n");
printf(" --line-pulse-duration <duration> Set line pulse duration\n"); printf(" --line-pulse-duration <duration> Set line pulse duration\n");
printf(" -a, --auto-connect new|latest|direct Automatic connect strategy (default: direct)\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-devices <pattern> Exclude devices by pattern\n");
printf(" --exclude-drivers <pattern> Exclude drivers by pattern\n"); printf(" --exclude-drivers <pattern> Exclude drivers by pattern\n");
printf(" --exclude-tids <pattern> Exclude topology IDs by pattern\n"); printf(" --exclude-tids <pattern> Exclude topology IDs by pattern\n");
printf(" -n, --no-reconnect Do not reconnect\n"); printf(" -n, --no-reconnect Do not reconnect\n");
printf(" -N, --no-tty-restore Do not restore initial TTY device settings\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|hexN Select output mode (default: normal)\n"); printf(" --output-mode normal|hex|hexN Select output mode (default: normal)\n");
@ -161,12 +178,16 @@ void option_print_help(char *argv[])
printf(" --log-append Append to log file\n"); printf(" --log-append Append to log file\n");
printf(" --log-strip Strip control characters and escape sequences\n"); printf(" --log-strip Strip control characters and escape sequences\n");
printf(" -m, --map <flags> Map characters\n"); printf(" -m, --map <flags> Map characters\n");
printf(" --keymap <keymaps> Set key-script mappings\n");
printf(" -c, --color 0..255|bold|none|list Colorize tio text (default: bold)\n"); printf(" -c, --color 0..255|bold|none|list Colorize tio text (default: bold)\n");
printf(" -S, --socket <socket> Redirect I/O to socket\n"); printf(" -S, --socket <socket> Redirect I/O to socket\n");
printf(" --raw off|on|on-nodelay Select raw mode for non-interactive use (default: on)\n");
printf(" --raw-interactive off|on|on-nodelay Select raw mode for interactive use (default: off)\n");
printf(" --rs-485 Enable RS-485 mode\n"); printf(" --rs-485 Enable RS-485 mode\n");
printf(" --rs-485-config <config> Set RS-485 configuration\n"); printf(" --rs-485-config <config> Set RS-485 configuration\n");
printf(" --alert bell|blink|none Alert on connect/disconnect (default: none)\n"); printf(" --alert bell|blink|none Alert on connect/disconnect (default: none)\n");
printf(" --mute Mute tio messages\n"); printf(" --mute Mute tio messages\n");
printf(" --script-init-file <filename> Set initial script file to run at startup\n");
printf(" --script <string> Run script from string\n"); printf(" --script <string> Run script from string\n");
printf(" --script-file <filename> Run script from file\n"); printf(" --script-file <filename> Run script from file\n");
printf(" --script-run once|always|never Run script on connect (default: always)\n"); printf(" --script-run once|always|never Run script on connect (default: always)\n");
@ -446,9 +467,9 @@ void option_parse_timestamp(const char *arg, timestamp_t *timestamp)
} }
} }
const char *option_alert_state_to_string(alert_t state) const char *option_alert_state_to_string(alert_t alert_state)
{ {
switch (state) switch (alert_state)
{ {
case ALERT_NONE: case ALERT_NONE:
return "none"; return "none";
@ -502,6 +523,25 @@ void option_parse_auto_connect(const char *arg, auto_connect_t *auto_connect)
} }
} }
void option_parse_output_line_delay_char(const char *arg)
{
assert(arg != NULL);
if (strcmp("cr", arg) == 0)
{
option.output_line_delay_char = '\r';
}
else if (strcmp("lf", arg) == 0)
{
option.output_line_delay_char = '\n';
}
else
{
tio_error_print("Invalid char '%s'", arg);
exit(EXIT_FAILURE);
}
}
void option_parse_line_pulse_duration(const char *arg) void option_parse_line_pulse_duration(const char *arg)
{ {
bool token_found = true; bool token_found = true;
@ -703,6 +743,44 @@ const char *option_output_mode_to_string(output_mode_t mode)
return NULL; return NULL;
} }
void option_parse_raw(const char *arg, raw_t *raw)
{
assert(arg != NULL);
if (strcmp("off", arg) == 0)
{
*raw = RAW_OFF;
}
else if (strcmp("on", arg) == 0)
{
*raw = RAW_ON_DELAY;
}
else if (strcmp("on-nodelay", arg) == 0)
{
*raw = RAW_ON_NODELAY;
}
else
{
tio_error_print("Invalid raw option '%s'", arg);
exit(EXIT_FAILURE);
}
}
const char *option_raw_to_string(raw_t raw)
{
switch (raw)
{
case RAW_OFF:
return "off";
case RAW_ON_DELAY:
return "on";
case RAW_ON_NODELAY:
return "on-nodelay";
}
return NULL;
}
void option_parse_script_run(const char *arg, script_run_t *script_run) void option_parse_script_run(const char *arg, script_run_t *script_run)
{ {
assert(arg != NULL); assert(arg != NULL);
@ -820,27 +898,34 @@ void option_parse_mappings(const char *map)
void options_print() void options_print()
{ {
/* note: negative true/false settings are rephrased as affirmative no/yes. */
tio_printf(" Device: %s", device_name); 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_to_string(option.flow)); tio_printf(" Flow: %s", option_flow_to_string(option.flow));
tio_printf(" Stopbits: %d", option.stopbits); tio_printf(" Stopbits: %d", option.stopbits);
tio_printf(" Parity: %s", option_parity_to_string(option.parity)); tio_printf(" Parity: %s", option_parity_to_string(option.parity));
tio_printf(" Local echo: %s", option.local_echo ? "true" : "false"); tio_printf(" Local echo: %s", option.local_echo ? "yes" : "no");
tio_printf(" Timestamp: %s", option_timestamp_format_to_string(option.timestamp)); tio_printf(" Timestamp: %s", option_timestamp_format_to_string(option.timestamp));
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(" Output line delay char: %s", option.output_line_delay_char == '\r' ? "cr" : "lf");
tio_printf(" Automatic connect strategy: %s", option_auto_connect_state_to_string(option.auto_connect)); tio_printf(" Automatic connect strategy: %s", option_auto_connect_state_to_string(option.auto_connect));
tio_printf(" Automatic reconnect: %s", option.no_reconnect ? "true" : "false"); tio_printf(" Automatic reconnect: %s", option.no_reconnect ? "no" : "yes");
tio_printf(" TTY device settings restore: %s", option.no_tty_restore ? "no" : "yes");
// clang-format off
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,
option.dsr_pulse_duration, option.dsr_pulse_duration,
option.dcd_pulse_duration, option.dcd_pulse_duration,
option.ri_pulse_duration); option.ri_pulse_duration);
// clang-format on
tio_printf(" Input mode: %s", option_input_mode_to_string(option.input_mode)); tio_printf(" Input mode: %s", option_input_mode_to_string(option.input_mode));
tio_printf(" Output mode: %s", option_output_mode_to_string(option.output_mode)); tio_printf(" Output mode: %s", option_output_mode_to_string(option.output_mode));
tio_printf(" Raw (non-interactive): %s", option_raw_to_string(option.raw));
tio_printf(" Raw interactive: %s", option_raw_to_string(option.raw_interactive));
tio_printf(" Alert: %s", option_alert_state_to_string(option.alert)); tio_printf(" Alert: %s", option_alert_state_to_string(option.alert));
if (option.log) if (option.log)
{ {
@ -856,11 +941,20 @@ void options_print()
{ {
tio_printf(" Socket: %s", option.socket); tio_printf(" Socket: %s", option.socket);
} }
if (option.script_init_filename != NULL)
{
tio_printf(" Script init file: %s", option.script_init_filename);
}
if (option.script_filename != NULL) if (option.script_filename != NULL)
{ {
tio_printf(" Script file: %s", option.script_filename); tio_printf(" Script file: %s", option.script_filename);
tio_printf(" Script run: %s", script_run_state_to_string(option.script_run)); tio_printf(" Script run: %s", script_run_state_to_string(option.script_run));
} }
if (option.script != NULL)
{
tio_printf(" Script command: %s", option.script);
tio_printf(" Script run: %s", script_run_state_to_string(option.script_run));
}
} }
void options_parse(int argc, char *argv[]) void options_parse(int argc, char *argv[])
@ -887,8 +981,9 @@ void options_parse(int argc, char *argv[])
option.vt100 = true; option.vt100 = true;
} }
while (1) while (true)
{ {
// clang-format off
static struct option long_options[] = static struct option long_options[] =
{ {
{"baudrate", required_argument, 0, 'b' }, {"baudrate", required_argument, 0, 'b' },
@ -898,12 +993,14 @@ void options_parse(int argc, char *argv[])
{"parity", required_argument, 0, 'p' }, {"parity", required_argument, 0, 'p' },
{"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' },
{"output-line-delay-char", required_argument, 0, OPT_OUTPUT_LINE_DELAY_CHAR},
{"line-pulse-duration", required_argument, 0, OPT_LINE_PULSE_DURATION }, {"line-pulse-duration", required_argument, 0, OPT_LINE_PULSE_DURATION },
{"auto-connect", required_argument, 0, 'a' }, {"auto-connect", required_argument, 0, 'a' },
{"exclude-devices", required_argument, 0, OPT_EXCLUDE_DEVICES }, {"exclude-devices", required_argument, 0, OPT_EXCLUDE_DEVICES },
{"exclude-drivers", required_argument, 0, OPT_EXCLUDE_DRIVERS }, {"exclude-drivers", required_argument, 0, OPT_EXCLUDE_DRIVERS },
{"exclude-tids", required_argument, 0, OPT_EXCLUDE_TIDS }, {"exclude-tids", required_argument, 0, OPT_EXCLUDE_TIDS },
{"no-reconnect", no_argument, 0, 'n' }, {"no-reconnect", no_argument, 0, 'n' },
{"no-tty-restore", 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 },
@ -916,13 +1013,17 @@ void options_parse(int argc, char *argv[])
{"log-strip", no_argument, 0, OPT_LOG_STRIP }, {"log-strip", no_argument, 0, OPT_LOG_STRIP },
{"socket", required_argument, 0, 'S' }, {"socket", required_argument, 0, 'S' },
{"map", required_argument, 0, 'm' }, {"map", required_argument, 0, 'm' },
{"keymap", required_argument, 0, OPT_KEYMAP },
{"color", required_argument, 0, 'c' }, {"color", required_argument, 0, 'c' },
{"input-mode", required_argument, 0, OPT_INPUT_MODE }, {"input-mode", required_argument, 0, OPT_INPUT_MODE },
{"output-mode", required_argument, 0, OPT_OUTPUT_MODE }, {"output-mode", required_argument, 0, OPT_OUTPUT_MODE },
{"raw", required_argument, 0, OPT_RAW },
{"raw-interactive", required_argument, 0, OPT_RAW_INTERACTIVE },
{"rs-485", no_argument, 0, OPT_RS485 }, {"rs-485", no_argument, 0, OPT_RS485 },
{"rs-485-config", required_argument, 0, OPT_RS485_CONFIG }, {"rs-485-config", required_argument, 0, OPT_RS485_CONFIG },
{"alert", required_argument, 0, OPT_ALERT }, {"alert", required_argument, 0, OPT_ALERT },
{"mute", no_argument, 0, OPT_MUTE }, {"mute", no_argument, 0, OPT_MUTE },
{"script-init-file", required_argument, 0, OPT_SCRIPT_INIT_FILE },
{"script", required_argument, 0, OPT_SCRIPT }, {"script", required_argument, 0, OPT_SCRIPT },
{"script-file", required_argument, 0, OPT_SCRIPT_FILE }, {"script-file", required_argument, 0, OPT_SCRIPT_FILE },
{"script-run", required_argument, 0, OPT_SCRIPT_RUN }, {"script-run", required_argument, 0, OPT_SCRIPT_RUN },
@ -932,12 +1033,13 @@ void options_parse(int argc, char *argv[])
{"complete-profiles", no_argument, 0, OPT_COMPLETE_PROFILES }, {"complete-profiles", no_argument, 0, OPT_COMPLETE_PROFILES },
{0, 0, 0, 0 } {0, 0, 0, 0 }
}; };
// clang-format on
/* getopt_long stores the option index here */ /* getopt_long stores the option index here */
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:a:netLlS:m:c:xrvh", long_options, &option_index); c = getopt_long(argc, argv, "b:d:f:s:p:o:O:a:nNetLlS: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)
@ -983,6 +1085,10 @@ void options_parse(int argc, char *argv[])
option_string_to_integer(optarg, &option.output_line_delay, "output line delay", 0, INT_MAX); option_string_to_integer(optarg, &option.output_line_delay, "output line delay", 0, INT_MAX);
break; break;
case OPT_OUTPUT_LINE_DELAY_CHAR:
option_parse_output_line_delay_char(optarg);
break;
case OPT_LINE_PULSE_DURATION: case OPT_LINE_PULSE_DURATION:
option_parse_line_pulse_duration(optarg); option_parse_line_pulse_duration(optarg);
break; break;
@ -1007,6 +1113,10 @@ void options_parse(int argc, char *argv[])
option.no_reconnect = true; option.no_reconnect = true;
break; break;
case 'N':
option.no_tty_restore = true;
break;
case 'e': case 'e':
option.local_echo = true; option.local_echo = true;
break; break;
@ -1088,6 +1198,10 @@ void options_parse(int argc, char *argv[])
option.mute = true; option.mute = true;
break; break;
case OPT_SCRIPT_INIT_FILE:
option.script_init_filename = optarg;
break;
case OPT_SCRIPT: case OPT_SCRIPT:
option.script = optarg; option.script = optarg;
break; break;
@ -1104,6 +1218,19 @@ void options_parse(int argc, char *argv[])
option.exec = optarg; option.exec = optarg;
break; break;
case OPT_RAW:
option_parse_raw(optarg, &option.raw);
break;
case OPT_RAW_INTERACTIVE:
option_parse_raw(optarg, &option.raw_interactive);
break;
case OPT_KEYMAP:
option.keymap = optarg;
option_parse_key_mappings(optarg);
break;
case 'v': case 'v':
printf("tio %s\n", VERSION); printf("tio %s\n", VERSION);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
@ -1131,7 +1258,7 @@ void options_parse(int argc, char *argv[])
/* Assume first non-option is the target (tty device, profile, tid) */ /* Assume first non-option is the target (tty device, profile, tid) */
if (strcmp(option.target, "")) if (strcmp(option.target, ""))
{ {
optind++; optind++;
} }
else if (optind < argc) else if (optind < argc)
{ {
@ -1176,6 +1303,7 @@ void options_parse_final(int argc, char *argv[])
#ifdef __CYGWIN__ #ifdef __CYGWIN__
unsigned char portnum; unsigned char portnum;
char *tty_win; char *tty_win;
// clang-format off
if ( ((strncmp("COM", option.target, 3) == 0) if ( ((strncmp("COM", option.target, 3) == 0)
|| (strncmp("com", option.target, 3) == 0) ) || (strncmp("com", option.target, 3) == 0) )
&& (sscanf(option.target + 3, "%hhu", &portnum) == 1) && (sscanf(option.target + 3, "%hhu", &portnum) == 1)
@ -1184,5 +1312,177 @@ void options_parse_final(int argc, char *argv[])
asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1); asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1);
option.target = tty_win; option.target = tty_win;
} }
// clang-format on
#endif #endif
} }
int keymap_set(char *key_str, int key_len, char *func_str, int func_len)
{
char func_str_r[KEYMAP_FUNC_STR_MAX + 1];
char *srcp;
int dst_ofs;
int key_ofs;
int empty_idx, matched_idx, idx;
bool found_empty = false;
bool found_matched = false;
bool unset_requested = false;
if (key_str[key_len] != '\0' || func_str[func_len] != '\0')
{
return -1;
}
/* key_str should not include spaces */
key_ofs = 0;
for (key_ofs = 0; key_ofs < key_len; key_ofs++)
{
if (key_str[key_ofs] == ' ')
{
tio_error_printf("Key should not include space");
return -1;
}
}
/* check disallowed key_str */
if (strcmp(key_str, "q") == 0)
{
tio_error_printf("Key %s is immutable", key_str);
return -1;
}
/* remove prefix spaces and postfix spaces from func_str */
for (srcp = func_str; *srcp != '\0'; srcp++)
{
if (*srcp != ' ')
{
break;
}
}
strncpy(func_str_r, srcp, KEYMAP_KEY_STR_MAX);
func_str_r[KEYMAP_KEY_STR_MAX] = '\0';
for (dst_ofs = strlen(func_str_r) - 1; dst_ofs >= 0; dst_ofs--)
{
if (func_str_r[dst_ofs] != ' ')
{
func_str_r[dst_ofs + 1] = '\0';
break;
}
}
if (dst_ofs < 0)
{
func_str_r[0] = '\0';
}
if (strcmp(func_str_r, "nil") == 0 || func_str_r[0] == '\0')
{
unset_requested = true;
}
/* search for entry which key matched or is empty */
for (idx = 0; idx < KEYMAP_MAX; idx++)
{
if (found_empty == false && keymaps[idx].key[0] == '\0')
{
empty_idx = idx;
found_empty = true;
}
if (found_matched == false && strcmp(keymaps[idx].key, key_str) == 0)
{
matched_idx = idx;
found_matched = true;
}
if (found_empty && found_matched)
{
break;
}
}
/* update entry */
if (unset_requested)
{
if (found_matched)
{
keymaps[matched_idx].key[0] = '\0';
keymaps[matched_idx].func[0] = '\0';
}
}
else /* set requested */
{
if (found_matched)
{
strcpy(keymaps[matched_idx].key, key_str);
strcpy(keymaps[matched_idx].func, func_str);
}
else if (found_empty)
{
strcpy(keymaps[empty_idx].key, key_str);
strcpy(keymaps[empty_idx].func, func_str);
}
else
{
tio_error_printf("Too many keymaps", key_str);
return -1;
}
}
return 0;
}
void keymaps_print(const char *title, int indent)
{
int idx;
bool keymap_title_done = false;
for (idx = 0; idx < KEYMAP_MAX; idx++)
{
if (keymaps[idx].key[0] == '\0')
{
continue;
}
if (!keymap_title_done)
{
if (title[0] != '\0')
{
tio_printf("%s", title);
}
keymap_title_done = true;
}
tio_printf("%*sctrl-%c %s : %s", indent, " ", option.prefix_key, keymaps[idx].key, keymaps[idx].func);
}
}
void option_parse_key_mappings(const char *keymap)
{
char key_str[KEYMAP_KEY_STR_MAX + 1];
char func_str[KEYMAP_FUNC_STR_MAX + 1];
int key_len, func_len;
char *buffer;
char *cp;
if (keymap == NULL)
{
return;
}
/* Parse specified key mappings */
buffer = strdup(keymap);
cp = strchr(buffer, '@');
if (cp == NULL)
{
tio_error_print("Can't find keymap top character '@'");
goto parse_end;
}
while (sscanf(cp, "@%" TOSTR(KEYMAP_KEY_STR_MAX) "[^=]=%" TOSTR(KEYMAP_FUNC_STR_MAX) "[^@]", key_str, func_str) == 2)
{
key_len = strlen(key_str);
func_len = strlen(func_str);
keymap_set(key_str, key_len, func_str, func_len);
cp = strchr(cp + key_len + func_len + 2, '@');
if (cp == NULL)
{
break;
}
}
parse_end:
free(buffer);
}

View file

@ -43,6 +43,13 @@ typedef enum
OUTPUT_MODE_END, OUTPUT_MODE_END,
} output_mode_t; } output_mode_t;
typedef enum
{
RAW_OFF,
RAW_ON_DELAY,
RAW_ON_NODELAY,
} raw_t;
/* Options */ /* Options */
struct option_t struct option_t
{ {
@ -54,6 +61,7 @@ struct option_t
parity_t parity; parity_t parity;
int output_delay; int output_delay;
int output_line_delay; int output_line_delay;
char output_line_delay_char;
int dtr_pulse_duration; int dtr_pulse_duration;
int rts_pulse_duration; int rts_pulse_duration;
int cts_pulse_duration; int cts_pulse_duration;
@ -61,6 +69,7 @@ struct option_t
int dcd_pulse_duration; int dcd_pulse_duration;
int ri_pulse_duration; int ri_pulse_duration;
bool no_reconnect; bool no_reconnect;
bool no_tty_restore;
auto_connect_t auto_connect; auto_connect_t auto_connect;
bool log; bool log;
bool log_append; bool log_append;
@ -73,6 +82,8 @@ struct option_t
int color; int color;
input_mode_t input_mode; input_mode_t input_mode;
output_mode_t output_mode; output_mode_t output_mode;
raw_t raw;
raw_t raw_interactive;
char prefix_code; char prefix_code;
char prefix_key; char prefix_key;
bool prefix_enabled; bool prefix_enabled;
@ -83,6 +94,7 @@ struct option_t
int32_t rs485_delay_rts_after_send; int32_t rs485_delay_rts_after_send;
alert_t alert; alert_t alert;
bool complete_profiles; bool complete_profiles;
char *script_init_filename;
char *script; char *script;
char *script_filename; char *script_filename;
script_run_t script_run; script_run_t script_run;
@ -93,6 +105,7 @@ struct option_t
int hex_n_value; int hex_n_value;
bool vt100; bool vt100;
char *exec; char *exec;
char *keymap;
bool map_i_nl_cr; bool map_i_nl_cr;
bool map_i_cr_nl; bool map_i_cr_nl;
bool map_ign_cr; bool map_ign_cr;
@ -108,7 +121,18 @@ struct option_t
bool map_o_ign_cr; bool map_o_ign_cr;
}; };
#define KEYMAP_MAX 32
#define KEYMAP_KEY_STR_MAX 7
#define KEYMAP_FUNC_STR_MAX 127
struct keymap_t
{
char key[KEYMAP_KEY_STR_MAX + 1];
char func[KEYMAP_FUNC_STR_MAX + 1];
};
extern struct option_t option; extern struct option_t option;
extern struct keymap_t keymaps[KEYMAP_MAX];
void options_print(); void options_print();
void options_parse(int argc, char *argv[]); void options_parse(int argc, char *argv[]);
@ -121,7 +145,9 @@ void option_parse_parity(const char *arg, parity_t *parity);
void option_parse_output_mode(const char *arg, output_mode_t *mode); void option_parse_output_mode(const char *arg, output_mode_t *mode);
void option_parse_input_mode(const char *arg, input_mode_t *mode); void option_parse_input_mode(const char *arg, input_mode_t *mode);
void option_parse_raw(const char *arg, raw_t *raw);
void option_parse_output_line_delay_char(const char *arg);
void option_parse_line_pulse_duration(const char *arg); void option_parse_line_pulse_duration(const char *arg);
void option_parse_script_run(const char *arg, script_run_t *script_run); void option_parse_script_run(const char *arg, script_run_t *script_run);
void option_parse_alert(const char *arg, alert_t *alert); void option_parse_alert(const char *arg, alert_t *alert);
@ -133,3 +159,8 @@ void option_parse_timestamp(const char *arg, timestamp_t *timestamp);
const char* option_timestamp_format_to_string(timestamp_t timestamp); const char* option_timestamp_format_to_string(timestamp_t timestamp);
void option_parse_mappings(const char *map); void option_parse_mappings(const char *map);
void option_parse_key_mappings(const char *keymap);
const char* option_raw_to_string(raw_t raw);
void keymaps_print(const char *title, int indent);

View file

@ -19,85 +19,146 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#include "readline.h"
#include "print.h" #include "print.h"
#include "misc.h" #include "misc.h"
#include <assert.h>
#define RL_LINE_LENGTH_MAX PATH_MAX #define RL_LINE_LENGTH_MAX PATH_MAX
#define RL_HISTORY_MAX 1000
static char rl_line[RL_LINE_LENGTH_MAX] = {}; typedef struct readline_s
static char *rl_history[RL_HISTORY_MAX]; {
static int rl_history_count = 0; char line[RL_LINE_LENGTH_MAX];
static int rl_history_index = 0; char *history[RL_HISTORY_MAX];
static int rl_line_length = 0; char prompt[RL_PROMPT_LENGTH_MAX];
static int rl_cursor_pos = 0; int prompt_length;
static int rl_escape = 0; int history_count;
int history_index;
int line_length;
int cursor_pos;
int escape;
} readline_t;
static void print_line(const char *string, int cursor_pos) void print_prompt(readline_t *rl)
{ {
clear_line(); clear_line();
print("%s", string); printf("%s", rl->prompt);
print("\r"); // Move the cursor back to the beginning printf("\r"); // Move the cursor back to the beginning
for (int i = 0; i < cursor_pos; ++i) for (int i = 0; i < rl->prompt_length; ++i)
{ {
print("\x1b[C"); // Move the cursor right printf("\x1b[C"); // Move the cursor right
} }
} }
void readline_init(void) void print_line(readline_t *rl)
{ {
rl_history_count = 0; clear_line();
rl_history_index = 0; printf("%s%s", rl->prompt, rl->line);
printf("\r"); // Move the cursor back to the beginning
for (int i = 0; i < rl->prompt_length + rl->cursor_pos; ++i)
{
printf("\x1b[C"); // Move the cursor right
}
}
readline_t *readline_create(void)
{
readline_t *rl = malloc(sizeof(readline_t));
if (rl == NULL)
return NULL;
readline_reinit(rl);
return rl;
}
void readline_reinit(readline_t *rl)
{
assert(rl != NULL);
rl->prompt[0] = '\0';
rl->prompt_length = 0;
rl->history_count = 0;
rl->history_index = 0;
for (int i = 0; i < RL_HISTORY_MAX; ++i) for (int i = 0; i < RL_HISTORY_MAX; ++i)
{ {
rl_history[i] = NULL; rl->history[i] = NULL;
} }
rl_line[0] = 0; rl->line[0] = 0;
rl_line_length = 0; rl->line_length = 0;
rl_cursor_pos = 0; rl->cursor_pos = 0;
rl_escape = 0; rl->escape = 0;
} }
char * readline_get(void) void readline_set_prompt(readline_t *rl, const char *prompt)
{ {
return rl_line; strncpy(rl->prompt, prompt, RL_PROMPT_LENGTH_MAX - 1);
rl->prompt[RL_PROMPT_LENGTH_MAX - 1] = '\0';
rl->prompt_length = strlen(rl->prompt);
} }
static void readline_input_char(char input_char) char * readline_get(readline_t *rl)
{ {
if (rl_line_length < RL_LINE_LENGTH_MAX - 1) assert(rl != NULL);
return rl->line;
}
void readline_prompt_for_input(readline_t *rl)
{
assert(rl != NULL);
rl->line[0] = 0;
rl->line_length = 0;
rl->cursor_pos = 0;
rl->escape = 0;
print_line(rl);
}
static void readline_input_char(readline_t *rl, char input_char)
{
assert(rl != NULL);
if (rl->line_length < RL_LINE_LENGTH_MAX - 1)
{ {
memmove(&rl_line[rl_cursor_pos + 1], &rl_line[rl_cursor_pos], rl_line_length - rl_cursor_pos); memmove(&rl->line[rl->cursor_pos + 1], &rl->line[rl->cursor_pos], rl->line_length - rl->cursor_pos);
rl_line[rl_cursor_pos] = input_char; rl->line[rl->cursor_pos] = input_char;
rl_line_length++; rl->line_length++;
rl_cursor_pos++; rl->cursor_pos++;
rl_line[rl_line_length] = '\0'; rl->line[rl->line_length] = '\0';
print_line(rl_line, rl_cursor_pos); print_line(rl);
} }
rl_escape = 0; rl->escape = 0;
} }
static void readline_input_cr(void) static void readline_input_cr(readline_t *rl)
{ {
if (rl_line_length > 0) rl->line[rl->line_length] = '\0';
if (rl->line_length > 0)
{ {
// Save to history // Different line only
if (rl_history_count < RL_HISTORY_MAX) if (rl->history_count == 0 ||
(rl->history_count > 0 &&
! (rl->history[rl->history_count - 1][rl->line_length] == '\0' &&
strncmp(rl->history[rl->history_count - 1], rl->line, rl->line_length) == 0)) )
{ {
rl_history[rl_history_count] = strndup(rl_line, rl_line_length); // Save to history
rl_history_count++; if (rl->history_count < RL_HISTORY_MAX)
} {
else rl->history[rl->history_count] = strndup(rl->line, rl->line_length);
{ rl->history_count++;
free(rl_history[0]); }
memmove(&rl_history[0], &rl_history[1], (RL_HISTORY_MAX - 1) * sizeof(char*)); else
rl_history[RL_HISTORY_MAX - 1] = strndup(rl_line, rl_line_length); {
free(rl->history[0]);
memmove(&rl->history[0], &rl->history[1], (RL_HISTORY_MAX - 1) * sizeof(char*));
rl->history[RL_HISTORY_MAX - 1] = strndup(rl->line, rl->line_length);
}
} }
} }
rl_line[rl_line_length] = '\0';
if (option.local_echo == false) if (option.local_echo == false)
{ {
clear_line(); clear_line();
@ -107,170 +168,171 @@ static void readline_input_cr(void)
print("\r\n"); print("\r\n");
} }
rl_line_length = 0; rl->line_length = 0;
rl_cursor_pos = 0; rl->cursor_pos = 0;
rl_history_index = rl_history_count; rl->history_index = rl->history_count;
rl_escape = 0; rl->escape = 0;
} }
static void readline_input_bs(void) static void readline_input_bs(readline_t *rl)
{ {
if (rl_cursor_pos > 0) if (rl->cursor_pos > 0)
{ {
memmove(&rl_line[rl_cursor_pos - 1], &rl_line[rl_cursor_pos], rl_line_length - rl_cursor_pos); memmove(&rl->line[rl->cursor_pos - 1], &rl->line[rl->cursor_pos], rl->line_length - rl->cursor_pos);
rl_line_length--; rl->line_length--;
rl_cursor_pos--; rl->cursor_pos--;
rl_line[rl_line_length] = '\0'; rl->line[rl->line_length] = '\0';
print_line(rl_line, rl_cursor_pos); print_line(rl);
} }
rl_escape = 0; rl->escape = 0;
} }
static void readline_input_escape(void) static void readline_input_escape(readline_t *rl)
{ {
rl_escape = 1; rl->escape = 1;
} }
static void readline_input_left_bracket(void) static void readline_input_left_bracket(readline_t *rl)
{ {
if (rl_escape == 1) if (rl->escape == 1)
{ {
rl_escape = 2; rl->escape = 2;
} }
else else
{ {
rl_escape = 0; readline_input_char(rl, '[');
rl->escape = 0;
} }
} }
static void readline_input_A(void) static void readline_input_A(readline_t *rl)
{ {
if (rl_escape == 2) if (rl->escape == 2)
{ {
// Up arrow // Up arrow
if (rl_history_index > 0) if (rl->history_index > 0)
{ {
rl_history_index--; rl->history_index--;
strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1); strncpy(rl->line, rl->history[rl->history_index], RL_LINE_LENGTH_MAX-1);
rl_line_length = strlen(rl_line); rl->line_length = strlen(rl->line);
rl_cursor_pos = rl_line_length; rl->cursor_pos = rl->line_length;
print_line(rl_line, rl_cursor_pos); print_line(rl);
} }
} }
else else
{ {
readline_input_char('A'); readline_input_char(rl, 'A');
} }
rl_escape = 0; rl->escape = 0;
} }
static void readline_input_B(void) static void readline_input_B(readline_t *rl)
{ {
if (rl_escape == 2) if (rl->escape == 2)
{ {
// Down arrow // Down arrow
if (rl_history_index < rl_history_count - 1) if (rl->history_index < rl->history_count - 1)
{ {
rl_history_index++; rl->history_index++;
strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1); strncpy(rl->line, rl->history[rl->history_index], RL_LINE_LENGTH_MAX-1);
rl_line_length = strlen(rl_line); rl->line_length = strlen(rl->line);
rl_cursor_pos = rl_line_length; rl->cursor_pos = rl->line_length;
print_line(rl_line, rl_cursor_pos); print_line(rl);
} }
else if (rl_history_index == rl_history_count - 1) else if (rl->history_index == rl->history_count - 1)
{ {
rl_history_index++; rl->history_index++;
rl_line_length = 0; rl->line_length = 0;
rl_cursor_pos = 0; rl->cursor_pos = 0;
rl_line[rl_line_length] = '\0'; rl->line[rl->line_length] = '\0';
print_line(rl_line, rl_cursor_pos); print_line(rl);
} }
} }
else else
{ {
readline_input_char('B'); readline_input_char(rl, 'B');
} }
rl_escape = 0; rl->escape = 0;
} }
static void readline_input_C(void) static void readline_input_C(readline_t *rl)
{ {
if (rl_escape == 2) if (rl->escape == 2)
{ {
// Right arrow // Right arrow
if (rl_cursor_pos < rl_line_length) if (rl->cursor_pos < rl->line_length)
{ {
rl_cursor_pos++; rl->cursor_pos++;
print("\x1b[C"); print("\x1b[C");
} }
} }
else else
{ {
readline_input_char('C'); readline_input_char(rl, 'C');
} }
rl_escape = 0; rl->escape = 0;
} }
static void readline_input_D(void) static void readline_input_D(readline_t *rl)
{ {
if (rl_escape == 2) if (rl->escape == 2)
{ {
// Left arrow // Left arrow
if (rl_cursor_pos > 0) if (rl->cursor_pos > 0)
{ {
rl_cursor_pos--; rl->cursor_pos--;
print("\b"); print("\b");
} }
} }
else else
{ {
readline_input_char('D'); readline_input_char(rl, 'D');
} }
rl_escape = 0; rl->escape = 0;
} }
void readline_input(char input_char) void readline_input(readline_t *rl, char input_char)
{ {
switch (input_char) switch (input_char)
{ {
case '\r': // Carriage return case '\r': // Carriage return
readline_input_cr(); readline_input_cr(rl);
break; break;
case 127: // Backspace case 127: // Backspace
readline_input_bs(); readline_input_bs(rl);
break; break;
case 27: // Escape case 27: // Escape
readline_input_escape(); readline_input_escape(rl);
break; break;
case '[': case '[':
readline_input_left_bracket(); readline_input_left_bracket(rl);
break; break;
case 'A': case 'A':
readline_input_A(); readline_input_A(rl);
break; break;
case 'B': case 'B':
readline_input_B(); readline_input_B(rl);
break; break;
case 'C': case 'C':
readline_input_C(); readline_input_C(rl);
break; break;
case 'D': case 'D':
readline_input_D(); readline_input_D(rl);
break; break;
default: default:
readline_input_char(input_char); readline_input_char(rl, input_char);
break; break;
} }
} }

View file

@ -21,6 +21,15 @@
#pragma once #pragma once
void readline_init(void); #define RL_HISTORY_MAX 500
void readline_input(char input_char); #define RL_PROMPT_LENGTH_MAX 16
char * readline_get(void);
typedef struct readline_s readline_t;
readline_t *readline_create(void);
void readline_reinit(readline_t *rl);
void readline_set_prompt(readline_t *rl, const char *prompt);
void readline_prompt_for_input(readline_t *rl);
void readline_input(readline_t *rl, char input_char);
char *readline_get(readline_t *rl);
void print_prompt(readline_t *rl);

File diff suppressed because it is too large Load diff

View file

@ -29,5 +29,10 @@ typedef enum
SCRIPT_RUN_END, SCRIPT_RUN_END,
} script_run_t; } script_run_t;
void script_run(int fd, const char *script_filename); void script_interp_init(void);
void script_device_bind(int fd);
void script_device_unbind(void);
void script_run(const char *script_filename);
void script_run_as_specified_by_options(void);
void script_do_line(const char *script_line);
const char *script_run_state_to_string(script_run_t state); const char *script_run_state_to_string(script_run_t state);

778
src/tty.c

File diff suppressed because it is too large Load diff

View file

@ -69,13 +69,25 @@ typedef struct
bool reserved; bool reserved;
} tty_line_config_t; } tty_line_config_t;
typedef enum
{
STATE_INTERACTIVE,
STATE_STARTING,
STATE_PIPED_INPUT,
STATE_EXEC_SHELL_COMMAND,
STATE_XYMODEM,
} state_t;
extern const char *device_name; extern const char *device_name;
extern bool interactive_mode; extern bool interactive_mode;
extern state_t state;
extern unsigned long rx_total, tx_total;
void stdout_configure(void); void stdout_configure(void);
void stdin_configure(void); void stdin_configure(void);
void tty_configure(void); void tty_configure(void);
void tty_reconfigure(void); void tty_reconfigure(void);
void tty_init(void);
int tty_connect(void); int tty_connect(void);
void tty_wait_for_device(void); void tty_wait_for_device(void);
void list_serial_devices(void); void list_serial_devices(void);
@ -84,3 +96,10 @@ void tty_input_thread_wait_ready(void);
void tty_line_set(int fd, tty_line_config_t line_config[]); void tty_line_set(int fd, tty_line_config_t line_config[]);
void tty_search(void); void tty_search(void);
GList *tty_search_for_serial_devices(void); GList *tty_search_for_serial_devices(void);
void forward_to_tty(int fd, char output_char);
ssize_t tty_write(int fd, const void *buffer, size_t count);
void tty_sync(int fd);
int tty_tcsetattr(int fd);
int tty_inkey(int mseconds);
int tty_simple_readln(const char *prompt);
int tty_subcmd_readln(const char *title_prompt);

File diff suppressed because it is too large Load diff

View file

@ -21,14 +21,15 @@
#pragma once #pragma once
typedef enum { typedef enum
{
XMODEM_1K, XMODEM_1K,
XMODEM_CRC, XMODEM_CRC,
XMODEM_SUM,
YMODEM, YMODEM,
} modem_mode_t; } modem_mode_t;
extern char key_hit; extern char key_hit;
int xymodem_send(int sio, const char *filename, modem_mode_t mode); int xymodem_send(int sio, const char *filename, modem_mode_t mode);
int xymodem_receive(int sio, const char *filename, modem_mode_t mode); int xymodem_receive(int sio, const char *filename, modem_mode_t mode);