mirror of
https://github.com/tio/tio.git
synced 2026-05-01 14:57:59 +02:00
Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fb3a64ba2 | ||
|
|
3af4c5591e | ||
|
|
cce94b9d92 | ||
|
|
86f48a2fb6 | ||
|
|
381c0b7823 | ||
|
|
8f33cff6ea | ||
|
|
9d00cd3492 | ||
|
|
3e0b2d861d | ||
|
|
a1217af4c6 | ||
|
|
58bf5c5008 | ||
|
|
7e61a34df3 | ||
|
|
2fb788f817 | ||
|
|
f887756a71 | ||
|
|
5d915134a3 | ||
|
|
7516dff802 | ||
|
|
03ef931fb2 | ||
|
|
437881f0ed | ||
|
|
c736b1e353 | ||
|
|
d682e98a66 |
22 changed files with 676 additions and 503 deletions
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
# - https://gh.io/supported-runners-and-hardware-resources
|
# - https://gh.io/supported-runners-and-hardware-resources
|
||||||
# - https://gh.io/using-larger-runners
|
# - https://gh.io/using-larger-runners
|
||||||
# Consider using larger runners for possible analysis time improvements.
|
# Consider using larger runners for possible analysis time improvements.
|
||||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-20.04' }}
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-22.04' }}
|
||||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
|
|
@ -51,7 +51,7 @@ jobs:
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
|
@ -66,7 +66,7 @@ jobs:
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
#- name: Autobuild
|
#- name: Autobuild
|
||||||
# uses: github/codeql-action/autobuild@v2
|
# uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
@ -78,7 +78,7 @@ jobs:
|
||||||
./.github/workflows/codeql-buildscript.sh
|
./.github/workflows/codeql-buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
upload: false
|
upload: false
|
||||||
|
|
@ -107,7 +107,7 @@ jobs:
|
||||||
output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
|
output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
|
||||||
|
|
||||||
- name: Upload CodeQL results to code scanning
|
- name: Upload CodeQL results to code scanning
|
||||||
uses: github/codeql-action/upload-sarif@v2
|
uses: github/codeql-action/upload-sarif@v3
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ steps.step1.outputs.sarif-output }}
|
sarif_file: ${{ steps.step1.outputs.sarif-output }}
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|
|
||||||
3
.github/workflows/ubuntu.yml
vendored
3
.github/workflows/ubuntu.yml
vendored
|
|
@ -21,7 +21,8 @@ jobs:
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install -y bash-completion git meson liblua5.2-dev libglib2.0-dev
|
sudo apt update
|
||||||
|
sudo apt install -y bash-completion git meson liblua5.2-dev libglib2.0-dev
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
2
AUTHORS
2
AUTHORS
|
|
@ -64,5 +64,7 @@ Keith Hill <k_hill@unitronlp.com>
|
||||||
Lubov66 <radolevanja@gmail.com>
|
Lubov66 <radolevanja@gmail.com>
|
||||||
V <v@anomalous.eu>
|
V <v@anomalous.eu>
|
||||||
Samuel Holland <samuel@sholland.org>
|
Samuel Holland <samuel@sholland.org>
|
||||||
|
David Ordnung <david.ordnung@googlemail.com>
|
||||||
|
|
||||||
|
|
||||||
Thanks to everyone who has contributed to this project.
|
Thanks to everyone who has contributed to this project.
|
||||||
|
|
|
||||||
94
README.md
94
README.md
|
|
@ -288,12 +288,12 @@ $ cat data.bin | tio /dev/ttyUSB0
|
||||||
|
|
||||||
Manipulate modem lines on connect:
|
Manipulate modem lines on connect:
|
||||||
```
|
```
|
||||||
$ tio --script "set{DTR=high,RTS=low}; msleep(100); set{DTR=toggle,RTS=toggle}" /dev/ttyUSB0
|
$ tio --script "tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=toggle,RTS=toggle}" /dev/ttyUSB0
|
||||||
```
|
```
|
||||||
|
|
||||||
Pipe command to serial device and wait for line response within 1 second:
|
Pipe command to serial device and wait for line response within 1 second:
|
||||||
```
|
```
|
||||||
$ echo "*IDN?" | tio /dev/ttyACM0 --script "expect('\r\n', 1000)" --mute
|
$ echo "*IDN?" | tio /dev/ttyACM0 --script "tio.expect('\r\n', 1000)" --mute
|
||||||
KORAD KD3305P V4.2 SN:32475045
|
KORAD KD3305P V4.2 SN:32475045
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -365,12 +365,12 @@ color = 11
|
||||||
[svf2]
|
[svf2]
|
||||||
device = /dev/ttyUSB0
|
device = /dev/ttyUSB0
|
||||||
baudrate = 9600
|
baudrate = 9600
|
||||||
script = expect("login: "); write("root\n"); expect("Password: "); write("root\n")
|
script = tio.expect("login: "); tio.write("root\n"); tio.expect("Password: "); tio.write("root\n")
|
||||||
color = 12
|
color = 12
|
||||||
|
|
||||||
[esp32]
|
[esp32]
|
||||||
device = /dev/serial/by-id/usb-0403_6014-if00-port0
|
device = /dev/serial/by-id/usb-0403_6014-if00-port0
|
||||||
script = set{DTR=high,RTS=low}; msleep(100); set{DTR=low,RTS=high}; msleep(100); set{RTS=low}
|
script = tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=low,RTS=high}; tio.msleep(100); tio.set{RTS=low}
|
||||||
script-run = once
|
script-run = once
|
||||||
color = 13
|
color = 13
|
||||||
|
|
||||||
|
|
@ -395,72 +395,80 @@ 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 Lua API tio makes the following functions available:
|
In addition to the standard Lua API tio makes the following functions
|
||||||
|
and variables available:
|
||||||
|
|
||||||
```
|
|
||||||
expect(string, timeout)
|
#### `tio.expect(pattern, timeout)`
|
||||||
Expect string - waits for string to match or timeout before continueing.
|
|
||||||
Supports regular expressions. Special characters must be escaped with '\\'.
|
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 0 meaning it will wait forever.
|
||||||
|
|
||||||
Returns 1 on successful match, 0 on timeout, or -1 on error.
|
Returns the captures from the pattern or `nil` on timeout.
|
||||||
|
|
||||||
On successful match it also returns the match string as second return value.
|
#### `tio.read(size, timeout)`
|
||||||
|
|
||||||
read(size, timeout)
|
Read up to `size` bytes from serial device. If timeout is 0 or not provided it
|
||||||
Read from serial device. If timeout is 0 or not provided it will wait
|
will wait forever until data is ready to read.
|
||||||
|
|
||||||
|
Returns a string up to `size` bytes long on success and `nil` on 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.
|
forever until data is ready to read.
|
||||||
|
|
||||||
Returns number of bytes read on success, 0 on timeout, or -1 on error.
|
Returns a string on success and `nil` on timeout. On timeout a partially read
|
||||||
|
line may be returned as a second return value.
|
||||||
|
|
||||||
On success, returns read string as second return value.
|
#### `tio.write(string)`
|
||||||
|
|
||||||
read_line(timeout)
|
|
||||||
Read line from serial device. If timeout is 0 or not provided it will
|
|
||||||
wait forever until data is ready to read.
|
|
||||||
|
|
||||||
Returns number of bytes read on success, 0 on timeout, or -1 on error.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
write(string)
|
|
||||||
Write string to serial device.
|
Write string to serial device.
|
||||||
|
|
||||||
Returns number of bytes written on success or -1 on error.
|
Returns the `tio` table.
|
||||||
|
|
||||||
|
#### `tio.send(file, protocol)`
|
||||||
|
|
||||||
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`, `YMODEM`.
|
||||||
|
|
||||||
|
#### `tio.ttysearch()`
|
||||||
|
|
||||||
tty_search()
|
|
||||||
Search for serial devices.
|
Search for serial devices.
|
||||||
|
|
||||||
Returns a table of number indexed tables, one for each serial device
|
Returns a table of number indexed tables, one for each serial device found.
|
||||||
found. Each of these tables contains the serial device information accessible
|
Each of these tables contains the serial device information accessible via the
|
||||||
via the following string indexed elements "path", "tid", "uptime", "driver",
|
following string indexed elements "path", "tid", "uptime", "driver",
|
||||||
"description".
|
"description".
|
||||||
|
|
||||||
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`, `low`, or `toggle`.
|
||||||
|
|
||||||
|
#### `tio.sleep(seconds)`
|
||||||
|
|
||||||
sleep(seconds)
|
|
||||||
Sleep for seconds.
|
Sleep for seconds.
|
||||||
|
|
||||||
msleep(ms)
|
#### `tio.msleep(ms)`
|
||||||
Sleep for miliseconds.
|
|
||||||
|
Sleep for milliseconds.
|
||||||
|
|
||||||
|
#### `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`.
|
||||||
|
|
||||||
exit(code)
|
|
||||||
Exit with exit code.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Installation
|
## 4. Installation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ color = 13
|
||||||
[esp32]
|
[esp32]
|
||||||
device = /dev/ttyUSB0
|
device = /dev/ttyUSB0
|
||||||
color = 14
|
color = 14
|
||||||
script = set{DTR=high,RTS=low}; msleep(100); set{DTR=low,RTS=high}; msleep(100); set{RTS=low}
|
script = tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=low,RTS=high}; tio.msleep(100); tio.set{RTS=low}
|
||||||
script-run = always
|
script-run = always
|
||||||
|
|
||||||
[buspirate]
|
[buspirate]
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,13 @@ local logins = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local found, match_str = expect("\\w+- login:", 10)
|
local hostname = tio.expect("^(%g+) login:", 10)
|
||||||
if (1 == found) then
|
if hostname then
|
||||||
local hostname = string.match(match_str, "^%w+")
|
|
||||||
local login = logins[hostname]
|
local login = logins[hostname]
|
||||||
if (nil ~= login) then
|
if (nil ~= login) then
|
||||||
write(login.username .. "\n")
|
tio.write(login.username .. "\n")
|
||||||
expect("Password:")
|
tio.expect("Password:")
|
||||||
write(login.password .. "\n")
|
tio.write(login.password .. "\n")
|
||||||
else
|
else
|
||||||
io.write("\r\nDon't know login info for " .. hostname .. "\r\n")
|
io.write("\r\nDon't know login info for " .. hostname .. "\r\n")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
set{DTR=high, RTS=low}
|
tio.set{DTR=high, RTS=low}
|
||||||
msleep(100)
|
tio.msleep(100)
|
||||||
set{DTR=low, RTS=high}
|
tio.set{DTR=low, RTS=high}
|
||||||
msleep(100)
|
tio.msleep(100)
|
||||||
set{RTS=toggle}
|
tio.set{RTS=toggle}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
read(1000, 6000) -- initial config
|
tio.read(1000, 6000) -- initial config
|
||||||
write("\n")
|
tio.write("\n")
|
||||||
msleep(100)
|
tio.msleep(100)
|
||||||
read(650, 60) -- main menu
|
tio.read(650, 60) -- main menu
|
||||||
write("S") -- S menu
|
tio.write("S") -- S menu
|
||||||
msleep(30)
|
tio.msleep(30)
|
||||||
read(650, 60)
|
tio.read(650, 60)
|
||||||
write("t") -- Parallel Value Table
|
tio.write("t") -- Parallel Value Table
|
||||||
read(650, 60)
|
tio.read(650, 60)
|
||||||
while true do
|
while true do
|
||||||
msleep(1000)
|
tio.msleep(1000)
|
||||||
write("t")
|
tio.write("t")
|
||||||
read(650, 50) -- repeat PVT forever
|
tio.read(650, 50) -- repeat PVT forever
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
read(1000, 8000) -- read initial config
|
tio.read(1000, 8000) -- read initial config
|
||||||
write("\n")
|
tio.write("\n")
|
||||||
read(650, 100) -- main menu
|
tio.read(650, 100) -- main menu
|
||||||
write("S") -- S menu
|
tio.write("S") -- S menu
|
||||||
n = 1
|
repeat
|
||||||
while n > 0 do -- while not empty, read more
|
str = tio.readline(25)
|
||||||
n, str = read_line(25)
|
until str == nil
|
||||||
end
|
|
||||||
while true do
|
while true do
|
||||||
write("t") -- query PV table
|
tio.write("t") -- query PV table
|
||||||
msleep(880)
|
tio.msleep(880)
|
||||||
n = 1
|
repeat
|
||||||
while n > 0 do -- while not empty, read more
|
str = tio.readline(60)
|
||||||
n, str = read_line(60)
|
tio.msleep(60)
|
||||||
msleep(60)
|
until str == nil
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
io.write("Searching... ")
|
io.write("Searching... ")
|
||||||
|
|
||||||
local device = tty_search()
|
local device = tio.ttysearch()
|
||||||
|
|
||||||
io.write("done\r\n")
|
io.write("done\r\n")
|
||||||
|
|
||||||
|
|
|
||||||
70
man/tio.1.in
70
man/tio.1.in
|
|
@ -150,6 +150,8 @@ Set timestamp format to any of the following timestamp formats:
|
||||||
ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
|
ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
|
||||||
.IP "\fBepoch"
|
.IP "\fBepoch"
|
||||||
Seconds since Unix epoch (1970-01-01)
|
Seconds since Unix epoch (1970-01-01)
|
||||||
|
.IP "\fBepoch-usec"
|
||||||
|
Seconds since Unix epoch (1970-01-01) with subdivision in microseconds
|
||||||
.PP
|
.PP
|
||||||
Default format is \fB24hour\fR
|
Default format is \fB24hour\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
@ -218,6 +220,8 @@ Map FF to ESC-c on input
|
||||||
Map NL to CR on input
|
Map NL to CR on input
|
||||||
.IP "\fBINLCRNL"
|
.IP "\fBINLCRNL"
|
||||||
Map NL to CR-NL on input
|
Map NL to CR-NL on input
|
||||||
|
.IP "\fBICRCRNL"
|
||||||
|
Map CR to CR-NL on input
|
||||||
.IP "\fBIMSB2LSB"
|
.IP "\fBIMSB2LSB"
|
||||||
Map MSB bit order to LSB on input
|
Map MSB bit order to LSB on input
|
||||||
.IP "\fBOCRNL"
|
.IP "\fBOCRNL"
|
||||||
|
|
@ -367,6 +371,10 @@ Default value is "always".
|
||||||
|
|
||||||
Execute shell command with I/O redirected to device
|
Execute shell command with I/O redirected to device
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR "\-\-complete-profiles
|
||||||
|
|
||||||
|
Prints profiles (for shell completion)
|
||||||
.TP
|
.TP
|
||||||
.BR \-v ", " \-\-version
|
.BR \-v ", " \-\-version
|
||||||
|
|
||||||
|
|
@ -429,49 +437,40 @@ Send ctrl-t character
|
||||||
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 following functions
|
||||||
available:
|
and variables available:
|
||||||
|
|
||||||
.TP 6n
|
.TP 6n
|
||||||
|
|
||||||
.IP "\fBexpect(string, timeout)"
|
.IP "\fBtio.expect(pattern, timeout)"
|
||||||
Expect string - waits for string to match or timeout before continuing.
|
Waits for the Lua pattern to match or timeout before continuing.
|
||||||
Supports regular expressions. Special characters must be escaped with '\e\e'.
|
|
||||||
Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
|
Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
|
||||||
|
|
||||||
Returns 1 on successful match, 0 on timeout, or -1 on error.
|
Returns the captures from the pattern or nil on timeout.
|
||||||
|
|
||||||
On successful match it also returns the match string as second return value.
|
.IP "\fBtio.read(size, timeout)"
|
||||||
|
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.
|
||||||
|
|
||||||
.IP "\fBread(size, timeout)"
|
Returns a string up to size bytes long on success and nil on timeout.
|
||||||
Read from serial device. If timeout is 0 or not provided it will wait forever
|
|
||||||
until data is ready to read.
|
|
||||||
|
|
||||||
Returns number of bytes read on success, 0 on timeout, or -1 on error.
|
.IP "\fBtio.readline(timeout)"
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
.IP "\fBread_line(timeout)"
|
|
||||||
Read line from serial device. If timeout is 0 or not provided it will wait
|
Read line from serial device. If timeout is 0 or not provided it will wait
|
||||||
forever until data is ready to read.
|
forever until data is ready to read.
|
||||||
|
|
||||||
Returns number of bytes read on success, 0 on timeout, or -1 on error.
|
Returns a string on success and nil on timeout. On timeout a partially read
|
||||||
|
line may be returned as a second return value.
|
||||||
|
|
||||||
On success, returns the string that was read as second return value. Also
|
.IP "\fBtio.write(string)"
|
||||||
emits a single timestamp to stdout and log file per options.timestamp
|
|
||||||
and options.log.
|
|
||||||
|
|
||||||
.IP "\fBwrite(string)"
|
|
||||||
Write string to serial device.
|
Write string to serial device.
|
||||||
|
|
||||||
Returns number of bytes written on success or -1 on error.
|
Returns the tio table.
|
||||||
|
|
||||||
.IP "\fBsend(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, YMODEM.
|
||||||
|
|
||||||
.IP "\fBtty_search()"
|
.IP "\fBtio.ttysearch()"
|
||||||
Search for serial devices.
|
Search for serial devices.
|
||||||
|
|
||||||
Returns a table of number indexed tables, one for each serial device found.
|
Returns a table of number indexed tables, one for each serial device found.
|
||||||
|
|
@ -481,19 +480,26 @@ following string indexed elements "path", "tid", "uptime", "driver",
|
||||||
|
|
||||||
Returns nil if no serial devices are found.
|
Returns nil if no serial devices are found.
|
||||||
|
|
||||||
.IP "\fBset{line=state, ...}"
|
.IP "\fBtio.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, low, or toggle.
|
||||||
|
|
||||||
.IP "\fBsleep(seconds)"
|
.IP "\fBtio.sleep(seconds)"
|
||||||
Sleep for seconds.
|
Sleep for seconds.
|
||||||
.IP "\fBmsleep(ms)"
|
.IP "\fBtio.msleep(ms)"
|
||||||
Sleep for milliseconds.
|
Sleep for milliseconds.
|
||||||
.IP "\fBexit(code)"
|
|
||||||
Exit with exit code.
|
.IP "\fBtio.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.
|
||||||
|
|
||||||
.SH "CONFIGURATION FILE"
|
.SH "CONFIGURATION FILE"
|
||||||
.PP
|
.PP
|
||||||
|
|
@ -722,7 +728,7 @@ expect -i $uart "prompt> "
|
||||||
.TP
|
.TP
|
||||||
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
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
Redirect device I/O to network file socket for remote TTY sharing:
|
Redirect device I/O to network file socket for remote TTY sharing:
|
||||||
|
|
@ -743,7 +749,7 @@ $ echo "ls -la" | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-p
|
||||||
.TP
|
.TP
|
||||||
Pipe command to serial device and wait for line response within 1 second:
|
Pipe command to serial device and wait for line response within 1 second:
|
||||||
|
|
||||||
$ echo "*IDN?" | tio /dev/ttyACM0 --script "expect('\\r\\n', 1000)" --mute
|
$ echo "*IDN?" | tio /dev/ttyACM0 --script "tio.expect('\\r\\n', 1000)" --mute
|
||||||
.TP
|
.TP
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
|
|
@ -764,7 +770,7 @@ $ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
|
||||||
.TP
|
.TP
|
||||||
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
|
||||||
|
|
||||||
.SH "WEBSITE"
|
.SH "WEBSITE"
|
||||||
.PP
|
.PP
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,8 @@ 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
|
||||||
|
|
||||||
Default format is 24hour
|
Default format is 24hour
|
||||||
|
|
||||||
--timestamp-timeout <ms>
|
--timestamp-timeout <ms>
|
||||||
|
|
@ -168,6 +170,8 @@ OPTIONS
|
||||||
|
|
||||||
INLCRNL Map NL to CR-NL on input
|
INLCRNL Map NL to CR-NL on input
|
||||||
|
|
||||||
|
ICRCRNL Map CR to CR-NL on input
|
||||||
|
|
||||||
IMSB2LSB Map MSB bit order to LSB on input
|
IMSB2LSB Map MSB bit order to LSB on input
|
||||||
|
|
||||||
OCRNL Map CR to NL on output
|
OCRNL Map CR to NL on output
|
||||||
|
|
@ -285,6 +289,10 @@ OPTIONS
|
||||||
|
|
||||||
Execute shell command with I/O redirected to device
|
Execute shell command with I/O redirected to device
|
||||||
|
|
||||||
|
--complete-profiles
|
||||||
|
|
||||||
|
Prints profiles (for shell completion)
|
||||||
|
|
||||||
-v, --version
|
-v, --version
|
||||||
|
|
||||||
Display program version.
|
Display program version.
|
||||||
|
|
@ -353,7 +361,7 @@ SCRIPT API
|
||||||
On successful match it also returns the match string as second return value.
|
On successful match it also returns the match string as second return value.
|
||||||
|
|
||||||
read(size, timeout)
|
read(size, timeout)
|
||||||
Read from serial device. If timeout is 0 or not provided it will wait forever until data is ready to read.
|
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.
|
||||||
|
|
||||||
Returns number of bytes read on success, 0 on timeout, or -1 on error.
|
Returns number of bytes read on success, 0 on timeout, or -1 on error.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
project('tio', 'c',
|
project('tio', 'c',
|
||||||
version : '3.9',
|
version : '3.9',
|
||||||
license : [ 'GPL-2'],
|
license : 'GPL-2.0-or-later',
|
||||||
meson_version : '>= 0.53.2',
|
meson_version : '>= 0.53.2',
|
||||||
default_options : [ 'warning_level=2', 'buildtype=release', 'c_std=gnu99' ]
|
default_options : [ 'warning_level=2', 'buildtype=release', 'c_std=gnu99' ]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ _tio()
|
||||||
--script-file \
|
--script-file \
|
||||||
--script-run \
|
--script-run \
|
||||||
--exec \
|
--exec \
|
||||||
|
--complete-profiles \
|
||||||
-v --version \
|
-v --version \
|
||||||
-h --help"
|
-h --help"
|
||||||
|
|
||||||
|
|
@ -85,11 +86,11 @@ _tio()
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
-m | --map)
|
-m | --map)
|
||||||
COMPREPLY=( $(compgen -W "ICRNL IGNCR INLCR IFFESCC INLCRNL IMSB2LSB OCRNL ODELBS ONLCRNL OLTU ONULBRK OIGNCR" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "ICRNL IGNCR INLCR IFFESCC INLCRNL ICRCRNL IMSB2LSB OCRNL ODELBS ONLCRNL OLTU ONULBRK OIGNCR" -- ${cur}) )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--timestamp-format)
|
--timestamp-format)
|
||||||
COMPREPLY=( $(compgen -W "24hour 24hour-start 24hour-delta iso8601" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "24hour 24hour-start 24hour-delta iso8601 epoch epoch-usec" -- ${cur}) )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
-c | --color)
|
-c | --color)
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
|
||||||
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)
|
||||||
{
|
{
|
||||||
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", "epoch", NULL);
|
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", "epoch", "epoch-usec", NULL);
|
||||||
if (string != NULL)
|
if (string != NULL)
|
||||||
{
|
{
|
||||||
option_parse_timestamp(string, &option.timestamp);
|
option_parse_timestamp(string, &option.timestamp);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,13 @@ tio_dep = [
|
||||||
lua_dep
|
lua_dep
|
||||||
]
|
]
|
||||||
|
|
||||||
tio_c_args = ['-Wno-unused-result']
|
if host_machine.system() == 'darwin'
|
||||||
|
iokit_dep = dependency('appleframeworks', modules: ['IOKit'], required: true)
|
||||||
|
corefoundation_dep = dependency('appleframeworks', modules: ['CoreFoundation'], required: true)
|
||||||
|
tio_dep += [iokit_dep, corefoundation_dep]
|
||||||
|
endif
|
||||||
|
|
||||||
|
tio_c_args = ['-Wshadow','-Wno-unused-result']
|
||||||
|
|
||||||
if enable_setspeed2
|
if enable_setspeed2
|
||||||
tio_c_args += '-DHAVE_TERMIOS2'
|
tio_c_args += '-DHAVE_TERMIOS2'
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ struct option_t option =
|
||||||
.map_ign_cr = false,
|
.map_ign_cr = false,
|
||||||
.map_i_ff_escc = false,
|
.map_i_ff_escc = false,
|
||||||
.map_i_nl_crnl = false,
|
.map_i_nl_crnl = false,
|
||||||
|
.map_i_cr_crnl = false,
|
||||||
.map_o_cr_nl = false,
|
.map_o_cr_nl = false,
|
||||||
.map_o_nl_crnl = false,
|
.map_o_nl_crnl = false,
|
||||||
.map_o_del_bs = false,
|
.map_o_del_bs = false,
|
||||||
|
|
@ -170,6 +171,7 @@ void option_print_help(char *argv[])
|
||||||
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");
|
||||||
printf(" --exec <command> Execute shell command with I/O redirected to device\n");
|
printf(" --exec <command> Execute shell command with I/O redirected to device\n");
|
||||||
|
printf(" --complete-profiles Prints profiles (for shell completion)\n");
|
||||||
printf(" -v, --version Display version\n");
|
printf(" -v, --version Display version\n");
|
||||||
printf(" -h, --help Display help\n");
|
printf(" -h, --help Display help\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
@ -399,6 +401,10 @@ const char* option_timestamp_format_to_string(timestamp_t timestamp)
|
||||||
return "epoch";
|
return "epoch";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TIMESTAMP_EPOCH_USEC:
|
||||||
|
return "epoch-usec";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "unknown";
|
return "unknown";
|
||||||
break;
|
break;
|
||||||
|
|
@ -429,6 +435,10 @@ void option_parse_timestamp(const char *arg, timestamp_t *timestamp)
|
||||||
{
|
{
|
||||||
*timestamp = TIMESTAMP_EPOCH;
|
*timestamp = TIMESTAMP_EPOCH;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(arg, "epoch-usec") == 0)
|
||||||
|
{
|
||||||
|
*timestamp = TIMESTAMP_EPOCH_USEC;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tio_error_print("Invalid timestamp '%s'", arg);
|
tio_error_print("Invalid timestamp '%s'", arg);
|
||||||
|
|
@ -770,6 +780,10 @@ void option_parse_mappings(const char *map)
|
||||||
{
|
{
|
||||||
option.map_i_nl_crnl = true;
|
option.map_i_nl_crnl = true;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(token,"ICRCRNL") == 0)
|
||||||
|
{
|
||||||
|
option.map_i_cr_crnl = true;
|
||||||
|
}
|
||||||
else if (strcmp(token, "ONLCRNL") == 0)
|
else if (strcmp(token, "ONLCRNL") == 0)
|
||||||
{
|
{
|
||||||
option.map_o_nl_crnl = true;
|
option.map_o_nl_crnl = true;
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ struct option_t
|
||||||
bool map_ign_cr;
|
bool map_ign_cr;
|
||||||
bool map_i_ff_escc;
|
bool map_i_ff_escc;
|
||||||
bool map_i_nl_crnl;
|
bool map_i_nl_crnl;
|
||||||
|
bool map_i_cr_crnl;
|
||||||
bool map_o_cr_nl;
|
bool map_o_cr_nl;
|
||||||
bool map_o_nl_crnl;
|
bool map_o_nl_crnl;
|
||||||
bool map_o_del_bs;
|
bool map_o_del_bs;
|
||||||
|
|
|
||||||
544
src/script.c
544
src/script.c
|
|
@ -20,7 +20,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <regex.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
@ -45,23 +44,87 @@
|
||||||
#define READ_LINE_SIZE 4096 // read_line buffer length
|
#define READ_LINE_SIZE 4096 // read_line buffer length
|
||||||
|
|
||||||
static int device_fd;
|
static int device_fd;
|
||||||
static char circular_buffer[MAX_BUFFER_SIZE];
|
|
||||||
static char match_string[MAX_BUFFER_SIZE];
|
|
||||||
static int buffer_size = 0;
|
|
||||||
|
|
||||||
static char script_init[] =
|
static char script_init[] =
|
||||||
"function set(arg)\n"
|
"tio.set = function(arg)\n"
|
||||||
" local dtr = arg.DTR or -1\n"
|
" local dtr = arg.DTR or -1\n"
|
||||||
" local rts = arg.RTS or -1\n"
|
" local rts = arg.RTS or -1\n"
|
||||||
" local cts = arg.CTS or -1\n"
|
" local cts = arg.CTS or -1\n"
|
||||||
" local dsr = arg.DSR or -1\n"
|
" local dsr = arg.DSR or -1\n"
|
||||||
" local cd = arg.CD or -1\n"
|
" local cd = arg.CD or -1\n"
|
||||||
" local ri = arg.RI or -1\n"
|
" local ri = arg.RI or -1\n"
|
||||||
" line_set(dtr, rts, cts, dsr, cd, ri)\n"
|
" tio.line_set(dtr, rts, cts, dsr, cd, ri)\n"
|
||||||
"end\n";
|
"end\n"
|
||||||
|
"tio.expect = function(pattern, timeout)\n"
|
||||||
|
" local str = ''\n"
|
||||||
|
" while true do\n"
|
||||||
|
" local c = tio.read(1, timeout)\n"
|
||||||
|
" if c then\n"
|
||||||
|
" str = str .. c\n"
|
||||||
|
" if string.match(str, pattern) then\n"
|
||||||
|
" return string.match(str, pattern)\n"
|
||||||
|
" end\n"
|
||||||
|
" else\n"
|
||||||
|
" return nil, str\n"
|
||||||
|
" end\n"
|
||||||
|
" end\n"
|
||||||
|
"end\n"
|
||||||
|
"tio.alwaysecho = true\n"
|
||||||
|
"setmetatable(tio, tio)\n";
|
||||||
|
|
||||||
// lua: sleep(seconds)
|
static bool alwaysecho(lua_State *L)
|
||||||
static int sleep_(lua_State *L)
|
{
|
||||||
|
bool b;
|
||||||
|
|
||||||
|
lua_getglobal(L, "tio");
|
||||||
|
lua_getfield(L, -1, "alwaysecho");
|
||||||
|
b = lua_toboolean(L, -1);
|
||||||
|
lua_pop(L, 2);
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int api_echo(lua_State *L)
|
||||||
|
{
|
||||||
|
size_t len = 0;
|
||||||
|
const char *str = luaL_checklstring(L, 1, &len);
|
||||||
|
|
||||||
|
if (option.timestamp)
|
||||||
|
{
|
||||||
|
char *pTimeStampNow = timestamp_current_time();
|
||||||
|
if (pTimeStampNow)
|
||||||
|
{
|
||||||
|
tio_printf("%s", str);
|
||||||
|
if (option.log)
|
||||||
|
{
|
||||||
|
log_printf("\n[%s] %s", pTimeStampNow, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (size_t i=0; i<len; i++)
|
||||||
|
{
|
||||||
|
putchar(str[i]);
|
||||||
|
|
||||||
|
if (option.log)
|
||||||
|
log_putc(str[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void maybe_echo(lua_State *L)
|
||||||
|
{
|
||||||
|
if (alwaysecho(L))
|
||||||
|
{
|
||||||
|
lua_pushcfunction(L, api_echo);
|
||||||
|
lua_pushvalue(L, -2);
|
||||||
|
lua_call(L, 1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lua: tio.sleep(seconds)
|
||||||
|
static int api_sleep(lua_State *L)
|
||||||
{
|
{
|
||||||
long seconds = lua_tointeger(L, 1);
|
long seconds = lua_tointeger(L, 1);
|
||||||
|
|
||||||
|
|
@ -77,8 +140,8 @@ static int sleep_(lua_State *L)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lua: msleep(miliseconds)
|
// lua: tio.msleep(miliseconds)
|
||||||
static int msleep(lua_State *L)
|
static int api_msleep(lua_State *L)
|
||||||
{
|
{
|
||||||
long mseconds = lua_tointeger(L, 1);
|
long mseconds = lua_tointeger(L, 1);
|
||||||
long useconds = mseconds * 1000;
|
long useconds = mseconds * 1000;
|
||||||
|
|
@ -94,7 +157,7 @@ static int msleep(lua_State *L)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lua: line_set(dtr,rts,cts,dsr,cd,ri)
|
// lua: tio.line_set(dtr,rts,cts,dsr,cd,ri)
|
||||||
static int line_set(lua_State *L)
|
static int line_set(lua_State *L)
|
||||||
{
|
{
|
||||||
tty_line_config_t line_config[6] = { };
|
tty_line_config_t line_config[6] = { };
|
||||||
|
|
@ -148,11 +211,11 @@ static int line_set(lua_State *L)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lua: modem_send(file, protocol)
|
// lua: tio.send(file, protocol)
|
||||||
static int modem_send(lua_State *L)
|
static int api_send(lua_State *L)
|
||||||
{
|
{
|
||||||
const char *file = lua_tostring(L, 1);
|
const char *file = luaL_checkstring(L, 1);
|
||||||
int protocol = lua_tointeger(L, 2);
|
int protocol = luaL_checkinteger(L, 2);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (file == NULL)
|
if (file == NULL)
|
||||||
|
|
@ -184,307 +247,118 @@ static int modem_send(lua_State *L)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lua: send(string)
|
// lua: tio.write(string)
|
||||||
static int write_(lua_State *L)
|
static int api_write(lua_State *L)
|
||||||
{
|
{
|
||||||
const char *string = lua_tostring(L, 1);
|
size_t len = 0;
|
||||||
int ret;
|
const char *string = luaL_checklstring(L, 1, &len);
|
||||||
|
ssize_t ret;
|
||||||
|
int attempts = 100;
|
||||||
|
|
||||||
if (string == NULL)
|
do {
|
||||||
{
|
ret = write(device_fd, string, len);
|
||||||
return 0;
|
if (ret < 0)
|
||||||
}
|
return luaL_error(L, "%s", strerror(errno));
|
||||||
|
|
||||||
|
len -= ret;
|
||||||
|
string += ret;
|
||||||
|
} while (len > 0 && --attempts);
|
||||||
|
|
||||||
|
if (len > 0)
|
||||||
|
return luaL_error(L, "partial write");
|
||||||
|
|
||||||
ret = write(device_fd, string, strlen(string));
|
|
||||||
fsync(device_fd); // flush these characters now
|
fsync(device_fd); // flush these characters now
|
||||||
tcdrain(device_fd); //ensure we flushed characters to our device
|
tcdrain(device_fd); //ensure we flushed characters to our device
|
||||||
|
|
||||||
lua_pushnumber(L, ret);
|
lua_getglobal(L, "tio");
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to add a character to the circular expect buffer
|
// lua: tio.read(size, timeout)
|
||||||
static void expect_buffer_add(char c)
|
static int api_read(lua_State *L)
|
||||||
{
|
{
|
||||||
if (!c)
|
int size = luaL_checkinteger(L, 1);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer_size < MAX_BUFFER_SIZE)
|
|
||||||
{
|
|
||||||
circular_buffer[buffer_size++] = c;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Shift the buffer to accommodate the new character
|
|
||||||
memmove(circular_buffer, circular_buffer + 1, MAX_BUFFER_SIZE - 1);
|
|
||||||
circular_buffer[MAX_BUFFER_SIZE - 1] = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to match against the circular expect buffer using regex
|
|
||||||
static bool match_regex(regex_t *regex)
|
|
||||||
{
|
|
||||||
char buffer[MAX_BUFFER_SIZE + 1]; // Temporary buffer for regex matching
|
|
||||||
const char *s = circular_buffer;
|
|
||||||
regmatch_t pmatch[1];
|
|
||||||
regoff_t len;
|
|
||||||
|
|
||||||
memcpy(buffer, circular_buffer, buffer_size);
|
|
||||||
buffer[buffer_size] = '\0'; // Null-terminate the buffer
|
|
||||||
|
|
||||||
// Match against the regex
|
|
||||||
int ret = regexec(regex, buffer, 1, pmatch, 0);
|
|
||||||
if (!ret)
|
|
||||||
{
|
|
||||||
// Match found
|
|
||||||
len = pmatch[0].rm_eo - pmatch[0].rm_so;
|
|
||||||
memcpy(match_string, s + pmatch[0].rm_so, len);
|
|
||||||
match_string[len] = '\0';
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (ret == REG_NOMATCH)
|
|
||||||
{
|
|
||||||
// No match found, do nothing
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Error occurred during matching
|
|
||||||
tio_error_print("Regex match failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to echo a buffer to stdout and to the log
|
|
||||||
// per the option.timestamp and option.log settings
|
|
||||||
static void echo_buffer(char buffer[], ssize_t len)
|
|
||||||
{
|
|
||||||
if (option.timestamp)
|
|
||||||
{
|
|
||||||
char *pTimeStampNow;
|
|
||||||
pTimeStampNow = timestamp_current_time();
|
|
||||||
if (pTimeStampNow)
|
|
||||||
{
|
|
||||||
tio_printf("%s", buffer); //does timestamps for us
|
|
||||||
if (option.log)
|
|
||||||
{
|
|
||||||
log_printf("\n[%s] %s", pTimeStampNow, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (ssize_t i=0; i<len; i++)
|
|
||||||
{
|
|
||||||
putchar(buffer[i]);
|
|
||||||
|
|
||||||
if (option.log)
|
|
||||||
{
|
|
||||||
log_putc(buffer[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lua: ret,string = read(size, timeout)
|
|
||||||
static int read_string(lua_State *L)
|
|
||||||
{
|
|
||||||
int size = lua_tointeger(L, 1) + 1; //plus one for null-terminated string
|
|
||||||
int timeout = lua_tointeger(L, 2);
|
int timeout = lua_tointeger(L, 2);
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
char *buffer = calloc(1, size);
|
|
||||||
if (buffer == NULL)
|
|
||||||
{
|
|
||||||
ret = -1; // Error
|
|
||||||
goto error_rs;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout == 0)
|
if (timeout == 0)
|
||||||
{
|
{
|
||||||
timeout = -1; // Wait forever
|
timeout = -1; // Wait forever
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t bytes_read = read_poll(device_fd, buffer, size, timeout);
|
luaL_Buffer buffer;
|
||||||
if (bytes_read < 0)
|
luaL_buffinit(L, &buffer);
|
||||||
|
|
||||||
|
#if LUA_VERSION_NUM >= 502
|
||||||
|
char *p = luaL_prepbuffsize(&buffer, size);
|
||||||
|
#else
|
||||||
|
if (size > LUAL_BUFFERSIZE)
|
||||||
|
return luaL_error(L, "buffer overflow, max size is: %d", LUAL_BUFFERSIZE);
|
||||||
|
char *p = luaL_prepbuffer(&buffer);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ssize_t ret = read_poll(device_fd, p, size, timeout);
|
||||||
|
if (ret < 0)
|
||||||
|
return luaL_error(L, "%s", strerror(errno));
|
||||||
|
|
||||||
|
luaL_addsize(&buffer, ret);
|
||||||
|
luaL_pushresult(&buffer);
|
||||||
|
|
||||||
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
ret = -1; // Error
|
// On timeout return nil instead of an empty string
|
||||||
goto error_rs;
|
lua_pop(L, 1);
|
||||||
}
|
lua_pushnil(L);
|
||||||
else if (bytes_read == 0)
|
|
||||||
{
|
|
||||||
ret = 0; // Timeout
|
|
||||||
goto error_rs;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
buffer[bytes_read] = (char)0;
|
maybe_echo(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo_buffer(&buffer[0], bytes_read);
|
return 1;
|
||||||
ret = bytes_read;
|
}
|
||||||
|
|
||||||
error_rs:
|
// lua: string = tio.readline(timeout)
|
||||||
lua_pushnumber(L, ret);
|
static int api_readline(lua_State *L) {
|
||||||
if (buffer != NULL)
|
int timeout = lua_tointeger(L, 1); //ms
|
||||||
|
luaL_Buffer b;
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
if (timeout == 0)
|
||||||
{
|
{
|
||||||
lua_pushstring(L, ret > 0 ? buffer : "");
|
timeout = -1; // Wait forever
|
||||||
free(buffer);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
luaL_buffinit(L, &b);
|
||||||
|
luaL_prepbuffer(&b);
|
||||||
|
while (true) {
|
||||||
|
int ret = read_poll(device_fd, &ch, 1, timeout);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return luaL_error(L, "%s", strerror(errno));
|
||||||
|
|
||||||
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
lua_pushstring(L, ""); // give empty string to caller
|
luaL_pushresult(&b);
|
||||||
}
|
maybe_echo(L);
|
||||||
|
lua_pushnil(L);
|
||||||
|
lua_insert(L, -2);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lua: ret,string = read_line(timeout)
|
|
||||||
static int read_line(lua_State *L)
|
|
||||||
{
|
|
||||||
static char linebuf[READ_LINE_SIZE];
|
|
||||||
int timeout = lua_tointeger(L, 1); //ms
|
|
||||||
int ret = 0;
|
|
||||||
int read_result = 1; //enable reading input from device
|
|
||||||
char ch;
|
|
||||||
int bytes_read = 0;
|
|
||||||
|
|
||||||
linebuf[0] = '\0';
|
|
||||||
if (timeout == 0)
|
|
||||||
{
|
|
||||||
timeout = -1; // Wait forever
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop reading input until a newline seen or timeout
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
read_result = read_poll(device_fd, &ch, 1, timeout);
|
|
||||||
if (read_result < 0)
|
|
||||||
{
|
|
||||||
ret = -1; // Error
|
|
||||||
linebuf[bytes_read] = '\0';
|
|
||||||
goto error_rl;
|
|
||||||
}
|
|
||||||
else if (!read_result)
|
|
||||||
{
|
|
||||||
// Timeout returns a non-empty linebuf as a 'line'
|
|
||||||
ret = bytes_read;
|
|
||||||
linebuf[bytes_read] = '\0';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else // we got a character, so handle it
|
|
||||||
{
|
|
||||||
if (ch == '\n')
|
if (ch == '\n')
|
||||||
{
|
{
|
||||||
linebuf[bytes_read] = '\0';
|
luaL_pushresult(&b);
|
||||||
break;
|
maybe_echo(L);
|
||||||
}
|
return 1;
|
||||||
else if (bytes_read <= (READ_LINE_SIZE-2))
|
|
||||||
{
|
|
||||||
if (isprint(ch)) // store all printable chars
|
|
||||||
{
|
|
||||||
linebuf[bytes_read++] = ch;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
luaL_addchar(&b, ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes_read)
|
// lua: table = tio.ttysearch()
|
||||||
{
|
static int api_ttysearch(lua_State *L)
|
||||||
echo_buffer(linebuf, bytes_read);
|
|
||||||
}
|
|
||||||
ret = bytes_read;
|
|
||||||
|
|
||||||
error_rl:
|
|
||||||
lua_pushnumber(L, ret);
|
|
||||||
lua_pushstring(L, linebuf);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// lua: expect(string, timeout)
|
|
||||||
static int expect(lua_State *L)
|
|
||||||
{
|
|
||||||
const char *string = lua_tostring(L, 1);
|
|
||||||
long timeout = lua_tointeger(L, 2);
|
|
||||||
regex_t regex;
|
|
||||||
int ret = 0;
|
|
||||||
char c;
|
|
||||||
|
|
||||||
// Resets buffer to ignore previous `expect` calls
|
|
||||||
buffer_size = 0;
|
|
||||||
match_string[0] = '\0';
|
|
||||||
|
|
||||||
if ((string == NULL) || (timeout < 0))
|
|
||||||
{
|
|
||||||
ret = -1;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout == 0)
|
|
||||||
{
|
|
||||||
// Let poll() wait forever
|
|
||||||
timeout = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile the regular expression
|
|
||||||
ret = regcomp(®ex, string, REG_EXTENDED);
|
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
tio_error_print("Could not compile regex");
|
|
||||||
ret = -1;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main loop to read and match
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
ssize_t bytes_read = read_poll(device_fd, &c, 1, timeout);
|
|
||||||
if (bytes_read > 0)
|
|
||||||
{
|
|
||||||
putchar(c);
|
|
||||||
expect_buffer_add(c);
|
|
||||||
|
|
||||||
if (option.log)
|
|
||||||
{
|
|
||||||
log_putc(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match against the entire buffer
|
|
||||||
if (match_regex(®ex))
|
|
||||||
{
|
|
||||||
ret = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Timeout or error
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
regfree(®ex);
|
|
||||||
|
|
||||||
error:
|
|
||||||
lua_pushnumber(L, ret);
|
|
||||||
lua_pushstring(L, match_string);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// lua: exit(code)
|
|
||||||
static int exit_(lua_State *L)
|
|
||||||
{
|
|
||||||
long code = lua_tointeger(L, 1);
|
|
||||||
|
|
||||||
exit(code);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// lua: list = tty_search()
|
|
||||||
static int tty_search_(lua_State *L)
|
|
||||||
{
|
{
|
||||||
UNUSED(L);
|
UNUSED(L);
|
||||||
GList *iter;
|
GList *iter;
|
||||||
|
|
@ -550,66 +424,7 @@ static void script_buffer_run(lua_State *L, const char *script_buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct luaL_Reg tio_lib[] =
|
static void script_file_run(lua_State *L, const char *filename)
|
||||||
{
|
|
||||||
{ "sleep", sleep_},
|
|
||||||
{ "msleep", msleep},
|
|
||||||
{ "line_set", line_set},
|
|
||||||
{ "send", modem_send},
|
|
||||||
{ "write", write_},
|
|
||||||
{ "read", read_string},
|
|
||||||
{ "read_line", read_line},
|
|
||||||
{ "expect", expect},
|
|
||||||
{ "exit", exit_},
|
|
||||||
{ "tty_search", tty_search_},
|
|
||||||
{NULL, NULL}
|
|
||||||
};
|
|
||||||
|
|
||||||
#if !defined LUA_VERSION_NUM || LUA_VERSION_NUM==501
|
|
||||||
/*
|
|
||||||
** Adapted from Lua 5.2.0 (for backwards compatibility)
|
|
||||||
*/
|
|
||||||
static void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup)
|
|
||||||
{
|
|
||||||
luaL_checkstack(L, nup+1, "too many upvalues");
|
|
||||||
for (; l->name != NULL; l++) { /* fill the table with given functions */
|
|
||||||
int i;
|
|
||||||
lua_pushstring(L, l->name);
|
|
||||||
for (i = 0; i < nup; i++) /* copy upvalues to the top */
|
|
||||||
lua_pushvalue(L, -(nup+1));
|
|
||||||
lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
|
|
||||||
lua_settable(L, -(nup + 3));
|
|
||||||
}
|
|
||||||
lua_pop(L, nup); /* remove upvalues */
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void script_load(lua_State *L)
|
|
||||||
{
|
|
||||||
int error;
|
|
||||||
|
|
||||||
error = luaL_loadbuffer(L, script_init, strlen(script_init), "tio") || lua_pcall(L, 0, 0, 0);
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
tio_error_print("%s\n", lua_tostring(L, -1));
|
|
||||||
lua_pop(L, 1); // Pop error message from the stack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_register_tio(lua_State *L)
|
|
||||||
{
|
|
||||||
// Register lxi functions
|
|
||||||
lua_getglobal(L, "_G");
|
|
||||||
luaL_setfuncs(L, tio_lib, 0);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
// Load lua init script
|
|
||||||
script_load(L);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void script_file_run(lua_State *L, const char *filename)
|
|
||||||
{
|
{
|
||||||
if (strlen(filename) == 0)
|
if (strlen(filename) == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -625,13 +440,39 @@ void script_file_run(lua_State *L, const char *filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void script_set_global(lua_State *L, const char *name, long value)
|
static const struct luaL_Reg tio_lib[] =
|
||||||
|
{
|
||||||
|
{ "echo", api_echo},
|
||||||
|
{ "sleep", api_sleep},
|
||||||
|
{ "msleep", api_msleep},
|
||||||
|
{ "line_set", line_set},
|
||||||
|
{ "send", api_send},
|
||||||
|
{ "write", api_write},
|
||||||
|
{ "read", api_read},
|
||||||
|
{ "readline", api_readline},
|
||||||
|
{ "ttysearch", api_ttysearch},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void script_load(lua_State *L)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
|
||||||
|
error = luaL_loadbuffer(L, script_init, strlen(script_init), "tio") || lua_pcall(L, 0, 0, 0);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
tio_error_print("%s\n", lua_tostring(L, -1));
|
||||||
|
lua_pop(L, 1); // Pop error message from the stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void script_set_global(lua_State *L, const char *name, long value)
|
||||||
{
|
{
|
||||||
lua_pushnumber(L, value);
|
lua_pushnumber(L, value);
|
||||||
lua_setglobal(L, name);
|
lua_setglobal(L, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void script_set_globals(lua_State *L)
|
static void script_set_globals(lua_State *L)
|
||||||
{
|
{
|
||||||
script_set_global(L, "toggle", 2);
|
script_set_global(L, "toggle", 2);
|
||||||
script_set_global(L, "high", 1);
|
script_set_global(L, "high", 1);
|
||||||
|
|
@ -641,6 +482,14 @@ void script_set_globals(lua_State *L)
|
||||||
script_set_global(L, "YMODEM", YMODEM);
|
script_set_global(L, "YMODEM", YMODEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LUA_VERSION_NUM >= 502
|
||||||
|
static int luaopen_tio(lua_State *L)
|
||||||
|
{
|
||||||
|
luaL_newlib(L, tio_lib);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void script_run(int fd, const char *script_filename)
|
void script_run(int fd, const char *script_filename)
|
||||||
{
|
{
|
||||||
lua_State *L;
|
lua_State *L;
|
||||||
|
|
@ -650,8 +499,15 @@ void script_run(int fd, const char *script_filename)
|
||||||
L = luaL_newstate();
|
L = luaL_newstate();
|
||||||
luaL_openlibs(L);
|
luaL_openlibs(L);
|
||||||
|
|
||||||
// Bind tio functions
|
#if LUA_VERSION_NUM >= 502
|
||||||
lua_register_tio(L);
|
luaL_requiref(L, "tio", luaopen_tio, 1);
|
||||||
|
#else
|
||||||
|
luaL_register(L, "tio", tio_lib);
|
||||||
|
#endif
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
// Load lua init script
|
||||||
|
script_load(L);
|
||||||
|
|
||||||
// Initialize globals
|
// Initialize globals
|
||||||
script_set_globals(L);
|
script_set_globals(L);
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ char *timestamp_current_time(void)
|
||||||
len = strftime(time_string, sizeof(time_string), "%Y-%m-%dT%H:%M:%S", tm);
|
len = strftime(time_string, sizeof(time_string), "%Y-%m-%dT%H:%M:%S", tm);
|
||||||
break;
|
break;
|
||||||
case TIMESTAMP_EPOCH:
|
case TIMESTAMP_EPOCH:
|
||||||
|
case TIMESTAMP_EPOCH_USEC:
|
||||||
// "N.sss" (seconds since Unix epoch, 1970-01-01 00:00:00Z)
|
// "N.sss" (seconds since Unix epoch, 1970-01-01 00:00:00Z)
|
||||||
tv = tv_now;
|
tv = tv_now;
|
||||||
tm = localtime(&tv.tv_sec);
|
tm = localtime(&tv.tv_sec);
|
||||||
|
|
@ -84,12 +85,18 @@ char *timestamp_current_time(void)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append milliseconds to all timestamps
|
// Append millis-/microseconds to all timestamps
|
||||||
if (len)
|
if (len)
|
||||||
|
{
|
||||||
|
if ( option.timestamp == TIMESTAMP_EPOCH_USEC )
|
||||||
|
{
|
||||||
|
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%06ld", (long)tv.tv_usec);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%03ld", (long)tv.tv_usec / 1000);
|
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%03ld", (long)tv.tv_usec / 1000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Save previous time value for next run
|
// Save previous time value for next run
|
||||||
tv_previous = tv_now;
|
tv_previous = tv_now;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ typedef enum
|
||||||
TIMESTAMP_24HOUR_DELTA,
|
TIMESTAMP_24HOUR_DELTA,
|
||||||
TIMESTAMP_ISO8601,
|
TIMESTAMP_ISO8601,
|
||||||
TIMESTAMP_EPOCH,
|
TIMESTAMP_EPOCH,
|
||||||
|
TIMESTAMP_EPOCH_USEC,
|
||||||
TIMESTAMP_END,
|
TIMESTAMP_END,
|
||||||
} timestamp_t;
|
} timestamp_t;
|
||||||
|
|
||||||
|
|
|
||||||
315
src/tty.c
315
src/tty.c
|
|
@ -22,6 +22,15 @@
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
#include <linux/serial.h>
|
#include <linux/serial.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__) || defined(__MACH__)
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#include <IOKit/IOBSD.h>
|
||||||
|
#include <IOKit/IOKitLib.h>
|
||||||
|
#include <IOKit/serial/IOSerialKeys.h>
|
||||||
|
#include <IOKit/usb/IOUSBLib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
@ -623,16 +632,18 @@ void tty_output_mode_set(output_mode_t mode)
|
||||||
static void mappings_print(void)
|
static void mappings_print(void)
|
||||||
{
|
{
|
||||||
if (option.map_i_cr_nl || option.map_ign_cr || option.map_i_ff_escc ||
|
if (option.map_i_cr_nl || option.map_ign_cr || option.map_i_ff_escc ||
|
||||||
option.map_i_nl_cr || option.map_i_nl_crnl || option.map_o_cr_nl ||
|
option.map_i_nl_cr || option.map_i_nl_crnl || option.map_i_cr_crnl ||
|
||||||
option.map_o_del_bs || option.map_o_nl_crnl || option.map_o_ltu ||
|
option.map_o_cr_nl || option.map_o_del_bs || option.map_o_nl_crnl ||
|
||||||
option.map_o_nulbrk || option.map_i_msb2lsb || option.map_o_ign_cr)
|
option.map_o_ltu || option.map_o_nulbrk || option.map_i_msb2lsb ||
|
||||||
|
option.map_o_ign_cr)
|
||||||
{
|
{
|
||||||
tio_printf(" Mappings:%s%s%s%s%s%s%s%s%s%s%s%s",
|
tio_printf(" Mappings:%s%s%s%s%s%s%s%s%s%s%s%s%s",
|
||||||
option.map_i_cr_nl ? " ICRNL" : "",
|
option.map_i_cr_nl ? " ICRNL" : "",
|
||||||
option.map_ign_cr ? " IGNCR" : "",
|
option.map_ign_cr ? " IGNCR" : "",
|
||||||
option.map_i_ff_escc ? " IFFESCC" : "",
|
option.map_i_ff_escc ? " IFFESCC" : "",
|
||||||
option.map_i_nl_cr ? " INLCR" : "",
|
option.map_i_nl_cr ? " INLCR" : "",
|
||||||
option.map_i_nl_crnl ? " INLCRNL" : "",
|
option.map_i_nl_crnl ? " INLCRNL" : "",
|
||||||
|
option.map_i_cr_crnl ? " ICRCRNL" : "",
|
||||||
option.map_i_msb2lsb ? " IMSB2LSB" : "",
|
option.map_i_msb2lsb ? " IMSB2LSB" : "",
|
||||||
option.map_o_cr_nl ? " OCRNL" : "",
|
option.map_o_cr_nl ? " OCRNL" : "",
|
||||||
option.map_o_del_bs ? " ODELBS" : "",
|
option.map_o_del_bs ? " ODELBS" : "",
|
||||||
|
|
@ -783,30 +794,34 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
||||||
tio_printf("INLCRNL is %s", option.map_i_nl_crnl ? "set" : "unset");
|
tio_printf("INLCRNL is %s", option.map_i_nl_crnl ? "set" : "unset");
|
||||||
break;
|
break;
|
||||||
case KEY_5:
|
case KEY_5:
|
||||||
|
option.map_i_cr_crnl = !option.map_i_cr_crnl;
|
||||||
|
tio_printf("ICRCRNL is %s", option.map_i_cr_crnl ? "set" : "unset");
|
||||||
|
break;
|
||||||
|
case KEY_6:
|
||||||
option.map_i_msb2lsb = !option.map_i_msb2lsb;
|
option.map_i_msb2lsb = !option.map_i_msb2lsb;
|
||||||
tio_printf("IMSB2LSB is %s", option.map_i_msb2lsb ? "set" : "unset");
|
tio_printf("IMSB2LSB is %s", option.map_i_msb2lsb ? "set" : "unset");
|
||||||
break;
|
break;
|
||||||
case KEY_6:
|
case KEY_7:
|
||||||
option.map_o_cr_nl = !option.map_o_cr_nl;
|
option.map_o_cr_nl = !option.map_o_cr_nl;
|
||||||
tio_printf("OCRNL is %s", option.map_o_cr_nl ? "set" : "unset");
|
tio_printf("OCRNL is %s", option.map_o_cr_nl ? "set" : "unset");
|
||||||
break;
|
break;
|
||||||
case KEY_7:
|
case KEY_8:
|
||||||
option.map_o_del_bs = !option.map_o_del_bs;
|
option.map_o_del_bs = !option.map_o_del_bs;
|
||||||
tio_printf("ODELBS is %s", option.map_o_del_bs ? "set" : "unset");
|
tio_printf("ODELBS is %s", option.map_o_del_bs ? "set" : "unset");
|
||||||
break;
|
break;
|
||||||
case KEY_8:
|
case KEY_9:
|
||||||
option.map_o_nl_crnl = !option.map_o_nl_crnl;
|
option.map_o_nl_crnl = !option.map_o_nl_crnl;
|
||||||
tio_printf("ONLCRNL is %s", option.map_o_nl_crnl ? "set" : "unset");
|
tio_printf("ONLCRNL is %s", option.map_o_nl_crnl ? "set" : "unset");
|
||||||
break;
|
break;
|
||||||
case KEY_9:
|
case KEY_A:
|
||||||
option.map_o_ltu = !option.map_o_ltu;
|
option.map_o_ltu = !option.map_o_ltu;
|
||||||
tio_printf("OLTU is %s", option.map_o_ltu ? "set" : "unset");
|
tio_printf("OLTU is %s", option.map_o_ltu ? "set" : "unset");
|
||||||
break;
|
break;
|
||||||
case KEY_A:
|
case KEY_B:
|
||||||
option.map_o_nulbrk = !option.map_o_nulbrk;
|
option.map_o_nulbrk = !option.map_o_nulbrk;
|
||||||
tio_printf("ONULBRK is %s", option.map_o_nulbrk ? "set" : "unset");
|
tio_printf("ONULBRK is %s", option.map_o_nulbrk ? "set" : "unset");
|
||||||
break;
|
break;
|
||||||
case KEY_B:
|
case KEY_C:
|
||||||
option.map_o_ign_cr = !option.map_o_ign_cr;
|
option.map_o_ign_cr = !option.map_o_ign_cr;
|
||||||
tio_printf("OIGNCR is %s", option.map_o_ign_cr ? "set" : "unset");
|
tio_printf("OIGNCR is %s", option.map_o_ign_cr ? "set" : "unset");
|
||||||
break;
|
break;
|
||||||
|
|
@ -1007,20 +1022,22 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
||||||
tio_printf(" (3) INLCR: %s mapping NL to CR on input",
|
tio_printf(" (3) INLCR: %s mapping NL to CR on input",
|
||||||
option.map_i_nl_cr ? "Unset" : "Set");
|
option.map_i_nl_cr ? "Unset" : "Set");
|
||||||
tio_printf(" (4) INLCRNL: %s mapping NL to CR-NL on input",
|
tio_printf(" (4) INLCRNL: %s mapping NL to CR-NL on input",
|
||||||
option.map_i_nl_cr ? "Unset" : "Set");
|
option.map_i_nl_crnl ? "Unset" : "Set");
|
||||||
tio_printf(" (5) IMSB2LSB: %s mapping MSB bit order to LSB on input",
|
tio_printf(" (5) ICRCRNL: %s mapping CR to CR-NL on input",
|
||||||
|
option.map_i_cr_crnl ? "Unset" : "Set");
|
||||||
|
tio_printf(" (6) IMSB2LSB: %s mapping MSB bit order to LSB on input",
|
||||||
option.map_i_msb2lsb ? "Unset" : "Set");
|
option.map_i_msb2lsb ? "Unset" : "Set");
|
||||||
tio_printf(" (6) OCRNL: %s mapping CR to NL on output",
|
tio_printf(" (7) OCRNL: %s mapping CR to NL on output",
|
||||||
option.map_o_cr_nl ? "Unset" : "Set");
|
option.map_o_cr_nl ? "Unset" : "Set");
|
||||||
tio_printf(" (7) ODELBS: %s mapping DEL to BS on output",
|
tio_printf(" (8) ODELBS: %s mapping DEL to BS on output",
|
||||||
option.map_o_del_bs ? "Unset" : "Set");
|
option.map_o_del_bs ? "Unset" : "Set");
|
||||||
tio_printf(" (8) ONLCRNL: %s mapping NL to CR-NL on output",
|
tio_printf(" (9) ONLCRNL: %s mapping NL to CR-NL on output",
|
||||||
option.map_o_nl_crnl ? "Unset" : "Set");
|
option.map_o_nl_crnl ? "Unset" : "Set");
|
||||||
tio_printf(" (9) OLTU: %s mapping lowercase to uppercase on output",
|
tio_printf(" (a) OLTU: %s mapping lowercase to uppercase on output",
|
||||||
option.map_o_ltu ? "Unset" : "Set");
|
option.map_o_ltu ? "Unset" : "Set");
|
||||||
tio_printf(" (a) ONULBRK: %s mapping NUL to send break signal on output",
|
tio_printf(" (b) ONULBRK: %s mapping NUL to send break signal on output",
|
||||||
option.map_o_nulbrk ? "Unset" : "Set");
|
option.map_o_nulbrk ? "Unset" : "Set");
|
||||||
tio_printf(" (b) OIGNCR: %s ignoring CR on output",
|
tio_printf(" (c) OIGNCR: %s ignoring CR on output",
|
||||||
option.map_o_ign_cr ? "Unset" : "Set");
|
option.map_o_ign_cr ? "Unset" : "Set");
|
||||||
|
|
||||||
// Process next input character as sub command
|
// Process next input character as sub command
|
||||||
|
|
@ -1083,6 +1100,9 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
||||||
case TIMESTAMP_EPOCH:
|
case TIMESTAMP_EPOCH:
|
||||||
tio_printf("Switched timestamp mode to epoch");
|
tio_printf("Switched timestamp mode to epoch");
|
||||||
break;
|
break;
|
||||||
|
case TIMESTAMP_EPOCH_USEC:
|
||||||
|
tio_printf("Switched timestamp mode to epoch with subdivision in microseconds");
|
||||||
|
break;
|
||||||
case TIMESTAMP_END:
|
case TIMESTAMP_END:
|
||||||
option.timestamp = TIMESTAMP_NONE;
|
option.timestamp = TIMESTAMP_NONE;
|
||||||
tio_printf("Switched timestamp mode off");
|
tio_printf("Switched timestamp mode off");
|
||||||
|
|
@ -1765,18 +1785,22 @@ GList *tty_search_for_serial_devices(void)
|
||||||
creation_time = fs_get_creation_time(path);
|
creation_time = fs_get_creation_time(path);
|
||||||
double uptime = current_time - creation_time;
|
double uptime = current_time - creation_time;
|
||||||
|
|
||||||
// Read sysfs files to get best possible description of the driver
|
// Read sysfs files to get best possible description
|
||||||
char description[50] = {};
|
char description[50] = {};
|
||||||
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/interface", entry->d_name);
|
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../product", entry->d_name);
|
||||||
if (length == -1)
|
|
||||||
{
|
|
||||||
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../interface", entry->d_name);
|
|
||||||
}
|
|
||||||
if (length == -1)
|
if (length == -1)
|
||||||
{
|
{
|
||||||
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../../product", entry->d_name);
|
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../../product", entry->d_name);
|
||||||
}
|
}
|
||||||
if (length == -1)
|
if (length == -1)
|
||||||
|
{
|
||||||
|
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/interface", entry->d_name);
|
||||||
|
}
|
||||||
|
if (length == -1)
|
||||||
|
{
|
||||||
|
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../interface", entry->d_name);
|
||||||
|
}
|
||||||
|
if (length == -1)
|
||||||
{
|
{
|
||||||
snprintf(description, sizeof(description), "%s", get_serial_port_type(path));
|
snprintf(description, sizeof(description), "%s", get_serial_port_type(path));
|
||||||
}
|
}
|
||||||
|
|
@ -1833,6 +1857,226 @@ GList *tty_search_for_serial_devices(void)
|
||||||
return device_list;
|
return device_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(__APPLE__) || defined(__MACH__)
|
||||||
|
|
||||||
|
char *getPropertyString(io_object_t device, CFStringRef property)
|
||||||
|
{
|
||||||
|
/* Validate inputs */
|
||||||
|
if (device == IO_OBJECT_NULL || property == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempt to get property */
|
||||||
|
CFTypeRef valueRef = IORegistryEntryCreateCFProperty(
|
||||||
|
device, property, kCFAllocatorDefault, 0);
|
||||||
|
if (!valueRef)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure it's a CFString */
|
||||||
|
if (CFGetTypeID(valueRef) != CFStringGetTypeID())
|
||||||
|
{
|
||||||
|
CFRelease(valueRef);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert to C string */
|
||||||
|
CFIndex length = CFStringGetLength(valueRef);
|
||||||
|
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
||||||
|
char *result = malloc(maxSize);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
CFRelease(valueRef);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool converted = CFStringGetCString(
|
||||||
|
(CFStringRef)valueRef,
|
||||||
|
result,
|
||||||
|
maxSize,
|
||||||
|
kCFStringEncodingUTF8
|
||||||
|
);
|
||||||
|
|
||||||
|
CFRelease(valueRef);
|
||||||
|
|
||||||
|
if (!converted)
|
||||||
|
{
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *getDeviceLocation(io_object_t device)
|
||||||
|
{
|
||||||
|
/* Validate device */
|
||||||
|
if (device == IO_OBJECT_NULL)
|
||||||
|
{
|
||||||
|
return strdup("Invalid Device");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempt to get location */
|
||||||
|
io_string_t location = {0};
|
||||||
|
kern_return_t result = IORegistryEntryGetLocationInPlane(
|
||||||
|
device, kIOServicePlane, location);
|
||||||
|
|
||||||
|
if (result != KERN_SUCCESS)
|
||||||
|
{
|
||||||
|
return strdup("Unknown Location");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safely copy location */
|
||||||
|
size_t len = strnlen(location, sizeof(io_string_t));
|
||||||
|
char *trimmed_location = calloc(1, len + 1);
|
||||||
|
|
||||||
|
if (!trimmed_location)
|
||||||
|
{
|
||||||
|
return strdup("Memory Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(trimmed_location, location, len);
|
||||||
|
return trimmed_location;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for __APPLE__
|
||||||
|
GList *tty_search_for_serial_devices(void)
|
||||||
|
{
|
||||||
|
search_reset();
|
||||||
|
io_iterator_t iter = IO_OBJECT_NULL;
|
||||||
|
CFMutableDictionaryRef matchingDict = NULL;
|
||||||
|
listing_device_name_length_max = 0;
|
||||||
|
|
||||||
|
/* Create matching dictionary for serial devices */
|
||||||
|
if (!(matchingDict = IOServiceMatching(kIOSerialBSDServiceValue)))
|
||||||
|
{
|
||||||
|
tio_error_print("Failed to create matching dictionary for serial devices");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get matching services */
|
||||||
|
kern_return_t kernResult = IOServiceGetMatchingServices(
|
||||||
|
kIOMainPortDefault, matchingDict, &iter);
|
||||||
|
matchingDict = NULL; /* Dictionary ownership transferred */
|
||||||
|
|
||||||
|
if (kernResult != KERN_SUCCESS)
|
||||||
|
{
|
||||||
|
tio_error_print("IOServiceGetMatchingServices failed: %d", kernResult);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Defensive check for iterator */
|
||||||
|
if (iter == IO_OBJECT_NULL)
|
||||||
|
{
|
||||||
|
tio_error_print("Invalid device iterator");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Iterate through serial devices and collect information */
|
||||||
|
for (io_object_t device; (device = IOIteratorNext(iter));)
|
||||||
|
{
|
||||||
|
char *devicePath = NULL, *locationID = NULL;
|
||||||
|
char *productName = NULL, *vendorName = NULL;
|
||||||
|
char tid[5] = {0};
|
||||||
|
double uptime = 0.0;
|
||||||
|
|
||||||
|
/* Get device path - key determines if we get tty. or cu. */
|
||||||
|
//if (!(devicePath = getPropertyString(device, CFSTR(kIODialinDeviceKey))))
|
||||||
|
if (!(devicePath = getPropertyString(device, CFSTR(kIOCalloutDeviceKey))))
|
||||||
|
{
|
||||||
|
IOObjectRelease(device);
|
||||||
|
continue; /* Skip devices without a path */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update length of longest device name string */
|
||||||
|
listing_device_name_length_max =
|
||||||
|
strlen(devicePath) > listing_device_name_length_max
|
||||||
|
? strlen(devicePath)
|
||||||
|
: listing_device_name_length_max;
|
||||||
|
|
||||||
|
/* Calculate uptime */
|
||||||
|
uptime = get_current_time() - fs_get_creation_time(devicePath);
|
||||||
|
|
||||||
|
/* Find USB device (if applicable) */
|
||||||
|
io_object_t usbDevice = IO_OBJECT_NULL;
|
||||||
|
kern_return_t usbResult = IORegistryEntryGetParentEntry(
|
||||||
|
device, kIOServicePlane, &usbDevice);
|
||||||
|
|
||||||
|
/* Traverse up the device tree to find a USB device */
|
||||||
|
while (usbResult == KERN_SUCCESS &&
|
||||||
|
!IOObjectConformsTo(usbDevice, "IOUSBDevice"))
|
||||||
|
{
|
||||||
|
io_object_t oldUsbDevice = usbDevice;
|
||||||
|
usbResult = IORegistryEntryGetParentEntry(
|
||||||
|
usbDevice, kIOServicePlane, &usbDevice);
|
||||||
|
IOObjectRelease(oldUsbDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we found a USB device */
|
||||||
|
if (usbResult == KERN_SUCCESS)
|
||||||
|
{
|
||||||
|
locationID = getDeviceLocation(usbDevice);
|
||||||
|
|
||||||
|
unsigned long hash2 = djb2_hash((const unsigned char *)(locationID ?: ""));
|
||||||
|
base62_encode(hash2, tid);
|
||||||
|
|
||||||
|
/* Get product and vendor names */
|
||||||
|
productName = getPropertyString(usbDevice, CFSTR("USB Product Name"));
|
||||||
|
vendorName = getPropertyString(usbDevice, CFSTR("USB Vendor Name"));
|
||||||
|
|
||||||
|
IOObjectRelease(usbDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create device structure */
|
||||||
|
device_t *device_info = g_new0(device_t, 1);
|
||||||
|
if (!device_info)
|
||||||
|
{
|
||||||
|
tio_error_print("Memory allocation failed for device_info");
|
||||||
|
free(devicePath);
|
||||||
|
free(locationID);
|
||||||
|
free(productName);
|
||||||
|
free(vendorName);
|
||||||
|
IOObjectRelease(device);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Populate device info */
|
||||||
|
*device_info = (device_t) {
|
||||||
|
.path = devicePath,
|
||||||
|
.tid = g_strdup(tid),
|
||||||
|
.uptime = uptime,
|
||||||
|
.driver = g_strdup(vendorName),
|
||||||
|
.description = g_strdup(productName ?: vendorName ?: "")
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Add to device list */
|
||||||
|
device_list = g_list_append(device_list, device_info);
|
||||||
|
|
||||||
|
/* Clean up */
|
||||||
|
free(locationID);
|
||||||
|
free(productName);
|
||||||
|
free(vendorName);
|
||||||
|
IOObjectRelease(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clean up iterator */
|
||||||
|
IOObjectRelease(iter);
|
||||||
|
|
||||||
|
/* Check if device list is empty */
|
||||||
|
if (!device_list)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sort device list by uptime */
|
||||||
|
device_list = g_list_sort(device_list, compare_uptime);
|
||||||
|
|
||||||
|
return device_list;
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
GList *tty_search_for_serial_devices(void)
|
GList *tty_search_for_serial_devices(void)
|
||||||
|
|
@ -2113,8 +2357,21 @@ void tty_wait_for_device(void)
|
||||||
}
|
}
|
||||||
else if (status == -1)
|
else if (status == -1)
|
||||||
{
|
{
|
||||||
|
#if defined(__CYGWIN__)
|
||||||
|
// Happens when port unpluged
|
||||||
|
if (errno == EACCES)
|
||||||
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
if (errno == EBADF)
|
||||||
|
{
|
||||||
|
break; // tty_disconnect() will be naturally triggered by atexit()
|
||||||
|
}
|
||||||
|
#else
|
||||||
tio_error_printf("select() failed (%s)", strerror(errno));
|
tio_error_printf("select() failed (%s)", strerror(errno));
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2584,6 +2841,15 @@ int tty_connect(void)
|
||||||
do_timestamp = true;
|
do_timestamp = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ((input_char == '\r') && (option.map_i_cr_crnl) && (!option.map_i_msb2lsb))
|
||||||
|
{
|
||||||
|
printchar('\r');
|
||||||
|
printchar('\n');
|
||||||
|
if (option.timestamp)
|
||||||
|
{
|
||||||
|
do_timestamp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
else if ((input_char == '\f') && (option.map_i_ff_escc) && (!option.map_i_msb2lsb))
|
else if ((input_char == '\f') && (option.map_i_ff_escc) && (!option.map_i_msb2lsb))
|
||||||
{
|
{
|
||||||
printchar('\e');
|
printchar('\e');
|
||||||
|
|
@ -2744,4 +3010,3 @@ error_read:
|
||||||
error_open:
|
error_open:
|
||||||
return TIO_ERROR;
|
return TIO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue