mirror of
https://github.com/tio/tio.git
synced 2026-05-01 14:57:59 +02:00
Compare commits
69 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fb3a64ba2 | ||
|
|
3af4c5591e | ||
|
|
cce94b9d92 | ||
|
|
86f48a2fb6 | ||
|
|
381c0b7823 | ||
|
|
8f33cff6ea | ||
|
|
9d00cd3492 | ||
|
|
3e0b2d861d | ||
|
|
a1217af4c6 | ||
|
|
58bf5c5008 | ||
|
|
7e61a34df3 | ||
|
|
2fb788f817 | ||
|
|
f887756a71 | ||
|
|
5d915134a3 | ||
|
|
7516dff802 | ||
|
|
03ef931fb2 | ||
|
|
437881f0ed | ||
|
|
c736b1e353 | ||
|
|
d682e98a66 | ||
|
|
bdfe87e1cb | ||
|
|
5c2ced1093 | ||
|
|
f87f470415 | ||
|
|
2e86718973 | ||
|
|
013aebcc05 | ||
|
|
b33045189f | ||
|
|
600c3d7563 | ||
|
|
ebce2d4ee9 | ||
|
|
16b7aee42f | ||
|
|
d33e275ca3 | ||
|
|
da4074c9a5 | ||
|
|
f716d2ccdd | ||
|
|
7567e08227 | ||
|
|
6aca9ffee5 | ||
|
|
f5740dbf31 | ||
|
|
d163afc6b1 | ||
|
|
1b60dd1ae7 | ||
|
|
795ef28f79 | ||
|
|
8e155c9276 | ||
|
|
6831ad0eae | ||
|
|
8f7bf2fd2c | ||
|
|
f389f11669 | ||
|
|
37994b3cc5 | ||
|
|
27f8f2c4e6 | ||
|
|
01e637cdf4 | ||
|
|
1b2a0ea130 | ||
|
|
b8135ea639 | ||
|
|
c49faa7337 | ||
|
|
4511d74a9e | ||
|
|
afd82f7ac4 | ||
|
|
db3f109c7d | ||
|
|
ab678e6c88 | ||
|
|
330e99381e | ||
|
|
7e314b2cc3 | ||
|
|
4fb034858a | ||
|
|
d494b9d3ac | ||
|
|
4034d0ad51 | ||
|
|
9fec689117 | ||
|
|
6c4b92270e | ||
|
|
a22b270749 | ||
|
|
9f27ce5899 | ||
|
|
27f9b9f2e8 | ||
|
|
2e7da862c8 | ||
|
|
bb2b4e30b2 | ||
|
|
f47467271f | ||
|
|
cdc773100c | ||
|
|
a3b67d3eb6 | ||
|
|
ef12ed62df | ||
|
|
2f6b3796f2 | ||
|
|
6163bc392b |
34 changed files with 963 additions and 426 deletions
5
.clang-format
Normal file
5
.clang-format
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
BasedOnStyle: llvm
|
||||
IndentWidth: 4
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
BreakBeforeBraces: Allman
|
||||
12
.github/workflows/codeql.yml
vendored
12
.github/workflows/codeql.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners
|
||||
# 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 }}
|
||||
permissions:
|
||||
actions: read
|
||||
|
|
@ -51,7 +51,7 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# 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).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
#- name: Autobuild
|
||||
# uses: github/codeql-action/autobuild@v2
|
||||
# uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ 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
|
||||
|
|
@ -78,7 +78,7 @@ jobs:
|
|||
./.github/workflows/codeql-buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
upload: false
|
||||
|
|
@ -107,14 +107,14 @@ jobs:
|
|||
output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
|
||||
|
||||
- name: Upload CodeQL results to code scanning
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: ${{ steps.step1.outputs.sarif-output }}
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
- name: Upload CodeQL results as an artifact
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: codeql-results
|
||||
path: ${{ steps.step1.outputs.sarif-output }}
|
||||
|
|
|
|||
3
.github/workflows/ubuntu.yml
vendored
3
.github/workflows/ubuntu.yml
vendored
|
|
@ -21,7 +21,8 @@ jobs:
|
|||
|
||||
- name: Install dependencies
|
||||
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
|
||||
run: |
|
||||
|
|
|
|||
2
.typos.toml
Normal file
2
.typos.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[default]
|
||||
extend-ignore-words-re = ["tio"]
|
||||
9
AUTHORS
9
AUTHORS
|
|
@ -57,5 +57,14 @@ KhazAkar <damianzrb@zohomail.eu>
|
|||
Eliot Alan Foss <eliotfoss@gmail.com>
|
||||
Robert Lipe <robertlipe@gpsbabel.org>
|
||||
Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
|
||||
Tomka Gergely <tomkatudor@gmail.com>
|
||||
Steve Marple <stevemarple@googlemail.com>
|
||||
konosubakonoakua <ailike_meow@qq.com>
|
||||
Keith Hill <k_hill@unitronlp.com>
|
||||
Lubov66 <radolevanja@gmail.com>
|
||||
V <v@anomalous.eu>
|
||||
Samuel Holland <samuel@sholland.org>
|
||||
David Ordnung <david.ordnung@googlemail.com>
|
||||
|
||||
|
||||
Thanks to everyone who has contributed to this project.
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2014-2022 Martin Lund <martin.lund@keep-it-simple.com>
|
||||
Copyright (c) 2014-2025 Martin Lund <martin.lund@keep-it-simple.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
|
|
|
|||
96
NEWS
96
NEWS
|
|
@ -1,5 +1,99 @@
|
|||
=== tio v3.9 (2025-04-13) ===
|
||||
|
||||
=== tio v3.6 (2024-07-19) ===
|
||||
|
||||
|
||||
Changes since tio v3.8 (2024-11-30):
|
||||
|
||||
* Fix parsing of timestamp options
|
||||
|
||||
* codeql: Upgrade to upload-artifact@v4
|
||||
|
||||
* Update plaintext man page
|
||||
|
||||
* Add character mapping examples
|
||||
|
||||
* Fix pattern matching memory corruption
|
||||
|
||||
Samuel Holland:
|
||||
|
||||
* Don't add null characters to the expect buffer
|
||||
|
||||
They prevent regexec() from seeing the remainder of the buffer.
|
||||
|
||||
V:
|
||||
|
||||
* Disable stdout buffering globally
|
||||
|
||||
This makes it possible to pipe output to other programs cleanly.
|
||||
|
||||
Lubov66:
|
||||
|
||||
* docs: edited the license date
|
||||
|
||||
Jakob Haufe:
|
||||
|
||||
* Manpage: Fix backslash encoding
|
||||
|
||||
Literal backslash needs to be written as \e.
|
||||
|
||||
|
||||
|
||||
Changes since tio v3.7 (2024-08-31):
|
||||
|
||||
* Rename git version to simply version
|
||||
|
||||
* Clean up lua API
|
||||
|
||||
Rename modem_send() to send()
|
||||
Rename send to write()
|
||||
|
||||
* Zero initialize buffer in read_string()
|
||||
|
||||
* Use version from git
|
||||
|
||||
* Fix memory leak in base62_encode()
|
||||
|
||||
* Fix name declaration conflict with socket send()
|
||||
|
||||
* Add clang-format spec
|
||||
|
||||
Keith Hill:
|
||||
|
||||
+ Add system timestamps to lua read() and new lua read_line() per global options
|
||||
+ Add missing timestamp-format epoch
|
||||
+ Update send_ to use fsync and tcdrain like normal tty_sync does
|
||||
+ Rework read_line to save partial line at timeout
|
||||
+ Simplified read_line to reduce cyclomatic complexity
|
||||
+ renamed example files read.lua and read_line.lua
|
||||
+ moved #define READ_LINE_SIZE to top of file
|
||||
+ renamed g_linebuf to linebuf, and moved it into read_line as a static variable
|
||||
|
||||
|
||||
|
||||
Changes since tio v3.6 (2024-07-19):
|
||||
|
||||
* Remove unnecessary sync in line input mode
|
||||
|
||||
This caused a problem for some highly timing sensitive modem read-eval-print
|
||||
loops because the input line and line termination characters (cr/nl) would be
|
||||
shifted out on the UART with too big delay inbetween because of two
|
||||
syncs.
|
||||
|
||||
* Fix socket send call on platforms without MSG_NOSIGNAL
|
||||
|
||||
To fix build issue encountered on MacOS Catalina but may apply to other
|
||||
platforms.
|
||||
|
||||
Steve Marple:
|
||||
|
||||
* Add "epoch" timestamp option
|
||||
|
||||
Add an option that prints the timestamp as the number of seconds since
|
||||
the Unix epoch.
|
||||
|
||||
Tomka Gergely:
|
||||
|
||||
* The log-directory options is not read from the configuration file.
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
125
README.md
125
README.md
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
# tio - a serial device I/O tool
|
||||
|
||||
[](https://github.com/tio/tio/actions/workflows/ubuntu.yml)
|
||||
[](https://github.com/tio/tio/actions/workflows/ubuntu.yml)
|
||||
[](https://github.com/tio/tio/actions/workflows/macos.yml)
|
||||
[](https://github.com/tio/tio/actions/workflows/codeql.yml)
|
||||
[](https://www.codefactor.io/repository/github/tio/tio)
|
||||
[](https://github.com/tio/tio/releases)
|
||||
[](https://repology.org/project/tio/versions)
|
||||
|
|
@ -232,19 +233,24 @@ Connect automatically to latest registered serial device:
|
|||
$ tio --auto-connect latest
|
||||
```
|
||||
|
||||
It is also possible to use exclude options to affect which serial devices are
|
||||
It is possible to use exclude options to affect which serial devices are
|
||||
involved in the automatic connection strategy:
|
||||
```
|
||||
$ tio --auto-connect new --exclude-devices "/dev/ttyACM?,/dev/ttyUSB2"
|
||||
```
|
||||
|
||||
Exclude drivers by pattern:
|
||||
And to exclude drivers by pattern:
|
||||
```
|
||||
$ tio --auto-connect new --exclude-drivers "cdc_acm,ftdi_sio"
|
||||
```
|
||||
Note: Pattern matching supports '*' and '?'. Use comma separation to define
|
||||
multiple patterns.
|
||||
|
||||
To include drivers by specific pattern simply negate the exclude option:
|
||||
```
|
||||
$ tio --auto-connect new --exclude-drivers !("cp2102")
|
||||
```
|
||||
|
||||
Log to file with autogenerated filename:
|
||||
```
|
||||
$ tio --log /dev/ttyUSB0
|
||||
|
|
@ -270,6 +276,11 @@ Redirect I/O to IPv4 network socket on port 4242:
|
|||
$ tio --socket inet:4242 /dev/ttyUSB0
|
||||
```
|
||||
|
||||
Map NL to CR-NL on input from device and DEL to BS on output to device:
|
||||
```
|
||||
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
|
||||
```
|
||||
|
||||
Pipe data to the serial device:
|
||||
```
|
||||
$ cat data.bin | tio /dev/ttyUSB0
|
||||
|
|
@ -277,12 +288,12 @@ $ cat data.bin | tio /dev/ttyUSB0
|
|||
|
||||
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:
|
||||
```
|
||||
$ 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
|
||||
```
|
||||
|
||||
|
|
@ -354,12 +365,12 @@ color = 11
|
|||
[svf2]
|
||||
device = /dev/ttyUSB0
|
||||
baudrate = 9600
|
||||
script = expect("login: "); send("root\n"); expect("Password: "); send("root\n")
|
||||
script = tio.expect("login: "); tio.write("root\n"); tio.expect("Password: "); tio.write("root\n")
|
||||
color = 12
|
||||
|
||||
[esp32]
|
||||
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
|
||||
color = 13
|
||||
|
||||
|
|
@ -384,62 +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.
|
||||
|
||||
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)
|
||||
Expect string - waits for string to match or timeout before continueing.
|
||||
Supports regular expressions. Special characters must be escaped with '\\'.
|
||||
Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
|
||||
|
||||
Returns 1 on successful match, 0 on timeout, or -1 on error.
|
||||
#### `tio.expect(pattern, timeout)`
|
||||
|
||||
On successful match it also returns the match string as second return value.
|
||||
Waits for the Lua pattern to match or timeout before continuing.
|
||||
Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
|
||||
|
||||
send(string)
|
||||
Send string.
|
||||
Returns the captures from the pattern or `nil` on timeout.
|
||||
|
||||
Returns number of bytes written on success or -1 on error.
|
||||
#### `tio.read(size, timeout)`
|
||||
|
||||
modem_send(file, protocol)
|
||||
Send file using x/y-modem protocol.
|
||||
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.
|
||||
|
||||
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
|
||||
Returns a string up to `size` bytes long on success and `nil` on timeout.
|
||||
|
||||
tty_search()
|
||||
Search for serial devices.
|
||||
#### `tio.readline(timeout)`
|
||||
|
||||
Returns a table of number indexed tables, one for each serial device
|
||||
found. Each of these tables contains the serial device information accessible
|
||||
via the following string indexed elements "path", "tid", "uptime", "driver",
|
||||
"description".
|
||||
Read line from serial device. If timeout is 0 or not provided it will wait
|
||||
forever until data is ready to read.
|
||||
|
||||
Returns nil if no serial devices are found.
|
||||
Returns a string on success and `nil` on timeout. On timeout a partially read
|
||||
line may be returned as a second return value.
|
||||
|
||||
read(size, timeout)
|
||||
Read from serial device. If timeout is 0 or not provided it will wait
|
||||
forever until data is ready to read.
|
||||
#### `tio.write(string)`
|
||||
|
||||
Returns number of bytes read on success, 0 on timeout, or -1 on error.
|
||||
Write string to serial device.
|
||||
|
||||
On success, returns read string as second return value.
|
||||
Returns the `tio` table.
|
||||
|
||||
set{line=state, ...}
|
||||
Set state of one or multiple tty modem lines.
|
||||
#### `tio.send(file, protocol)`
|
||||
|
||||
Line can be any of DTR, RTS, CTS, DSR, CD, RI
|
||||
Send file using x/y-modem protocol.
|
||||
|
||||
State is high, low, or toggle.
|
||||
Protocol can be any of `XMODEM_1K`, `XMODEM_CRC`, `YMODEM`.
|
||||
|
||||
sleep(seconds)
|
||||
Sleep for seconds.
|
||||
#### `tio.ttysearch()`
|
||||
|
||||
msleep(ms)
|
||||
Sleep for miliseconds.
|
||||
Search for serial devices.
|
||||
|
||||
Returns a table of number indexed tables, one for each serial device found.
|
||||
Each of these tables contains the serial device information accessible via the
|
||||
following string indexed elements "path", "tid", "uptime", "driver",
|
||||
"description".
|
||||
|
||||
Returns `nil` if no serial devices are found.
|
||||
|
||||
#### `tio.set{line=state, ...}`
|
||||
Set state of one or multiple tty modem lines.
|
||||
|
||||
Line can be any of `DTR`, `RTS`, `CTS`, `DSR`, `CD`, `RI`
|
||||
|
||||
State is `high`, `low`, or `toggle`.
|
||||
|
||||
#### `tio.sleep(seconds)`
|
||||
|
||||
Sleep for seconds.
|
||||
|
||||
#### `tio.msleep(ms)`
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -500,9 +529,13 @@ Note: The meson install steps may differ depending on your specific system.
|
|||
|
||||
Getting permission access errors trying to open your serial device?
|
||||
|
||||
Add your user to the group which allows serial device access. 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
|
||||
sudo usermod -a -G dialout <username>
|
||||
```
|
||||
$ sudo usermod -a -G dialout <username>
|
||||
Switch to the "dialout" group, temporary but immediately for this session.
|
||||
```bash
|
||||
newgrp dialout
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
16
TODO
16
TODO
|
|
@ -1,3 +1,19 @@
|
|||
* Add release support for arm and x86 binary tarballs
|
||||
|
||||
* Support input and input mapping from lua scripts
|
||||
|
||||
* Add option to send file raw (no modem protocol)
|
||||
|
||||
* Add loopback option
|
||||
|
||||
Send received serial input back to output (for testing etc.)
|
||||
|
||||
* Add loopback support between two serial ports
|
||||
|
||||
Useful for traffic monitoring
|
||||
|
||||
* Add mapping feature for printing non-printable characters
|
||||
|
||||
* Porting layer to support native win32 builds.
|
||||
|
||||
Some of the work that needs to be done:
|
||||
|
|
|
|||
|
|
@ -69,5 +69,10 @@ color = 13
|
|||
[esp32]
|
||||
device = /dev/ttyUSB0
|
||||
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
|
||||
|
||||
[buspirate]
|
||||
device = /dev/ttyACM0
|
||||
map = INLCRNL,ODELBS
|
||||
color = 15
|
||||
|
|
|
|||
|
|
@ -13,14 +13,13 @@ local logins = {
|
|||
},
|
||||
}
|
||||
|
||||
local found, match_str = expect("\\w+- login:", 10)
|
||||
if (1 == found) then
|
||||
local hostname = string.match(match_str, "^%w+")
|
||||
local hostname = tio.expect("^(%g+) login:", 10)
|
||||
if hostname then
|
||||
local login = logins[hostname]
|
||||
if (nil ~= login) then
|
||||
send(login.username .. "\n")
|
||||
expect("Password:")
|
||||
send(login.password .. "\n")
|
||||
tio.write(login.username .. "\n")
|
||||
tio.expect("Password:")
|
||||
tio.write(login.password .. "\n")
|
||||
else
|
||||
io.write("\r\nDon't know login info for " .. hostname .. "\r\n")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
set{DTR=high, RTS=low}
|
||||
msleep(100)
|
||||
set{DTR=low, RTS=high}
|
||||
msleep(100)
|
||||
set{RTS=toggle}
|
||||
tio.set{DTR=high, RTS=low}
|
||||
tio.msleep(100)
|
||||
tio.set{DTR=low, RTS=high}
|
||||
tio.msleep(100)
|
||||
tio.set{RTS=toggle}
|
||||
|
|
|
|||
14
examples/lua/read.lua
Normal file
14
examples/lua/read.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
tio.read(1000, 6000) -- initial config
|
||||
tio.write("\n")
|
||||
tio.msleep(100)
|
||||
tio.read(650, 60) -- main menu
|
||||
tio.write("S") -- S menu
|
||||
tio.msleep(30)
|
||||
tio.read(650, 60)
|
||||
tio.write("t") -- Parallel Value Table
|
||||
tio.read(650, 60)
|
||||
while true do
|
||||
tio.msleep(1000)
|
||||
tio.write("t")
|
||||
tio.read(650, 50) -- repeat PVT forever
|
||||
end
|
||||
15
examples/lua/read_line.lua
Normal file
15
examples/lua/read_line.lua
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
tio.read(1000, 8000) -- read initial config
|
||||
tio.write("\n")
|
||||
tio.read(650, 100) -- main menu
|
||||
tio.write("S") -- S menu
|
||||
repeat
|
||||
str = tio.readline(25)
|
||||
until str == nil
|
||||
while true do
|
||||
tio.write("t") -- query PV table
|
||||
tio.msleep(880)
|
||||
repeat
|
||||
str = tio.readline(60)
|
||||
tio.msleep(60)
|
||||
until str == nil
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
io.write("Searching... ")
|
||||
|
||||
local device = tty_search()
|
||||
local device = tio.ttysearch()
|
||||
|
||||
io.write("done\r\n")
|
||||
|
||||
|
|
|
|||
79
man/tio.1.in
79
man/tio.1.in
|
|
@ -148,6 +148,10 @@ Set timestamp format to any of the following timestamp formats:
|
|||
24-hour format relative to previous timestamp
|
||||
.IP "\fBiso8601"
|
||||
ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
|
||||
.IP "\fBepoch"
|
||||
Seconds since Unix epoch (1970-01-01)
|
||||
.IP "\fBepoch-usec"
|
||||
Seconds since Unix epoch (1970-01-01) with subdivision in microseconds
|
||||
.PP
|
||||
Default format is \fB24hour\fR
|
||||
.RE
|
||||
|
|
@ -216,6 +220,8 @@ Map FF to ESC-c on input
|
|||
Map NL to CR on input
|
||||
.IP "\fBINLCRNL"
|
||||
Map NL to CR-NL on input
|
||||
.IP "\fBICRCRNL"
|
||||
Map CR to CR-NL on input
|
||||
.IP "\fBIMSB2LSB"
|
||||
Map MSB bit order to LSB on input
|
||||
.IP "\fBOCRNL"
|
||||
|
|
@ -365,6 +371,10 @@ Default value is "always".
|
|||
|
||||
Execute shell command with I/O redirected to device
|
||||
|
||||
.TP
|
||||
.BR "\-\-complete-profiles
|
||||
|
||||
Prints profiles (for shell completion)
|
||||
.TP
|
||||
.BR \-v ", " \-\-version
|
||||
|
||||
|
|
@ -426,30 +436,41 @@ Send ctrl-t character
|
|||
.PP
|
||||
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:
|
||||
|
||||
.TP 6n
|
||||
|
||||
.IP "\fBexpect(string, timeout)"
|
||||
Expect string - waits for string to match or timeout before continuing.
|
||||
Supports regular expressions. Special characters must be escaped with '\\\\'.
|
||||
.IP "\fBtio.expect(pattern, timeout)"
|
||||
Waits for the Lua pattern to match or timeout before continuing.
|
||||
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 "\fBsend(string)"
|
||||
Send string.
|
||||
Returns a string up to size bytes long on success and nil on timeout.
|
||||
|
||||
Returns number of bytes written on success or -1 on error.
|
||||
.IP "\fBtio.readline(timeout)"
|
||||
Read line from serial device. If timeout is 0 or not provided it will wait
|
||||
forever until data is ready to read.
|
||||
|
||||
.IP "\fBmodem_send(file, protocol)"
|
||||
Returns a string on success and nil on timeout. On timeout a partially read
|
||||
line may be returned as a second return value.
|
||||
|
||||
.IP "\fBtio.write(string)"
|
||||
Write string to serial device.
|
||||
|
||||
Returns the tio table.
|
||||
|
||||
.IP "\fBtio.send(file, protocol)"
|
||||
Send file using x/y-modem protocol.
|
||||
|
||||
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
|
||||
|
||||
.IP "\fBtty_search()"
|
||||
.IP "\fBtio.ttysearch()"
|
||||
Search for serial devices.
|
||||
|
||||
Returns a table of number indexed tables, one for each serial device found.
|
||||
|
|
@ -459,27 +480,26 @@ following string indexed elements "path", "tid", "uptime", "driver",
|
|||
|
||||
Returns nil if no serial devices are found.
|
||||
|
||||
.IP "\fBread(size, 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.
|
||||
|
||||
On success, returns read string as second return value.
|
||||
|
||||
.IP "\fBset{line=state, ...}"
|
||||
.IP "\fBtio.set{line=state, ...}"
|
||||
Set state of one or multiple tty modem lines.
|
||||
|
||||
Line can be any of DTR, RTS, CTS, DSR, CD, RI
|
||||
|
||||
State is high, low, or toggle.
|
||||
|
||||
.IP "\fBsleep(seconds)"
|
||||
.IP "\fBtio.sleep(seconds)"
|
||||
Sleep for seconds.
|
||||
.IP "\fBmsleep(ms)"
|
||||
.IP "\fBtio.msleep(ms)"
|
||||
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"
|
||||
.PP
|
||||
|
|
@ -708,7 +728,7 @@ expect -i $uart "prompt> "
|
|||
.TP
|
||||
It is also possible to use tio's own simpler expect/send script functionality to e.g. automate logins:
|
||||
|
||||
$ tio --script 'expect("login: "); send("root\\n"); expect("Password: "); send("root\\n")' /dev/ttyUSB0
|
||||
$ tio --script 'tio.expect("login: "); tio.write("root\\n"); tio.expect("Password: "); tio.write("root\\n")' /dev/ttyUSB0
|
||||
|
||||
.TP
|
||||
Redirect device I/O to network file socket for remote TTY sharing:
|
||||
|
|
@ -729,7 +749,7 @@ $ echo "ls -la" | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-p
|
|||
.TP
|
||||
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
|
||||
|
|
@ -737,6 +757,11 @@ Likewise, to pipe data from file to the serial device:
|
|||
|
||||
$ cat data.bin | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
|
||||
|
||||
.TP
|
||||
Map NL to CR-NL on input from device and DEL to BS on output to device:
|
||||
|
||||
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
|
||||
|
||||
.TP
|
||||
Enable RS-485 mode:
|
||||
|
||||
|
|
@ -745,7 +770,7 @@ $ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
|
|||
.TP
|
||||
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"
|
||||
.PP
|
||||
|
|
|
|||
|
|
@ -114,6 +114,10 @@ OPTIONS
|
|||
|
||||
iso8601 ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
|
||||
|
||||
epoch Seconds since Unix epoch (1970-01-01)
|
||||
|
||||
epoch-usec Seconds since Unix epoch (1970-01-01) with subdivision microseconds
|
||||
|
||||
Default format is 24hour
|
||||
|
||||
--timestamp-timeout <ms>
|
||||
|
|
@ -166,6 +170,8 @@ OPTIONS
|
|||
|
||||
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
|
||||
|
||||
OCRNL Map CR to NL on output
|
||||
|
|
@ -216,7 +222,8 @@ OPTIONS
|
|||
|
||||
Redirect I/O to socket.
|
||||
|
||||
Any input from clients connected to the socket is sent on the serial port as if entered at the terminal where tio is running (except that ctrl-t sequences are not recognized), and any input from the serial port is multiplexed to the terminal and all connected clients.
|
||||
Any input from clients connected to the socket is sent on the serial port as if entered at the terminal where tio is running (except that ctrl-t sequences are not recognized), and any input from the serial port is multi‐
|
||||
plexed to the terminal and all connected clients.
|
||||
|
||||
Sockets remain open while the serial port is disconnected, and writes will block.
|
||||
|
||||
|
|
@ -282,6 +289,10 @@ OPTIONS
|
|||
|
||||
Execute shell command with I/O redirected to device
|
||||
|
||||
--complete-profiles
|
||||
|
||||
Prints profiles (for shell completion)
|
||||
|
||||
-v, --version
|
||||
|
||||
Display program version.
|
||||
|
|
@ -340,7 +351,7 @@ KEY COMMANDS
|
|||
SCRIPT API
|
||||
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 available:
|
||||
|
||||
expect(string, 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.
|
||||
|
|
@ -349,12 +360,26 @@ SCRIPT API
|
|||
|
||||
On successful match it also returns the match string as second return value.
|
||||
|
||||
send(string)
|
||||
Send string.
|
||||
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.
|
||||
|
||||
Returns number of bytes read on success, 0 on timeout, or -1 on error.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Returns number of bytes written on success or -1 on error.
|
||||
|
||||
modem_send(file, protocol)
|
||||
send(file, protocol)
|
||||
Send file using x/y-modem protocol.
|
||||
|
||||
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
|
||||
|
|
@ -362,17 +387,11 @@ SCRIPT API
|
|||
tty_search()
|
||||
Search for serial devices.
|
||||
|
||||
Returns a table of number indexed tables, one for each serial device found. Each of these tables contains the serial device information accessible via the following string indexed elements "path", "tid", "uptime", "driver", "description".
|
||||
Returns 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‐
|
||||
ver", "description".
|
||||
|
||||
Returns nil if no serial devices are found.
|
||||
|
||||
read(size, 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.
|
||||
|
||||
On success, returns read string as second return value.
|
||||
|
||||
set{line=state, ...}
|
||||
Set state of one or multiple tty modem lines.
|
||||
|
||||
|
|
@ -563,7 +582,7 @@ EXAMPLES
|
|||
|
||||
It is also possible to use tio's own simpler expect/send script functionality to e.g. automate logins:
|
||||
|
||||
$ tio --script 'expect("login: "); send("root\n"); expect("Password: "); send("root\n")' /dev/ttyUSB0
|
||||
$ tio --script 'expect("login: "); write("root\n"); expect("Password: "); write("root\n")' /dev/ttyUSB0
|
||||
|
||||
Redirect device I/O to network file socket for remote TTY sharing:
|
||||
|
||||
|
|
@ -585,6 +604,10 @@ EXAMPLES
|
|||
|
||||
$ cat data.bin | tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
|
||||
|
||||
Map NL to CR-NL on input from device and DEL to BS on output to device:
|
||||
|
||||
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
|
||||
|
||||
Enable RS-485 mode:
|
||||
|
||||
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
|
||||
|
|
@ -599,4 +622,4 @@ WEBSITE
|
|||
AUTHOR
|
||||
Maintained by Martin Lund <martin.lund@keep-it-simple.com>.
|
||||
|
||||
tio 3.6 2024-07-19 tio(1)
|
||||
tio 3.9 2025-04-13 tio(1)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
project('tio', 'c',
|
||||
version : '3.6',
|
||||
license : [ 'GPL-2'],
|
||||
version : '3.9',
|
||||
license : 'GPL-2.0-or-later',
|
||||
meson_version : '>= 0.53.2',
|
||||
default_options : [ 'warning_level=2', 'buildtype=release', 'c_std=gnu99' ]
|
||||
)
|
||||
|
||||
# The tag date of the project_version(), update when the version bumps.
|
||||
version_date = '2024-07-19'
|
||||
version_date = '2025-04-13'
|
||||
|
||||
# Test for dynamic baudrate configuration interface
|
||||
compiler = meson.get_compiler('c')
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ _tio()
|
|||
--script-file \
|
||||
--script-run \
|
||||
--exec \
|
||||
--complete-profiles \
|
||||
-v --version \
|
||||
-h --help"
|
||||
|
||||
|
|
@ -85,11 +86,11 @@ _tio()
|
|||
return 0
|
||||
;;
|
||||
-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
|
||||
;;
|
||||
--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
|
||||
;;
|
||||
-c | --color)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <regex.h>
|
||||
#include <glib.h>
|
||||
#include "configfile.h"
|
||||
#include "timestamp.h"
|
||||
#include "print.h"
|
||||
#include "rs485.h"
|
||||
#include "misc.h"
|
||||
|
|
@ -148,7 +149,6 @@ static void config_get_bool(GKeyFile *key_file, gchar *group, gchar *key, bool *
|
|||
static void config_parse_keys(GKeyFile *key_file, char *group)
|
||||
{
|
||||
char *string = NULL;
|
||||
bool boolean = false;
|
||||
|
||||
config_get_string(key_file, group, "device", &config.device, NULL);
|
||||
config_get_integer(key_file, group, "baudrate", &option.baudrate, 0, INT_MAX);
|
||||
|
|
@ -204,21 +204,21 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
|
|||
g_free((void *)string);
|
||||
string = NULL;
|
||||
}
|
||||
config_get_bool(key_file, group, "timestamp", &boolean);
|
||||
if (boolean == true)
|
||||
config_get_bool(key_file, group, "timestamp", (bool*) &option.timestamp);
|
||||
if (option.timestamp != TIMESTAMP_NONE)
|
||||
{
|
||||
option.timestamp = TIMESTAMP_24HOUR;
|
||||
}
|
||||
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", NULL);
|
||||
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", "epoch", "epoch-usec", NULL);
|
||||
if (string != NULL)
|
||||
{
|
||||
option_parse_timestamp(string, &option.timestamp);
|
||||
g_free((void *)string);
|
||||
string = NULL;
|
||||
}
|
||||
}
|
||||
config_get_integer(key_file, group, "timestamp-timeout", &option.timestamp_timeout, 0, INT_MAX);
|
||||
config_get_bool(key_file, group, "log", &option.log);
|
||||
config_get_string(key_file, group, "log-file", &option.log_filename, NULL);
|
||||
config_get_string(key_file, group, "log-directory", &option.log_directory, NULL);
|
||||
config_get_bool(key_file, group, "log-append", &option.log_append);
|
||||
config_get_bool(key_file, group, "log-strip", &option.log_strip);
|
||||
config_get_string(key_file, group, "map", &string, NULL);
|
||||
|
|
@ -428,12 +428,13 @@ static char *match_and_replace(const char *str, const char *pattern, char *devic
|
|||
assert(pattern != NULL);
|
||||
assert(device != NULL);
|
||||
|
||||
char *string = strndup(device, PATH_MAX);
|
||||
char *string = calloc(PATH_MAX, 1);
|
||||
if (string == NULL)
|
||||
{
|
||||
tio_debug_printf("Failure allocating string memory\n");
|
||||
return NULL;
|
||||
}
|
||||
strncpy(string, device, PATH_MAX - 1);
|
||||
|
||||
/* Find matches of pattern in str. For each match, replace any '%mN' in the
|
||||
* copy of the device string with the corresponding match subexpression and
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define __STDC_WANT_LIB_EXT2__ 1 // To access vasprintf
|
||||
|
||||
#define _GNU_SOURCE // To access vasprintf
|
||||
#include <stdio.h>
|
||||
#include "print.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define __STDC_WANT_LIB_EXT2__ 1 // To access vasprintf
|
||||
|
||||
#define _GNU_SOURCE // To access vasprintf
|
||||
#include <sys/time.h>
|
||||
#include <libgen.h>
|
||||
#include <errno.h>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "version.h"
|
||||
#include "config.h"
|
||||
#include "options.h"
|
||||
#include "configfile.h"
|
||||
|
|
@ -60,6 +61,10 @@ int main(int argc, char *argv[])
|
|||
/* Configure tty device */
|
||||
tty_configure();
|
||||
|
||||
/* Disable line buffering in stdout. This is necessary if we
|
||||
* want things like local echo to work correctly. */
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
/* Configure input terminal */
|
||||
if (isatty(fileno(stdin)))
|
||||
{
|
||||
|
|
@ -101,7 +106,7 @@ int main(int argc, char *argv[])
|
|||
error_enter_session_mode();
|
||||
|
||||
/* Print launch hints */
|
||||
tio_printf("tio v%s", VERSION);
|
||||
tio_printf("tio %s", VERSION);
|
||||
if (interactive_mode)
|
||||
{
|
||||
tio_printf("Press ctrl-%c q to quit", option.prefix_key);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
# Generate version header
|
||||
version_h = vcs_tag(command : ['git', 'describe', '--tags', '--always', '--dirty'],
|
||||
input : 'version.h.in',
|
||||
output :'version.h',
|
||||
replace_string:'@VERSION@')
|
||||
|
||||
config_h = configuration_data()
|
||||
config_h.set_quoted('VERSION', meson.project_version())
|
||||
config_h.set('BAUDRATE_CASES', baudrate_cases)
|
||||
configure_file(output: 'config.h', configuration: config_h)
|
||||
|
||||
|
|
@ -21,7 +26,8 @@ tio_sources = [
|
|||
'xymodem.c',
|
||||
'script.c',
|
||||
'fs.c',
|
||||
'readline.c'
|
||||
'readline.c',
|
||||
version_h
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -41,7 +47,13 @@ tio_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
|
||||
tio_c_args += '-DHAVE_TERMIOS2'
|
||||
|
|
|
|||
|
|
@ -121,10 +121,9 @@ unsigned long djb2_hash(const unsigned char *str)
|
|||
}
|
||||
|
||||
// Function to encode a number to base62
|
||||
char *base62_encode(unsigned long num)
|
||||
void *base62_encode(unsigned long num, char *output)
|
||||
{
|
||||
const char base62_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
char *output = (char *) malloc(5); // 4 characters + null terminator
|
||||
if (output == NULL)
|
||||
{
|
||||
tio_error_print("Memory allocation failed");
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ void delay(long ms);
|
|||
int ctrl_key_code(unsigned char key);
|
||||
bool regex_match(const char *string, const char *pattern);
|
||||
unsigned long djb2_hash(const unsigned char *str);
|
||||
char *base62_encode(unsigned long num);
|
||||
void *base62_encode(unsigned long num, char *output);
|
||||
int read_poll(int fd, void *data, size_t len, int timeout);
|
||||
double get_current_time(void);
|
||||
bool match_patterns(const char *string, const char *patterns);
|
||||
|
|
|
|||
|
|
@ -19,10 +19,13 @@
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE // To access vasprintf
|
||||
|
||||
#include <assert.h>
|
||||
#include <regex.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include "version.h"
|
||||
#include "config.h"
|
||||
#include "misc.h"
|
||||
#include "print.h"
|
||||
|
|
@ -113,6 +116,7 @@ struct option_t option =
|
|||
.map_ign_cr = false,
|
||||
.map_i_ff_escc = false,
|
||||
.map_i_nl_crnl = false,
|
||||
.map_i_cr_crnl = false,
|
||||
.map_o_cr_nl = false,
|
||||
.map_o_nl_crnl = false,
|
||||
.map_o_del_bs = false,
|
||||
|
|
@ -167,6 +171,7 @@ void option_print_help(char *argv[])
|
|||
printf(" --script-file <filename> Run script from file\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(" --complete-profiles Prints profiles (for shell completion)\n");
|
||||
printf(" -v, --version Display version\n");
|
||||
printf(" -h, --help Display help\n");
|
||||
printf("\n");
|
||||
|
|
@ -392,6 +397,14 @@ const char* option_timestamp_format_to_string(timestamp_t timestamp)
|
|||
return "iso8601";
|
||||
break;
|
||||
|
||||
case TIMESTAMP_EPOCH:
|
||||
return "epoch";
|
||||
break;
|
||||
|
||||
case TIMESTAMP_EPOCH_USEC:
|
||||
return "epoch-usec";
|
||||
break;
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
break;
|
||||
|
|
@ -418,6 +431,14 @@ void option_parse_timestamp(const char *arg, timestamp_t *timestamp)
|
|||
{
|
||||
*timestamp = TIMESTAMP_ISO8601;
|
||||
}
|
||||
else if (strcmp(arg, "epoch") == 0)
|
||||
{
|
||||
*timestamp = TIMESTAMP_EPOCH;
|
||||
}
|
||||
else if (strcmp(arg, "epoch-usec") == 0)
|
||||
{
|
||||
*timestamp = TIMESTAMP_EPOCH_USEC;
|
||||
}
|
||||
else
|
||||
{
|
||||
tio_error_print("Invalid timestamp '%s'", arg);
|
||||
|
|
@ -759,6 +780,10 @@ void option_parse_mappings(const char *map)
|
|||
{
|
||||
option.map_i_nl_crnl = true;
|
||||
}
|
||||
else if (strcmp(token,"ICRCRNL") == 0)
|
||||
{
|
||||
option.map_i_cr_crnl = true;
|
||||
}
|
||||
else if (strcmp(token, "ONLCRNL") == 0)
|
||||
{
|
||||
option.map_o_nl_crnl = true;
|
||||
|
|
@ -987,7 +1012,10 @@ void options_parse(int argc, char *argv[])
|
|||
break;
|
||||
|
||||
case 't':
|
||||
if (option.timestamp == TIMESTAMP_NONE)
|
||||
{
|
||||
option.timestamp = TIMESTAMP_24HOUR;
|
||||
}
|
||||
break;
|
||||
|
||||
case OPT_TIMESTAMP_FORMAT:
|
||||
|
|
@ -1077,7 +1105,7 @@ void options_parse(int argc, char *argv[])
|
|||
break;
|
||||
|
||||
case 'v':
|
||||
printf("tio v%s\n", VERSION);
|
||||
printf("tio %s\n", VERSION);
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ struct option_t
|
|||
bool map_ign_cr;
|
||||
bool map_i_ff_escc;
|
||||
bool map_i_nl_crnl;
|
||||
bool map_i_cr_crnl;
|
||||
bool map_o_cr_nl;
|
||||
bool map_o_nl_crnl;
|
||||
bool map_o_del_bs;
|
||||
|
|
|
|||
470
src/script.c
470
src/script.c
|
|
@ -20,7 +20,6 @@
|
|||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <regex.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -29,6 +28,7 @@
|
|||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <ctype.h>
|
||||
#include "misc.h"
|
||||
#include "print.h"
|
||||
#include "options.h"
|
||||
|
|
@ -37,27 +37,94 @@
|
|||
#include "log.h"
|
||||
#include "script.h"
|
||||
#include "fs.h"
|
||||
#include "timestamp.h"
|
||||
#include "termios.h"
|
||||
|
||||
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
|
||||
#define READ_LINE_SIZE 4096 // read_line buffer length
|
||||
|
||||
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[] =
|
||||
"function set(arg)\n"
|
||||
"tio.set = function(arg)\n"
|
||||
" local dtr = arg.DTR or -1\n"
|
||||
" local rts = arg.RTS or -1\n"
|
||||
" local cts = arg.CTS or -1\n"
|
||||
" local dsr = arg.DSR or -1\n"
|
||||
" local cd = arg.CD or -1\n"
|
||||
" local ri = arg.RI or -1\n"
|
||||
" line_set(dtr, rts, cts, dsr, cd, ri)\n"
|
||||
"end\n";
|
||||
" tio.line_set(dtr, rts, cts, dsr, cd, ri)\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 int sleep_(lua_State *L)
|
||||
static bool alwaysecho(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);
|
||||
|
||||
|
|
@ -73,8 +140,8 @@ static int sleep_(lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// lua: msleep(miliseconds)
|
||||
static int msleep(lua_State *L)
|
||||
// lua: tio.msleep(miliseconds)
|
||||
static int api_msleep(lua_State *L)
|
||||
{
|
||||
long mseconds = lua_tointeger(L, 1);
|
||||
long useconds = mseconds * 1000;
|
||||
|
|
@ -90,7 +157,7 @@ static int msleep(lua_State *L)
|
|||
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)
|
||||
{
|
||||
tty_line_config_t line_config[6] = { };
|
||||
|
|
@ -144,11 +211,11 @@ static int line_set(lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// lua: modem_send(file, protocol)
|
||||
static int modem_send(lua_State *L)
|
||||
// lua: tio.send(file, protocol)
|
||||
static int api_send(lua_State *L)
|
||||
{
|
||||
const char *file = lua_tostring(L, 1);
|
||||
int protocol = lua_tointeger(L, 2);
|
||||
const char *file = luaL_checkstring(L, 1);
|
||||
int protocol = luaL_checkinteger(L, 2);
|
||||
int ret;
|
||||
|
||||
if (file == NULL)
|
||||
|
|
@ -180,210 +247,118 @@ static int modem_send(lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// lua: send(string)
|
||||
static int send(lua_State *L)
|
||||
// lua: tio.write(string)
|
||||
static int api_write(lua_State *L)
|
||||
{
|
||||
const char *string = lua_tostring(L, 1);
|
||||
int ret;
|
||||
size_t len = 0;
|
||||
const char *string = luaL_checklstring(L, 1, &len);
|
||||
ssize_t ret;
|
||||
int attempts = 100;
|
||||
|
||||
if (string == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
do {
|
||||
ret = write(device_fd, string, len);
|
||||
if (ret < 0)
|
||||
return luaL_error(L, "%s", strerror(errno));
|
||||
|
||||
ret = write(device_fd, string, strlen(string));
|
||||
len -= ret;
|
||||
string += ret;
|
||||
} while (len > 0 && --attempts);
|
||||
|
||||
lua_pushnumber(L, ret);
|
||||
if (len > 0)
|
||||
return luaL_error(L, "partial write");
|
||||
|
||||
fsync(device_fd); // flush these characters now
|
||||
tcdrain(device_fd); //ensure we flushed characters to our device
|
||||
|
||||
lua_getglobal(L, "tio");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Function to add a character to the circular expect buffer
|
||||
static void expect_buffer_add(char c)
|
||||
// lua: tio.read(size, timeout)
|
||||
static int api_read(lua_State *L)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// lua: ret,string = read_string(size, timeout)
|
||||
static int read_string(lua_State *L)
|
||||
{
|
||||
int size = lua_tointeger(L, 1);
|
||||
int size = luaL_checkinteger(L, 1);
|
||||
int timeout = lua_tointeger(L, 2);
|
||||
int ret = 0;
|
||||
|
||||
char *buffer = malloc(size);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
ret = -1; // Error
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (timeout == 0)
|
||||
{
|
||||
timeout = -1; // Wait forever
|
||||
}
|
||||
|
||||
ssize_t bytes_read = read_poll(device_fd, buffer, size, timeout);
|
||||
if (bytes_read < 0)
|
||||
{
|
||||
ret = -1; // Error
|
||||
goto error;
|
||||
}
|
||||
else if (bytes_read == 0)
|
||||
{
|
||||
ret = 0; // Timeout
|
||||
goto error;
|
||||
}
|
||||
luaL_Buffer buffer;
|
||||
luaL_buffinit(L, &buffer);
|
||||
|
||||
for (ssize_t i=0; i<bytes_read; i++)
|
||||
#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)
|
||||
{
|
||||
putchar(buffer[i]);
|
||||
|
||||
if (option.log)
|
||||
{
|
||||
log_putc(buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ret = bytes_read;
|
||||
|
||||
error:
|
||||
lua_pushnumber(L, ret);
|
||||
if (buffer != NULL)
|
||||
{
|
||||
lua_pushstring(L, buffer);
|
||||
free(buffer);
|
||||
}
|
||||
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;
|
||||
}
|
||||
// On timeout return nil instead of an empty string
|
||||
lua_pop(L, 1);
|
||||
lua_pushnil(L);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Timeout or error
|
||||
break;
|
||||
}
|
||||
maybe_echo(L);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
regfree(®ex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
error:
|
||||
lua_pushnumber(L, ret);
|
||||
lua_pushstring(L, match_string);
|
||||
// lua: string = tio.readline(timeout)
|
||||
static int api_readline(lua_State *L) {
|
||||
int timeout = lua_tointeger(L, 1); //ms
|
||||
luaL_Buffer b;
|
||||
char ch;
|
||||
|
||||
if (timeout == 0)
|
||||
{
|
||||
timeout = -1; // Wait forever
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
luaL_pushresult(&b);
|
||||
maybe_echo(L);
|
||||
lua_pushnil(L);
|
||||
lua_insert(L, -2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (ch == '\n')
|
||||
{
|
||||
luaL_pushresult(&b);
|
||||
maybe_echo(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
luaL_addchar(&b, ch);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// lua: table = tio.ttysearch()
|
||||
static int api_ttysearch(lua_State *L)
|
||||
{
|
||||
UNUSED(L);
|
||||
GList *iter;
|
||||
|
|
@ -449,65 +424,7 @@ static void script_buffer_run(lua_State *L, const char *script_buffer)
|
|||
}
|
||||
}
|
||||
|
||||
static const struct luaL_Reg tio_lib[] =
|
||||
{
|
||||
{ "sleep", sleep_},
|
||||
{ "msleep", msleep},
|
||||
{ "line_set", line_set},
|
||||
{ "modem_send", modem_send},
|
||||
{ "send", send},
|
||||
{ "read", read_string},
|
||||
{ "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)
|
||||
static void script_file_run(lua_State *L, const char *filename)
|
||||
{
|
||||
if (strlen(filename) == 0)
|
||||
{
|
||||
|
|
@ -523,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_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, "high", 1);
|
||||
|
|
@ -539,6 +482,14 @@ void script_set_globals(lua_State *L)
|
|||
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)
|
||||
{
|
||||
lua_State *L;
|
||||
|
|
@ -548,8 +499,15 @@ void script_run(int fd, const char *script_filename)
|
|||
L = luaL_newstate();
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Bind tio functions
|
||||
lua_register_tio(L);
|
||||
#if LUA_VERSION_NUM >= 502
|
||||
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
|
||||
script_set_globals(L);
|
||||
|
|
|
|||
|
|
@ -226,7 +226,11 @@ void socket_configure(void)
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_NOSIGPIPE, &optval, sizeof(optval)))
|
||||
#else
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)))
|
||||
#endif
|
||||
{
|
||||
tio_error_printf("Failed to set socket options (%s)", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
|
|
@ -270,7 +274,12 @@ void socket_write(char input_char)
|
|||
{
|
||||
if (clientfds[i] != -1)
|
||||
{
|
||||
|
||||
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
|
||||
if (send(clientfds[i], &input_char, 1, 0) <= 0)
|
||||
#else
|
||||
if (send(clientfds[i], &input_char, 1, MSG_NOSIGNAL) <= 0)
|
||||
#endif
|
||||
{
|
||||
tio_error_printf_silent("Failed to write to socket (%s)", strerror(errno));
|
||||
close(clientfds[i]);
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@
|
|||
#include "options.h"
|
||||
#include "timestamp.h"
|
||||
|
||||
#define TIME_STRING_SIZE_MAX 24
|
||||
|
||||
char *timestamp_current_time(void)
|
||||
{
|
||||
static char time_string[TIME_STRING_SIZE_MAX];
|
||||
|
|
@ -76,16 +74,29 @@ char *timestamp_current_time(void)
|
|||
tm = localtime(&tv.tv_sec);
|
||||
len = strftime(time_string, sizeof(time_string), "%Y-%m-%dT%H:%M:%S", tm);
|
||||
break;
|
||||
case TIMESTAMP_EPOCH:
|
||||
case TIMESTAMP_EPOCH_USEC:
|
||||
// "N.sss" (seconds since Unix epoch, 1970-01-01 00:00:00Z)
|
||||
tv = tv_now;
|
||||
tm = localtime(&tv.tv_sec);
|
||||
len = strftime(time_string, sizeof(time_string), "%s", tm);
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Append milliseconds to all timestamps
|
||||
// Append millis-/microseconds to all timestamps
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
// Save previous time value for next run
|
||||
tv_previous = tv_now;
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,12 @@ typedef enum
|
|||
TIMESTAMP_24HOUR_START,
|
||||
TIMESTAMP_24HOUR_DELTA,
|
||||
TIMESTAMP_ISO8601,
|
||||
TIMESTAMP_EPOCH,
|
||||
TIMESTAMP_EPOCH_USEC,
|
||||
TIMESTAMP_END,
|
||||
} timestamp_t;
|
||||
|
||||
#define TIME_STRING_SIZE_MAX 24
|
||||
|
||||
char *timestamp_current_time(void);
|
||||
|
||||
|
|
|
|||
330
src/tty.c
330
src/tty.c
|
|
@ -22,6 +22,16 @@
|
|||
#if defined(__linux__)
|
||||
#include <linux/serial.h>
|
||||
#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 "config.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -622,16 +632,18 @@ void tty_output_mode_set(output_mode_t mode)
|
|||
static void mappings_print(void)
|
||||
{
|
||||
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_o_del_bs || option.map_o_nl_crnl || option.map_o_ltu ||
|
||||
option.map_o_nulbrk || option.map_i_msb2lsb || option.map_o_ign_cr)
|
||||
option.map_i_nl_cr || option.map_i_nl_crnl || option.map_i_cr_crnl ||
|
||||
option.map_o_cr_nl || option.map_o_del_bs || option.map_o_nl_crnl ||
|
||||
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_ign_cr ? " IGNCR" : "",
|
||||
option.map_i_ff_escc ? " IFFESCC" : "",
|
||||
option.map_i_nl_cr ? " INLCR" : "",
|
||||
option.map_i_nl_crnl ? " INLCRNL" : "",
|
||||
option.map_i_cr_crnl ? " ICRCRNL" : "",
|
||||
option.map_i_msb2lsb ? " IMSB2LSB" : "",
|
||||
option.map_o_cr_nl ? " OCRNL" : "",
|
||||
option.map_o_del_bs ? " ODELBS" : "",
|
||||
|
|
@ -782,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");
|
||||
break;
|
||||
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;
|
||||
tio_printf("IMSB2LSB is %s", option.map_i_msb2lsb ? "set" : "unset");
|
||||
break;
|
||||
case KEY_6:
|
||||
case KEY_7:
|
||||
option.map_o_cr_nl = !option.map_o_cr_nl;
|
||||
tio_printf("OCRNL is %s", option.map_o_cr_nl ? "set" : "unset");
|
||||
break;
|
||||
case KEY_7:
|
||||
case KEY_8:
|
||||
option.map_o_del_bs = !option.map_o_del_bs;
|
||||
tio_printf("ODELBS is %s", option.map_o_del_bs ? "set" : "unset");
|
||||
break;
|
||||
case KEY_8:
|
||||
case KEY_9:
|
||||
option.map_o_nl_crnl = !option.map_o_nl_crnl;
|
||||
tio_printf("ONLCRNL is %s", option.map_o_nl_crnl ? "set" : "unset");
|
||||
break;
|
||||
case KEY_9:
|
||||
case KEY_A:
|
||||
option.map_o_ltu = !option.map_o_ltu;
|
||||
tio_printf("OLTU is %s", option.map_o_ltu ? "set" : "unset");
|
||||
break;
|
||||
case KEY_A:
|
||||
case KEY_B:
|
||||
option.map_o_nulbrk = !option.map_o_nulbrk;
|
||||
tio_printf("ONULBRK is %s", option.map_o_nulbrk ? "set" : "unset");
|
||||
break;
|
||||
case KEY_B:
|
||||
case KEY_C:
|
||||
option.map_o_ign_cr = !option.map_o_ign_cr;
|
||||
tio_printf("OIGNCR is %s", option.map_o_ign_cr ? "set" : "unset");
|
||||
break;
|
||||
|
|
@ -1006,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",
|
||||
option.map_i_nl_cr ? "Unset" : "Set");
|
||||
tio_printf(" (4) INLCRNL: %s mapping NL to CR-NL on input",
|
||||
option.map_i_nl_cr ? "Unset" : "Set");
|
||||
tio_printf(" (5) IMSB2LSB: %s mapping MSB bit order to LSB on input",
|
||||
option.map_i_nl_crnl ? "Unset" : "Set");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
|
||||
// Process next input character as sub command
|
||||
|
|
@ -1079,6 +1097,12 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
|||
case TIMESTAMP_ISO8601:
|
||||
tio_printf("Switched timestamp mode to iso8601");
|
||||
break;
|
||||
case TIMESTAMP_EPOCH:
|
||||
tio_printf("Switched timestamp mode to epoch");
|
||||
break;
|
||||
case TIMESTAMP_EPOCH_USEC:
|
||||
tio_printf("Switched timestamp mode to epoch with subdivision in microseconds");
|
||||
break;
|
||||
case TIMESTAMP_END:
|
||||
option.timestamp = TIMESTAMP_NONE;
|
||||
tio_printf("Switched timestamp mode off");
|
||||
|
|
@ -1087,7 +1111,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
|||
break;
|
||||
|
||||
case KEY_V:
|
||||
tio_printf("tio v%s", VERSION);
|
||||
tio_printf("tio %s", VERSION);
|
||||
break;
|
||||
|
||||
case KEY_X:
|
||||
|
|
@ -1180,10 +1204,6 @@ void stdout_configure(void)
|
|||
{
|
||||
int status;
|
||||
|
||||
/* Disable line buffering in stdout. This is necessary if we
|
||||
* want things like local echo to work correctly. */
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
/* Save current stdout settings */
|
||||
if (tcgetattr(STDOUT_FILENO, &stdout_old) < 0)
|
||||
{
|
||||
|
|
@ -1611,6 +1631,7 @@ const char* get_serial_port_type(const char* port_name)
|
|||
|
||||
const char* get_serial_port_type(const char* port_name)
|
||||
{
|
||||
(void)port_name;
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
@ -1732,7 +1753,8 @@ GList *tty_search_for_serial_devices(void)
|
|||
|
||||
// Hash remaining string to get unique topology ID
|
||||
unsigned long hash = djb2_hash((const unsigned char *)devices_path);
|
||||
char *tid = base62_encode(hash);
|
||||
char tid[5];
|
||||
base62_encode(hash, tid);
|
||||
free(devices_path);
|
||||
|
||||
// Construct the path to the device's driver symlink
|
||||
|
|
@ -1763,18 +1785,22 @@ GList *tty_search_for_serial_devices(void)
|
|||
creation_time = fs_get_creation_time(path);
|
||||
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] = {};
|
||||
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);
|
||||
}
|
||||
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/../../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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
|
@ -1831,6 +1857,226 @@ GList *tty_search_for_serial_devices(void)
|
|||
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
|
||||
|
||||
GList *tty_search_for_serial_devices(void)
|
||||
|
|
@ -2111,8 +2357,21 @@ void tty_wait_for_device(void)
|
|||
}
|
||||
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));
|
||||
exit(EXIT_FAILURE);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2582,6 +2841,15 @@ int tty_connect(void)
|
|||
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))
|
||||
{
|
||||
printchar('\e');
|
||||
|
|
@ -2669,7 +2937,6 @@ int tty_connect(void)
|
|||
// Write current line to tty device
|
||||
char *rl_line = readline_get();
|
||||
tty_write(device_fd, rl_line, strlen(rl_line));
|
||||
tty_sync(device_fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -2743,4 +3010,3 @@ error_read:
|
|||
error_open:
|
||||
return TIO_ERROR;
|
||||
}
|
||||
|
||||
|
|
|
|||
3
src/version.h.in
Normal file
3
src/version.h.in
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
#define VERSION "@VERSION@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue