mirror of
https://github.com/tio/tio.git
synced 2026-05-01 14:57:59 +02:00
Compare commits
146 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 | ||
|
|
475bc29cc8 | ||
|
|
c4f5269c83 | ||
|
|
13ad59ac12 | ||
|
|
2259244eb2 | ||
|
|
725423c50c | ||
|
|
14963032c3 | ||
|
|
e1fe232254 | ||
|
|
9cafcbcab5 | ||
|
|
f4076258f1 | ||
|
|
866b5bcb30 | ||
|
|
289bbfd393 | ||
|
|
68a64ac554 | ||
|
|
0a95da00f1 | ||
|
|
32d97683f8 | ||
|
|
c3654486c7 | ||
|
|
b5184012c4 | ||
|
|
fc0c6f61d2 | ||
|
|
53bb2a6ad1 | ||
|
|
da9f7a6540 | ||
|
|
2a1dae6336 | ||
|
|
ada2db0739 | ||
|
|
14ee38a0d9 | ||
|
|
2c700a90b0 | ||
|
|
da04c2c444 | ||
|
|
5f70b75e90 | ||
|
|
02cac07a77 | ||
|
|
a3a6b5127f | ||
|
|
ef6fa8030e | ||
|
|
561376696b | ||
|
|
d34fa1c1ad | ||
|
|
9022f51ea5 | ||
|
|
4723cc3f4e | ||
|
|
8c471105fe | ||
|
|
b9902bbd78 | ||
|
|
c5afd86b9a | ||
|
|
b756d2e1f1 | ||
|
|
5c7d81b900 | ||
|
|
7e9574f98d | ||
|
|
053ae53f19 | ||
|
|
8f77ad5830 | ||
|
|
be4fc0908f | ||
|
|
6ec2ac19d0 | ||
|
|
bed34a0b9a | ||
|
|
134038c1ce | ||
|
|
003b2e37d4 | ||
|
|
8014ef68c0 | ||
|
|
563c4fa6ea | ||
|
|
d1d6b45e8e | ||
|
|
f148a1413c | ||
|
|
bb3636e2d5 | ||
|
|
133789517a | ||
|
|
7e0bd980f2 | ||
|
|
883acbaa4b | ||
|
|
d10e762a7d | ||
|
|
94e40d82f3 | ||
|
|
9315cf6a55 | ||
|
|
eb9726bbcc | ||
|
|
4014fc4b3e | ||
|
|
ccc01433b7 | ||
|
|
ee3687430b | ||
|
|
6f6038ebcd | ||
|
|
0921796054 | ||
|
|
dba4690a88 | ||
|
|
b3aac7b182 | ||
|
|
5b5248929e | ||
|
|
8f45d6f688 | ||
|
|
37f8b4fd1b | ||
|
|
5f5a8a9cdd | ||
|
|
3f616a47c8 | ||
|
|
694524cb6f | ||
|
|
747ac62733 | ||
|
|
c76a4d0172 | ||
|
|
d2dd9f5a5b | ||
|
|
ae9c8edbca | ||
|
|
f71ffeabb7 | ||
|
|
6ebd50ab85 | ||
|
|
241ff93bf4 |
46 changed files with 2537 additions and 855 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
|
||||||
16
.github/workflows/codeql.yml
vendored
16
.github/workflows/codeql.yml
vendored
|
|
@ -12,8 +12,8 @@
|
||||||
name: "CodeQL"
|
name: "CodeQL"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# push:
|
push:
|
||||||
# branches: [ "main", "master" ]
|
branches: [ "main", "master" ]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * *'
|
- cron: '0 0 * * *'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
@ -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,14 +107,14 @@ 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}}"
|
||||||
|
|
||||||
- name: Upload CodeQL results as an artifact
|
- name: Upload CodeQL results as an artifact
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: codeql-results
|
name: codeql-results
|
||||||
path: ${{ steps.step1.outputs.sarif-output }}
|
path: ${{ steps.step1.outputs.sarif-output }}
|
||||||
|
|
|
||||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
name: macOS build
|
name: MacOS build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
|
||||||
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
.typos.toml
Normal file
2
.typos.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[default]
|
||||||
|
extend-ignore-words-re = ["tio"]
|
||||||
13
AUTHORS
13
AUTHORS
|
|
@ -53,5 +53,18 @@ Sebastian <sebastian.krahmer@gmail.com>
|
||||||
Mingjie Shen <shen497@purdue.edu>
|
Mingjie Shen <shen497@purdue.edu>
|
||||||
Brian <bayuan@purdue.edu>
|
Brian <bayuan@purdue.edu>
|
||||||
Davis C <davisclaib@gmail.com>
|
Davis C <davisclaib@gmail.com>
|
||||||
|
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.
|
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
|
This program is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU General Public License
|
modify it under the terms of the GNU General Public License
|
||||||
|
|
|
||||||
278
NEWS
278
NEWS
|
|
@ -1,5 +1,237 @@
|
||||||
|
=== tio v3.9 (2025-04-13) ===
|
||||||
|
|
||||||
=== tio v3.2 ===
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Changes since tio v3.5 (2024-06-29):
|
||||||
|
|
||||||
|
* Add configuration file include directive
|
||||||
|
|
||||||
|
To include the contents of another configuration file simply do e.g.:
|
||||||
|
|
||||||
|
[include raspberrypi.conf]
|
||||||
|
|
||||||
|
Also, included file can include other files which can include other
|
||||||
|
files etc.
|
||||||
|
|
||||||
|
This feature is useful for managing many configuration files and sharing
|
||||||
|
configuration files with others.
|
||||||
|
|
||||||
|
* Mention how to list key commands in help output
|
||||||
|
|
||||||
|
* Fix hex output mode when using normal input mode
|
||||||
|
|
||||||
|
In this combination of modes the input character was not forwarded to
|
||||||
|
the tty device. This fix makes sure it is forwarded.
|
||||||
|
|
||||||
|
* Fix uptime on MacOS
|
||||||
|
|
||||||
|
On MacOS the birth time is apparently not available so we use
|
||||||
|
modification time instead.
|
||||||
|
|
||||||
|
* Improve warning upon failing connect
|
||||||
|
|
||||||
|
Add device path to warning when connect fails.
|
||||||
|
|
||||||
|
* Fix crashy search_reset() on macOS
|
||||||
|
|
||||||
|
* Clean up shadow variable
|
||||||
|
|
||||||
|
* Clean up readline code
|
||||||
|
|
||||||
|
* Improve listing of long device names
|
||||||
|
|
||||||
|
* Fix listing of serial devices on macOS
|
||||||
|
|
||||||
|
Heinrich Schuchardt:
|
||||||
|
|
||||||
|
* Print correct 'Done' timestamp for X- and Y-modem transfers
|
||||||
|
|
||||||
|
Call tio_printf() after completing xymodem_send().
|
||||||
|
|
||||||
|
Robert Lipe:
|
||||||
|
|
||||||
|
* Recompute listing_device_name_length_max for MacOS case, too.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Changes since tio v3.4:
|
||||||
|
|
||||||
|
* Clarify input and output direction of map flags
|
||||||
|
|
||||||
|
* Rename mapping flag MSB2LSB to IMSB2LSB
|
||||||
|
|
||||||
|
This is the correct naming since we are changing the input bit order on
|
||||||
|
input from the serial device.
|
||||||
|
|
||||||
|
* Add OIGNCR mapping flag
|
||||||
|
|
||||||
|
Ignores CR on output to serial device.
|
||||||
|
|
||||||
|
* Fix line input mode ignoring characters ABCD
|
||||||
|
|
||||||
|
* Fix tainted print
|
||||||
|
|
||||||
|
Jakob Haufe:
|
||||||
|
|
||||||
|
* Fix typos
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Changes since tio v3.3:
|
||||||
|
|
||||||
|
* Update configuration output
|
||||||
|
|
||||||
|
* Clean up script run interaction text
|
||||||
|
|
||||||
|
* Fix unbounded writes
|
||||||
|
|
||||||
|
* Add history and editing feature to line input mode
|
||||||
|
|
||||||
|
Use up and down arrow keys to navigate history.
|
||||||
|
|
||||||
|
Use left and right arrow keys to move cursor back and forth.
|
||||||
|
|
||||||
|
We try mimic the behaviour of GNU readline which we can not use because
|
||||||
|
we also need to react to key commands.
|
||||||
|
|
||||||
|
* Reuse socket address
|
||||||
|
|
||||||
|
To avoid having to wait for socket timeout when restarting server.
|
||||||
|
|
||||||
|
* Fix line input mode
|
||||||
|
|
||||||
|
Fix so that ABCD are no longer ignored.
|
||||||
|
|
||||||
|
* Make sure ICRNL, IGNCR, INLCR take effect
|
||||||
|
|
||||||
|
* Include correct header for poll()
|
||||||
|
|
||||||
|
* Add group write permission to xymodem received file
|
||||||
|
|
||||||
|
* Fix missing open() flags in xymodem_receive()
|
||||||
|
|
||||||
|
Vyacheslav Patkov:
|
||||||
|
|
||||||
|
* Show current mappings in the configuration printout
|
||||||
|
|
||||||
|
* Use "ctrl-t m" to change mappings interactively
|
||||||
|
|
||||||
|
* Prompt for Lua script or shell command in interactive session
|
||||||
|
|
||||||
|
Eliot Alan Foss:
|
||||||
|
|
||||||
|
* Added support to receive XMODEM-CRC files from the connected serial port.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Changes since tio v3.2:
|
||||||
|
|
||||||
|
* Force destructive backspace when using local echo
|
||||||
|
|
||||||
|
Only takes effect in normal output mode.
|
||||||
|
|
||||||
|
* Fix local-echo in configuration file
|
||||||
|
|
||||||
|
* Clean up includes
|
||||||
|
|
||||||
|
* Force socket write operation to ignore any signals
|
||||||
|
|
||||||
|
* Man page cleanup
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,7 +255,7 @@ Changes since tio v3.1:
|
||||||
|
|
||||||
* Fix line ending in --list output
|
* Fix line ending in --list output
|
||||||
|
|
||||||
* Print location of configuratin file in --list output
|
* Print location of configuration file in --list output
|
||||||
|
|
||||||
* Fix alignment of profile listing
|
* Fix alignment of profile listing
|
||||||
|
|
||||||
|
|
@ -62,7 +294,7 @@ Changes since tio v3.0:
|
||||||
After including the use of glib we might as well replace inih
|
After including the use of glib we might as well replace inih
|
||||||
with the glib key file parser.
|
with the glib key file parser.
|
||||||
|
|
||||||
All configuraiton file parsing has been reworked and also the options
|
All configuration file parsing has been reworked and also the options
|
||||||
parsing has been cleaned up, resulting in better and stricter
|
parsing has been cleaned up, resulting in better and stricter
|
||||||
configuration file and option value checks.
|
configuration file and option value checks.
|
||||||
|
|
||||||
|
|
@ -116,9 +348,9 @@ Rui Chen:
|
||||||
|
|
||||||
* fix: add build patch for `FNM_EXTMATCH`
|
* fix: add build patch for `FNM_EXTMATCH`
|
||||||
|
|
||||||
* feat: add macos workflow
|
* feat: add macOS workflow
|
||||||
|
|
||||||
* fix: add macos build patch for `fs_get_creation_time`
|
* fix: add macOS build patch for `fs_get_creation_time`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -343,11 +575,11 @@ Changes since tio v2.7:
|
||||||
|
|
||||||
* Add lua modem_send(file,protocol)
|
* Add lua modem_send(file,protocol)
|
||||||
|
|
||||||
* Fix xymodem error print outs
|
* Fix xymodem error messages
|
||||||
|
|
||||||
* Rework x/y-modem transfer command
|
* Rework x/y-modem transfer command
|
||||||
|
|
||||||
Remove ctrl-t X optin and instead introduce submenu to ctrl-t x option
|
Remove ctrl-t X option and instead introduce submenu to ctrl-t x option
|
||||||
for picking which xmodem protocol to use.
|
for picking which xmodem protocol to use.
|
||||||
|
|
||||||
* Update README
|
* Update README
|
||||||
|
|
@ -356,22 +588,22 @@ Changes since tio v2.7:
|
||||||
|
|
||||||
* Add independent input and output mode
|
* Add independent input and output mode
|
||||||
|
|
||||||
Replaces -x, --hexadecimal option with --intput-mode and --output-mode
|
Replaces -x, --hexadecimal option with --input-mode and --output-mode
|
||||||
so it is possible to select hex or normal mode for both input and output
|
so it is possible to select hex or normal mode for both input and output
|
||||||
independently.
|
independently.
|
||||||
|
|
||||||
To obtain same behaviour as -x, --hexadecimal use the following
|
To obtain same behavior as -x, --hexadecimal use the following
|
||||||
configuration:
|
configuration:
|
||||||
|
|
||||||
input-mode = hex
|
input-mode = hex
|
||||||
output-mode = hex
|
output-mode = hex
|
||||||
|
|
||||||
* Fix file descriptor handling on MacOS
|
* Fix file descriptor handling on macOS
|
||||||
|
|
||||||
* Add tty line configuration script API
|
* Add tty line configuration script API
|
||||||
|
|
||||||
On some platforms calling high()/low() to switch line states result in
|
On some platforms calling high()/low() to switch line states result in
|
||||||
costly system calls whick makes it impossible to swith two or more tty
|
costly system calls which makes it impossible to switch two or more tty
|
||||||
lines simultaneously.
|
lines simultaneously.
|
||||||
|
|
||||||
To help solve this timing issue we introduce a tty line state
|
To help solve this timing issue we introduce a tty line state
|
||||||
|
|
@ -407,7 +639,7 @@ Changes since tio v2.7:
|
||||||
lines. Script is activated automatically on connect or manually via in
|
lines. Script is activated automatically on connect or manually via in
|
||||||
session key command.
|
session key command.
|
||||||
|
|
||||||
The Lua scripting feature opens up for many posibilities in the future
|
The Lua scripting feature opens up for many possibilities in the future
|
||||||
such as adding expect like functionality to easily and programatically
|
such as adding expect like functionality to easily and programatically
|
||||||
interact with the connected device.
|
interact with the connected device.
|
||||||
|
|
||||||
|
|
@ -504,7 +736,7 @@ Changes since tio v2.5:
|
||||||
|
|
||||||
Add --log-append option which makes tio append to any existing log file.
|
Add --log-append option which makes tio append to any existing log file.
|
||||||
|
|
||||||
This also changes the default behaviour of tio from appending to
|
This also changes the default behavior of tio from appending to
|
||||||
overwriting any existing log file. Now you have to use this new option
|
overwriting any existing log file. Now you have to use this new option
|
||||||
to make tio append.
|
to make tio append.
|
||||||
|
|
||||||
|
|
@ -838,7 +1070,7 @@ Changes since tio v1.46:
|
||||||
|
|
||||||
Victor Oliveira
|
Victor Oliveira
|
||||||
|
|
||||||
* add macports install instructions
|
* add MacPorts install instructions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1338,7 +1570,7 @@ Changes since tio v1.35:
|
||||||
* Handle SIGHUP
|
* Handle SIGHUP
|
||||||
|
|
||||||
Handle SIGHUP so that the registered exit handlers are called to restore
|
Handle SIGHUP so that the registered exit handlers are called to restore
|
||||||
the terminal back to its orignal state.
|
the terminal back to its original state.
|
||||||
|
|
||||||
* Add color configuration support
|
* Add color configuration support
|
||||||
|
|
||||||
|
|
@ -1394,10 +1626,10 @@ Changes since tio v1.34:
|
||||||
|
|
||||||
* Add support for configurable timestamp format
|
* Add support for configurable timestamp format
|
||||||
|
|
||||||
Also changes default timestamp format from ISO8601 to classic 24-hour
|
Also changes default timestamp format from ISO 8601 to classic 24-hour
|
||||||
format as this is assumed to be the format that most users would prefer.
|
format as this is assumed to be the format that most users would prefer.
|
||||||
|
|
||||||
And reintroduces strict but optional ISO8601 format.
|
And reintroduces strict but optional ISO 8601 format.
|
||||||
|
|
||||||
This feature allows to easily add more timestamp formats in the future.
|
This feature allows to easily add more timestamp formats in the future.
|
||||||
|
|
||||||
|
|
@ -1518,10 +1750,10 @@ Sylvain LAFRASSE:
|
||||||
|
|
||||||
attila-v:
|
attila-v:
|
||||||
|
|
||||||
* Refine timestamps with milliseconds and ISO-8601 format (#129).
|
* Refine timestamps with milliseconds and ISO 8601 format (#129).
|
||||||
|
|
||||||
* Show milliseconds too in the timestamp (#114) and log file (#124)
|
* Show milliseconds too in the timestamp (#114) and log file (#124)
|
||||||
* Change timestamp format to ISO-8601.
|
* Change timestamp format to ISO 8601.
|
||||||
|
|
||||||
Yin Fengwei:
|
Yin Fengwei:
|
||||||
|
|
||||||
|
|
@ -1600,7 +1832,7 @@ Lars Kellogg-Stedman:
|
||||||
|
|
||||||
George Stark:
|
George Stark:
|
||||||
|
|
||||||
* dont show line state if ioctl failed
|
* don't show line state if ioctl failed
|
||||||
|
|
||||||
* add serial lines manual control
|
* add serial lines manual control
|
||||||
|
|
||||||
|
|
@ -1613,9 +1845,9 @@ arichi:
|
||||||
|
|
||||||
Mariusz Midor:
|
Mariusz Midor:
|
||||||
|
|
||||||
* Newline: handle booth NL and CR
|
* Newline: handle both NL and CR
|
||||||
|
|
||||||
Flag ONLCRNL expects code \n after press Enter, but on some systems \r is send instead.
|
Flag ONLCRNL expects code \n after press Enter, but on some systems \r is sent instead.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2107,7 +2339,7 @@ Changes since tio v1.11:
|
||||||
To display the total number of bytes transmitted/received simply perform the
|
To display the total number of bytes transmitted/received simply perform the
|
||||||
'ctrl-t s' command sequence.
|
'ctrl-t s' command sequence.
|
||||||
|
|
||||||
This feature can be useful when eg. trying to detect non-printable
|
This feature can be useful when e.g. trying to detect non-printable
|
||||||
characters.
|
characters.
|
||||||
|
|
||||||
* Further simplification of key handling
|
* Further simplification of key handling
|
||||||
|
|
|
||||||
151
README.md
151
README.md
|
|
@ -3,8 +3,9 @@
|
||||||
|
|
||||||
# tio - a serial device I/O tool
|
# 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/macos.yml)
|
||||||
|
[](https://github.com/tio/tio/actions/workflows/codeql.yml)
|
||||||
[](https://www.codefactor.io/repository/github/tio/tio)
|
[](https://www.codefactor.io/repository/github/tio/tio)
|
||||||
[](https://github.com/tio/tio/releases)
|
[](https://github.com/tio/tio/releases)
|
||||||
[](https://repology.org/project/tio/versions)
|
[](https://repology.org/project/tio/versions)
|
||||||
|
|
@ -33,14 +34,15 @@ when used in combination with [tmux](https://tmux.github.io).
|
||||||
|
|
||||||
* Easily connect to serial TTY devices
|
* Easily connect to serial TTY devices
|
||||||
* Sensible defaults (115200 8n1)
|
* Sensible defaults (115200 8n1)
|
||||||
* Support for non-standard baud rates
|
|
||||||
* Support for mark and space parity
|
|
||||||
* Automatic connection management
|
* Automatic connection management
|
||||||
|
* Automatic detection of serial ports
|
||||||
* Automatic reconnect
|
* Automatic reconnect
|
||||||
* Automatically connect to first new appearing serial device
|
* Automatically connect to first new appearing serial device
|
||||||
* Automatically connect to latest registered serial device
|
* Automatically connect to latest registered serial device
|
||||||
* Connect to same port/device combination via unique topology ID (TID)
|
* Connect to same port/device combination via unique topology ID (TID)
|
||||||
* Useful for reconnecting when serial device has no serial device by ID
|
* Useful for reconnecting when serial device has no serial device by ID
|
||||||
|
* Support for non-standard baud rates
|
||||||
|
* Support for mark and space parity
|
||||||
* X-modem (1K/CRC) and Y-modem file upload
|
* X-modem (1K/CRC) and Y-modem file upload
|
||||||
* Support for RS-485 mode
|
* Support for RS-485 mode
|
||||||
* List available serial devices
|
* List available serial devices
|
||||||
|
|
@ -73,6 +75,8 @@ when used in combination with [tmux](https://tmux.github.io).
|
||||||
* Configuration file support
|
* Configuration file support
|
||||||
* Support for configuration profiles
|
* Support for configuration profiles
|
||||||
* Activate configuration profiles by name or pattern
|
* Activate configuration profiles by name or pattern
|
||||||
|
* Support for including other configuration files
|
||||||
|
* Redirect I/O of shell command to serial device
|
||||||
* Redirect I/O to UNIX socket or IPv4/v6 network socket
|
* Redirect I/O to UNIX socket or IPv4/v6 network socket
|
||||||
* Useful for scripting or TTY sharing
|
* Useful for scripting or TTY sharing
|
||||||
* Pipe input and/or output
|
* Pipe input and/or output
|
||||||
|
|
@ -88,7 +92,7 @@ when used in combination with [tmux](https://tmux.github.io).
|
||||||
* Send files via x/y-modem protocol
|
* Send files via x/y-modem protocol
|
||||||
* Search for serial devices
|
* Search for serial devices
|
||||||
* Man page documentation
|
* Man page documentation
|
||||||
* Plays nicely with [tmux](https://tmux.github.io)
|
* Plays nicely with [tmux](https://tmux.github.io) and similar terminal multiplexers
|
||||||
|
|
||||||
## 3. Usage
|
## 3. Usage
|
||||||
|
|
||||||
|
|
@ -98,7 +102,7 @@ For more usage details please see the man page documentation
|
||||||
### 3.1 Command-line
|
### 3.1 Command-line
|
||||||
|
|
||||||
The command-line interface is straightforward as reflected in the output from
|
The command-line interface is straightforward as reflected in the output from
|
||||||
'tio --help':
|
```tio --help```:
|
||||||
```
|
```
|
||||||
Usage: tio [<options>] <tty-device|profile|tid>
|
Usage: tio [<options>] <tty-device|profile|tid>
|
||||||
|
|
||||||
|
|
@ -124,7 +128,7 @@ Options:
|
||||||
-t, --timestamp Enable line timestamp
|
-t, --timestamp Enable line timestamp
|
||||||
--timestamp-format <format> Set timestamp format (default: 24hour)
|
--timestamp-format <format> Set timestamp format (default: 24hour)
|
||||||
--timestamp-timeout <ms> Set timestamp timeout (default: 200)
|
--timestamp-timeout <ms> Set timestamp timeout (default: 200)
|
||||||
-l, --list List available serial devices
|
-l, --list List available serial devices, TIDs, and profiles
|
||||||
-L, --log Enable log to file
|
-L, --log Enable log to file
|
||||||
--log-file <filename> Set log filename
|
--log-file <filename> Set log filename
|
||||||
--log-directory <path> Set log directory path for automatic named logs
|
--log-directory <path> Set log directory path for automatic named logs
|
||||||
|
|
@ -140,11 +144,14 @@ Options:
|
||||||
--script <string> Run script from string
|
--script <string> Run script from string
|
||||||
--script-file <filename> Run script from file
|
--script-file <filename> Run script from file
|
||||||
--script-run once|always|never Run script on connect (default: always)
|
--script-run once|always|never Run script on connect (default: always)
|
||||||
|
--exec <command> Execute shell command with I/O redirected to device
|
||||||
-v, --version Display version
|
-v, --version Display version
|
||||||
-h, --help Display help
|
-h, --help Display help
|
||||||
|
|
||||||
Options and profiles may be set via configuration file.
|
Options and profiles may be set via configuration file.
|
||||||
|
|
||||||
|
In session you can press ctrl-t ? to list available key commands.
|
||||||
|
|
||||||
See the man page for more details.
|
See the man page for more details.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -226,19 +233,24 @@ Connect automatically to latest registered serial device:
|
||||||
$ tio --auto-connect latest
|
$ 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:
|
involved in the automatic connection strategy:
|
||||||
```
|
```
|
||||||
$ tio --auto-connect new --exclude-devices "/dev/ttyACM?,/dev/ttyUSB2"
|
$ 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"
|
$ tio --auto-connect new --exclude-drivers "cdc_acm,ftdi_sio"
|
||||||
```
|
```
|
||||||
Note: Pattern matching supports '*' and '?'. Use comma separation to define
|
Note: Pattern matching supports '*' and '?'. Use comma separation to define
|
||||||
multiple patterns.
|
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:
|
Log to file with autogenerated filename:
|
||||||
```
|
```
|
||||||
$ tio --log /dev/ttyUSB0
|
$ tio --log /dev/ttyUSB0
|
||||||
|
|
@ -264,6 +276,11 @@ Redirect I/O to IPv4 network socket on port 4242:
|
||||||
$ tio --socket inet:4242 /dev/ttyUSB0
|
$ 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:
|
Pipe data to the serial device:
|
||||||
```
|
```
|
||||||
$ cat data.bin | tio /dev/ttyUSB0
|
$ cat data.bin | tio /dev/ttyUSB0
|
||||||
|
|
@ -271,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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -297,14 +314,14 @@ ctrl-t ? to list the available key commands.
|
||||||
[15:02:53.269] ctrl-t i Toggle input mode
|
[15:02:53.269] ctrl-t i Toggle input mode
|
||||||
[15:02:53.269] ctrl-t l Clear screen
|
[15:02:53.269] ctrl-t l Clear screen
|
||||||
[15:02:53.269] ctrl-t L Show line states
|
[15:02:53.269] ctrl-t L Show line states
|
||||||
[15:02:53.269] ctrl-t m Toggle MSB to LSB bit order
|
[15:02:53.269] ctrl-t m Change mapping of characters on input or output
|
||||||
[15:02:53.269] ctrl-t o Toggle output mode
|
[15:02:53.269] ctrl-t o Toggle output mode
|
||||||
[15:02:53.269] ctrl-t p Pulse serial port line
|
[15:02:53.269] ctrl-t p Pulse serial port line
|
||||||
[15:02:53.269] ctrl-t q Quit
|
[15:02:53.269] ctrl-t q Quit
|
||||||
[15:02:53.269] ctrl-t r Run script
|
[15:02:53.269] ctrl-t r Run script
|
||||||
|
[15:02:53.269] ctrl-t R Execute shell command with I/O redirected to device
|
||||||
[15:02:53.269] ctrl-t s Show statistics
|
[15:02:53.269] ctrl-t s Show statistics
|
||||||
[15:02:53.269] ctrl-t t Toggle line timestamp mode
|
[15:02:53.269] ctrl-t t Toggle line timestamp mode
|
||||||
[15:02:53.269] ctrl-t U Toggle conversion to uppercase on output
|
|
||||||
[15:02:53.269] ctrl-t v Show version
|
[15:02:53.269] ctrl-t v Show version
|
||||||
[15:02:53.269] ctrl-t x Send file via Xmodem
|
[15:02:53.269] ctrl-t x Send file via Xmodem
|
||||||
[15:02:53.269] ctrl-t y Send file via Ymodem
|
[15:02:53.269] ctrl-t y Send file via Ymodem
|
||||||
|
|
@ -348,12 +365,12 @@ color = 11
|
||||||
[svf2]
|
[svf2]
|
||||||
device = /dev/ttyUSB0
|
device = /dev/ttyUSB0
|
||||||
baudrate = 9600
|
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
|
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
|
||||||
|
|
||||||
|
|
@ -378,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.
|
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)
|
Returns the captures from the pattern or `nil` on timeout.
|
||||||
Send string.
|
|
||||||
|
|
||||||
Returns number of bytes written on success or -1 on error.
|
#### `tio.read(size, timeout)`
|
||||||
|
|
||||||
modem_send(file, protocol)
|
Read up to `size` bytes from serial device. If timeout is 0 or not provided it
|
||||||
Send file using x/y-modem protocol.
|
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()
|
#### `tio.readline(timeout)`
|
||||||
Search for serial devices.
|
|
||||||
|
|
||||||
Returns a table of number indexed tables, one for each serial device
|
Read line from serial device. If timeout is 0 or not provided it will wait
|
||||||
found. Each of these tables contains the serial device information accessible
|
forever until data is ready to read.
|
||||||
via the following string indexed elements "path", "tid", "uptime", "driver",
|
|
||||||
"description".
|
|
||||||
|
|
||||||
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)
|
#### `tio.write(string)`
|
||||||
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.
|
Write string to serial device.
|
||||||
|
|
||||||
On success, returns read string as second return value.
|
Returns the `tio` table.
|
||||||
|
|
||||||
set{line=state, ...}
|
#### `tio.send(file, protocol)`
|
||||||
Set state of one or multiple tty modem lines.
|
|
||||||
|
|
||||||
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)
|
#### `tio.ttysearch()`
|
||||||
Sleep for seconds.
|
|
||||||
|
|
||||||
msleep(ms)
|
Search for serial devices.
|
||||||
Sleep for miliseconds.
|
|
||||||
|
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
|
## 4. Installation
|
||||||
|
|
||||||
|
|
@ -473,6 +508,12 @@ $ pacman -S tio
|
||||||
|
|
||||||
The latest source releases can be found [here](https://github.com/tio/tio/releases).
|
The latest source releases can be found [here](https://github.com/tio/tio/releases).
|
||||||
|
|
||||||
|
Before running the install steps make sure you have glib and lua libraries installed. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt install libglib2.0-dev liblua5.2-dev
|
||||||
|
```
|
||||||
|
|
||||||
Install steps:
|
Install steps:
|
||||||
```
|
```
|
||||||
$ meson setup build
|
$ meson setup build
|
||||||
|
|
@ -488,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?
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
43
TODO
43
TODO
|
|
@ -1,3 +1,40 @@
|
||||||
|
* 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:
|
||||||
|
|
||||||
|
All posix functions need to be platform independent, go though file by file:
|
||||||
|
|
||||||
|
termios.h
|
||||||
|
unistd.h has very limited functions
|
||||||
|
ENV different in config_file_resolve
|
||||||
|
errno
|
||||||
|
sys/ioctl.h
|
||||||
|
sys/poll.h
|
||||||
|
socket, may need a new thread
|
||||||
|
Serial, RS485, character mapping
|
||||||
|
Communication pipe
|
||||||
|
|
||||||
|
Port enumerate, all devices of the same type have the same name (eg. USB
|
||||||
|
Serial Device for ttyACM) -> which makes regex not meaningful (kind of a
|
||||||
|
good thing since libtre in Mingw has too much dependencies makes binary too
|
||||||
|
big)
|
||||||
|
|
||||||
* Support traditional hex output format such as:
|
* Support traditional hex output format such as:
|
||||||
|
|
||||||
00000000 74 65 73 74 20 74 65 73 74 20 74 65 73 74 20 74 |test test test t|
|
00000000 74 65 73 74 20 74 65 73 74 20 74 65 73 74 20 74 |test test test t|
|
||||||
|
|
@ -13,12 +50,6 @@
|
||||||
|
|
||||||
Already supported in hex output mode.
|
Already supported in hex output mode.
|
||||||
|
|
||||||
* Advanced line mode
|
|
||||||
|
|
||||||
Current line mode only support backspace editing. Would be nice with arrow
|
|
||||||
key navigation left/right and insert/overwrite support. Also history browsing
|
|
||||||
pressing up/down.
|
|
||||||
|
|
||||||
* Allow tio to connect to socket
|
* Allow tio to connect to socket
|
||||||
|
|
||||||
After some more consideration I think it makes sense to support connecting to a
|
After some more consideration I think it makes sense to support connecting to a
|
||||||
|
|
|
||||||
|
|
@ -69,5 +69,10 @@ 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]
|
||||||
|
device = /dev/ttyACM0
|
||||||
|
map = INLCRNL,ODELBS
|
||||||
|
color = 15
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
send(login.username .. "\n")
|
tio.write(login.username .. "\n")
|
||||||
expect("Password:")
|
tio.expect("Password:")
|
||||||
send(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}
|
||||||
|
|
|
||||||
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... ")
|
io.write("Searching... ")
|
||||||
|
|
||||||
local device = tty_search()
|
local device = tio.ttysearch()
|
||||||
|
|
||||||
io.write("done\r\n")
|
io.write("done\r\n")
|
||||||
|
|
||||||
|
|
|
||||||
110
man/tio.1.in
110
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
|
24-hour format relative to previous timestamp
|
||||||
.IP "\fBiso8601"
|
.IP "\fBiso8601"
|
||||||
ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
|
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
|
.PP
|
||||||
Default format is \fB24hour\fR
|
Default format is \fB24hour\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
@ -201,8 +205,8 @@ Strip control characters and escape sequences from log.
|
||||||
.TP
|
.TP
|
||||||
.BR \-m ", " "\-\-map " \fI<flags>
|
.BR \-m ", " "\-\-map " \fI<flags>
|
||||||
|
|
||||||
Map (replace, translate) characters on input or output. The following mapping
|
Map (replace, translate) characters on input to the serial device or output
|
||||||
flags are supported:
|
from the serial device. The following mapping flags are supported:
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.TP 12n
|
.TP 12n
|
||||||
|
|
@ -216,6 +220,10 @@ 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"
|
||||||
|
Map MSB bit order to LSB on input
|
||||||
.IP "\fBOCRNL"
|
.IP "\fBOCRNL"
|
||||||
Map CR to NL on output
|
Map CR to NL on output
|
||||||
.IP "\fBODELBS"
|
.IP "\fBODELBS"
|
||||||
|
|
@ -226,8 +234,8 @@ Map NL to CR-NL on output
|
||||||
Map lowercase characters to uppercase on output
|
Map lowercase characters to uppercase on output
|
||||||
.IP "\fBONULBRK"
|
.IP "\fBONULBRK"
|
||||||
Map nul (zero) to send break signal on output
|
Map nul (zero) to send break signal on output
|
||||||
.IP "\fBMSB2LSB"
|
.IP "\fBOIGNCR"
|
||||||
Map MSB bit order to LSB on output
|
Ignore CR on output
|
||||||
.P
|
.P
|
||||||
If defining more than one flag, the flags must be comma separated.
|
If defining more than one flag, the flags must be comma separated.
|
||||||
.RE
|
.RE
|
||||||
|
|
@ -363,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
|
||||||
|
|
||||||
|
|
@ -371,10 +383,10 @@ Display program version.
|
||||||
.BR \-h ", " \-\-help
|
.BR \-h ", " \-\-help
|
||||||
|
|
||||||
Display help.
|
Display help.
|
||||||
.SH "KEYS"
|
.SH "KEY COMMANDS"
|
||||||
.PP
|
.PP
|
||||||
.TP 16n
|
.TP 16n
|
||||||
In session, all key strokes are forwarded to the serial device except the following key sequence: a prefix key (default: ctrl-t) followed by a command key. These sequences are intercepted as tio commands:
|
In session, all key strokes are forwarded to the serial device except the following key sequence: a prefix key (default: ctrl-t) followed by a command key. These sequences are intercepted as key commands:
|
||||||
.IP "\fBctrl-t ?"
|
.IP "\fBctrl-t ?"
|
||||||
List available key commands
|
List available key commands
|
||||||
.IP "\fBctrl-t b"
|
.IP "\fBctrl-t b"
|
||||||
|
|
@ -396,7 +408,7 @@ Clear screen
|
||||||
.IP "\fBctrl-t L"
|
.IP "\fBctrl-t L"
|
||||||
Show line states (DTR, RTS, CTS, DSR, DCD, RI)
|
Show line states (DTR, RTS, CTS, DSR, DCD, RI)
|
||||||
.IP "\fBctrl-t m"
|
.IP "\fBctrl-t m"
|
||||||
Toggle MSB to LSB bit order
|
Change mapping of characters on input or output
|
||||||
.IP "\fBctrl-t o"
|
.IP "\fBctrl-t o"
|
||||||
Toggle output mode
|
Toggle output mode
|
||||||
.IP "\fBctrl-t p"
|
.IP "\fBctrl-t p"
|
||||||
|
|
@ -405,12 +417,12 @@ Pulse serial port line
|
||||||
Quit
|
Quit
|
||||||
.IP "\fBctrl-t r"
|
.IP "\fBctrl-t r"
|
||||||
Run script
|
Run script
|
||||||
|
.IP "\fBctrl-t R"
|
||||||
|
Execute shell command with I/O redirected to device
|
||||||
.IP "\fBctrl-t s"
|
.IP "\fBctrl-t s"
|
||||||
Show TX/RX statistics
|
Show TX/RX statistics
|
||||||
.IP "\fBctrl-t t"
|
.IP "\fBctrl-t t"
|
||||||
Toggle line timestamp mode
|
Toggle line timestamp mode
|
||||||
.IP "\fBctrl-t U"
|
|
||||||
Toggle conversion to uppercase on output
|
|
||||||
.IP "\fBctrl-t v"
|
.IP "\fBctrl-t v"
|
||||||
Show version
|
Show version
|
||||||
.IP "\fBctrl-t x"
|
.IP "\fBctrl-t x"
|
||||||
|
|
@ -424,30 +436,41 @@ Send ctrl-t character
|
||||||
.PP
|
.PP
|
||||||
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:
|
||||||
|
|
||||||
.TP 6n
|
.TP 6n
|
||||||
|
|
||||||
.IP "\fBexpect(string, timeout)"
|
.IP "\fBtio.expect(pattern, timeout)"
|
||||||
Expect string - waits for string to match or timeout before continueing.
|
Waits for the Lua pattern 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.
|
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)"
|
Returns a string up to size bytes long on success and nil on timeout.
|
||||||
Send string.
|
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
@ -457,27 +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 "\fBread(size, timeout)"
|
.IP "\fBtio.set{line=state, ...}"
|
||||||
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, ...}"
|
|
||||||
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 miliseconds.
|
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
|
||||||
|
|
@ -579,6 +601,9 @@ Run script on connect
|
||||||
.IP "\fBexec"
|
.IP "\fBexec"
|
||||||
Execute shell command with I/O redirected to device
|
Execute shell command with I/O redirected to device
|
||||||
|
|
||||||
|
.PP
|
||||||
|
It is possible to include the content of other configuration files using the
|
||||||
|
include directive like so: "[include <file>]".
|
||||||
|
|
||||||
.SH "CONFIGURATION FILE EXAMPLES"
|
.SH "CONFIGURATION FILE EXAMPLES"
|
||||||
|
|
||||||
|
|
@ -624,8 +649,7 @@ Which is equivalent to:
|
||||||
$ tio -b 115200 -c 11 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
|
$ tio -b 115200 -c 11 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
A configuration profile can also be activated by its pattern which supports
|
A configuration profile can also be activated by its pattern which supports regular expressions:
|
||||||
regular expressions:
|
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.nf
|
.nf
|
||||||
|
|
@ -649,8 +673,7 @@ Which becomes equivalent to:
|
||||||
$ tio -b 115200 /dev/ttyUSB12
|
$ tio -b 115200 /dev/ttyUSB12
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
It is also possible to combine use of configuration profile and command-line
|
It is also possible to combine use of configuration profile and command-line options. For example:
|
||||||
options. For example:
|
|
||||||
|
|
||||||
$ tio -l -t usb12
|
$ tio -l -t usb12
|
||||||
|
|
||||||
|
|
@ -705,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: "); 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
|
.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:
|
||||||
|
|
@ -726,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
|
||||||
|
|
@ -734,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
|
$ 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
|
.TP
|
||||||
Enable RS-485 mode:
|
Enable RS-485 mode:
|
||||||
|
|
||||||
|
|
@ -742,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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
tio(1) User Commands tio(1)
|
tio(1) User Commands tio(1)
|
||||||
|
|
||||||
NAME
|
NAME
|
||||||
tio - a serial device I/O tool
|
tio - a serial device I/O tool
|
||||||
|
|
@ -114,6 +114,10 @@ OPTIONS
|
||||||
|
|
||||||
iso8601 ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
|
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
|
Default format is 24hour
|
||||||
|
|
||||||
--timestamp-timeout <ms>
|
--timestamp-timeout <ms>
|
||||||
|
|
@ -154,7 +158,7 @@ OPTIONS
|
||||||
|
|
||||||
-m, --map <flags>
|
-m, --map <flags>
|
||||||
|
|
||||||
Map (replace, translate) characters on input or output. The following mapping flags are supported:
|
Map (replace, translate) characters on input to the serial device or output from the serial device. The following mapping flags are supported:
|
||||||
|
|
||||||
ICRNL Map CR to NL on input (unless IGNCR is set)
|
ICRNL Map CR to NL on input (unless IGNCR is set)
|
||||||
|
|
||||||
|
|
@ -166,6 +170,10 @@ 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
|
||||||
|
|
||||||
OCRNL Map CR to NL on output
|
OCRNL Map CR to NL on output
|
||||||
|
|
||||||
ODELBS Map DEL to BS on output
|
ODELBS Map DEL to BS on output
|
||||||
|
|
@ -176,7 +184,7 @@ OPTIONS
|
||||||
|
|
||||||
ONULBRK Map nul (zero) to send break signal on output
|
ONULBRK Map nul (zero) to send break signal on output
|
||||||
|
|
||||||
MSB2LSB Map MSB bit order to LSB on output
|
OIGNCR Ignore CR on output
|
||||||
|
|
||||||
If defining more than one flag, the flags must be comma separated.
|
If defining more than one flag, the flags must be comma separated.
|
||||||
|
|
||||||
|
|
@ -214,7 +222,8 @@ OPTIONS
|
||||||
|
|
||||||
Redirect I/O to socket.
|
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.
|
Sockets remain open while the serial port is disconnected, and writes will block.
|
||||||
|
|
||||||
|
|
@ -276,6 +285,14 @@ OPTIONS
|
||||||
|
|
||||||
Default value is "always".
|
Default value is "always".
|
||||||
|
|
||||||
|
--exec <command>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -284,8 +301,8 @@ OPTIONS
|
||||||
|
|
||||||
Display help.
|
Display help.
|
||||||
|
|
||||||
KEYS
|
KEY COMMANDS
|
||||||
In session, all key strokes are forwarded to the serial device except the following key sequence: a prefix key (default: ctrl-t) followed by a command key. These sequences are intercepted as tio commands:
|
In session, all key strokes are forwarded to the serial device except the following key sequence: a prefix key (default: ctrl-t) followed by a command key. These sequences are intercepted as key commands:
|
||||||
|
|
||||||
ctrl-t ? List available key commands
|
ctrl-t ? List available key commands
|
||||||
|
|
||||||
|
|
@ -307,7 +324,7 @@ KEYS
|
||||||
|
|
||||||
ctrl-t L Show line states (DTR, RTS, CTS, DSR, DCD, RI)
|
ctrl-t L Show line states (DTR, RTS, CTS, DSR, DCD, RI)
|
||||||
|
|
||||||
ctrl-t m Toggle MSB to LSB bit order
|
ctrl-t m Change mapping of characters on input or output
|
||||||
|
|
||||||
ctrl-t o Toggle output mode
|
ctrl-t o Toggle output mode
|
||||||
|
|
||||||
|
|
@ -317,12 +334,12 @@ KEYS
|
||||||
|
|
||||||
ctrl-t r Run script
|
ctrl-t r Run script
|
||||||
|
|
||||||
|
ctrl-t R Execute shell command with I/O redirected to device
|
||||||
|
|
||||||
ctrl-t s Show TX/RX statistics
|
ctrl-t s Show TX/RX statistics
|
||||||
|
|
||||||
ctrl-t t Toggle line timestamp mode
|
ctrl-t t Toggle line timestamp mode
|
||||||
|
|
||||||
ctrl-t U Toggle conversion to uppercase on output
|
|
||||||
|
|
||||||
ctrl-t v Show version
|
ctrl-t v Show version
|
||||||
|
|
||||||
ctrl-t x Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol)
|
ctrl-t x Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol)
|
||||||
|
|
@ -334,21 +351,35 @@ KEYS
|
||||||
SCRIPT API
|
SCRIPT API
|
||||||
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 available:
|
||||||
|
|
||||||
expect(string, timeout)
|
expect(string, timeout)
|
||||||
Expect string - waits for string to match or timeout before continueing. Supports regular expressions. Special characters must be escaped with '\\'. Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
|
Expect string - waits for string to match or timeout before continuing. 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.
|
Returns 1 on successful match, 0 on timeout, or -1 on error.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
send(string)
|
read(size, timeout)
|
||||||
Send string.
|
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.
|
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.
|
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.
|
||||||
|
|
@ -356,17 +387,11 @@ SCRIPT API
|
||||||
tty_search()
|
tty_search()
|
||||||
Search for serial devices.
|
Search for serial devices.
|
||||||
|
|
||||||
Returns a table of number indexed tables, one for each serial device found. Each of these tables contains the serial device information accessible via the following string indexed elements "path", "tid", "uptime", "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.
|
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{line=state, ...}
|
||||||
Set state of one or multiple tty modem lines.
|
Set state of one or multiple tty modem lines.
|
||||||
|
|
||||||
|
|
@ -378,7 +403,7 @@ SCRIPT API
|
||||||
Sleep for seconds.
|
Sleep for seconds.
|
||||||
|
|
||||||
msleep(ms)
|
msleep(ms)
|
||||||
Sleep for miliseconds.
|
Sleep for milliseconds.
|
||||||
|
|
||||||
exit(code)
|
exit(code)
|
||||||
Exit with exit code.
|
Exit with exit code.
|
||||||
|
|
@ -468,6 +493,10 @@ CONFIGURATION FILE
|
||||||
|
|
||||||
script-run Run script on connect
|
script-run Run script on connect
|
||||||
|
|
||||||
|
exec Execute shell command with I/O redirected to device
|
||||||
|
|
||||||
|
It is possible to include the content of other configuration files using the include directive like so: "[include <file>]".
|
||||||
|
|
||||||
CONFIGURATION FILE EXAMPLES
|
CONFIGURATION FILE EXAMPLES
|
||||||
To change the default configuration simply set options like so:
|
To change the default configuration simply set options like so:
|
||||||
|
|
||||||
|
|
@ -494,8 +523,7 @@ CONFIGURATION FILE EXAMPLES
|
||||||
|
|
||||||
$ tio -b 115200 -c 11 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
|
$ tio -b 115200 -c 11 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
|
||||||
|
|
||||||
A configuration profile can also be activated by its pattern which supports
|
A configuration profile can also be activated by its pattern which supports regular expressions:
|
||||||
regular expressions:
|
|
||||||
|
|
||||||
[usb-devices]
|
[usb-devices]
|
||||||
pattern = ^usb([0-9]*)
|
pattern = ^usb([0-9]*)
|
||||||
|
|
@ -510,8 +538,7 @@ CONFIGURATION FILE EXAMPLES
|
||||||
|
|
||||||
$ tio -b 115200 /dev/ttyUSB12
|
$ tio -b 115200 /dev/ttyUSB12
|
||||||
|
|
||||||
It is also possible to combine use of configuration profile and command-line
|
It is also possible to combine use of configuration profile and command-line options. For example:
|
||||||
options. For example:
|
|
||||||
|
|
||||||
$ tio -l -t usb12
|
$ tio -l -t usb12
|
||||||
|
|
||||||
|
|
@ -553,9 +580,9 @@ EXAMPLES
|
||||||
send -i $uart "ls -la\n"
|
send -i $uart "ls -la\n"
|
||||||
expect -i $uart "prompt> "
|
expect -i $uart "prompt> "
|
||||||
|
|
||||||
It is also possible to use the 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: "); 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:
|
Redirect device I/O to network file socket for remote TTY sharing:
|
||||||
|
|
||||||
|
|
@ -577,6 +604,10 @@ EXAMPLES
|
||||||
|
|
||||||
$ cat data.bin | tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
|
$ 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:
|
Enable RS-485 mode:
|
||||||
|
|
||||||
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
|
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
|
||||||
|
|
@ -585,14 +616,10 @@ EXAMPLES
|
||||||
|
|
||||||
$ tio --script "set{DTR=high,RTS=low}; msleep(100); set{RTS=toggle}" --script-run once /dev/ttyUSB0
|
$ tio --script "set{DTR=high,RTS=low}; msleep(100); set{RTS=toggle}" --script-run once /dev/ttyUSB0
|
||||||
|
|
||||||
Automatically log in to connected OS:
|
|
||||||
|
|
||||||
$ tio --script "expect('password:'); send('my_password\n')" /dev/ttyUSB0
|
|
||||||
|
|
||||||
WEBSITE
|
WEBSITE
|
||||||
Visit https://tio.github.io
|
Visit https://tio.github.io
|
||||||
|
|
||||||
AUTHOR
|
AUTHOR
|
||||||
Maintained by Martin Lund <martin.lund@keep-it-simple.com>.
|
Maintained by Martin Lund <martin.lund@keep-it-simple.com>.
|
||||||
|
|
||||||
tio 3.1 2024-05-03 tio(1)
|
tio 3.9 2025-04-13 tio(1)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
project('tio', 'c',
|
project('tio', 'c',
|
||||||
version : '3.2',
|
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' ]
|
||||||
)
|
)
|
||||||
|
|
||||||
# The tag date of the project_version(), update when the version bumps.
|
# The tag date of the project_version(), update when the version bumps.
|
||||||
version_date = '2024-05-08'
|
version_date = '2025-04-13'
|
||||||
|
|
||||||
# Test for dynamic baudrate configuration interface
|
# Test for dynamic baudrate configuration interface
|
||||||
compiler = meson.get_compiler('c')
|
compiler = meson.get_compiler('c')
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,10 @@
|
||||||
* 02110-1301, USA.
|
* 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "alert.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
|
||||||
#include "error.h"
|
|
||||||
#include "print.h"
|
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "alert.h"
|
||||||
|
|
||||||
void blink_background(void)
|
void blink_background(void)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 OCRNL ODELBS ONLCRNL MSB2LSB" -- ${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)
|
||||||
|
|
|
||||||
208
src/configfile.c
208
src/configfile.c
|
|
@ -21,36 +21,30 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <errno.h>
|
#include <stdlib.h>
|
||||||
#include <getopt.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <errno.h>
|
||||||
#include <regex.h>
|
#include <regex.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include "options.h"
|
|
||||||
#include "configfile.h"
|
#include "configfile.h"
|
||||||
#include "misc.h"
|
#include "timestamp.h"
|
||||||
#include "options.h"
|
|
||||||
#include "error.h"
|
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
#include "rs485.h"
|
#include "rs485.h"
|
||||||
#include "timestamp.h"
|
#include "misc.h"
|
||||||
#include "alert.h"
|
|
||||||
|
|
||||||
#define CONFIG_GROUP_NAME_DEFAULT "default"
|
#define CONFIG_GROUP_NAME_DEFAULT "default"
|
||||||
|
#define CONFIG_GROUP_INCLUDE_PREFIX "include "
|
||||||
|
#define MAX_LINE_LENGTH 1024
|
||||||
|
|
||||||
struct config_t config = {};
|
struct config_t config = {};
|
||||||
|
|
||||||
|
static void config_file_load(const char *filename, GString *buffer, bool test);
|
||||||
|
static void config_file_process(const char *filename, GString *buffer, GList **included_files, bool test);
|
||||||
|
|
||||||
static void config_get_string(GKeyFile *key_file, gchar *group, gchar *key, char **dest, char *allowed_string, ...)
|
static void config_get_string(GKeyFile *key_file, gchar *group, gchar *key, char **dest, char *allowed_string, ...)
|
||||||
{
|
{
|
||||||
(void)dest;
|
(void)dest;
|
||||||
|
|
@ -155,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)
|
static void config_parse_keys(GKeyFile *key_file, char *group)
|
||||||
{
|
{
|
||||||
char *string = NULL;
|
char *string = NULL;
|
||||||
bool boolean = false;
|
|
||||||
|
|
||||||
config_get_string(key_file, group, "device", &config.device, NULL);
|
config_get_string(key_file, group, "device", &config.device, NULL);
|
||||||
config_get_integer(key_file, group, "baudrate", &option.baudrate, 0, INT_MAX);
|
config_get_integer(key_file, group, "baudrate", &option.baudrate, 0, INT_MAX);
|
||||||
|
|
@ -196,7 +189,7 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
|
||||||
config_get_string(key_file, group, "exclude-drivers", &option.exclude_devices, NULL);
|
config_get_string(key_file, group, "exclude-drivers", &option.exclude_devices, NULL);
|
||||||
config_get_string(key_file, group, "exclude-tids", &option.exclude_devices, NULL);
|
config_get_string(key_file, group, "exclude-tids", &option.exclude_devices, NULL);
|
||||||
config_get_bool(key_file, group, "no-reconnect", &option.no_reconnect);
|
config_get_bool(key_file, group, "no-reconnect", &option.no_reconnect);
|
||||||
config_get_bool(key_file, group, "local-echo", &option.no_reconnect);
|
config_get_bool(key_file, group, "local-echo", &option.local_echo);
|
||||||
config_get_string(key_file, group, "input-mode", &string, "normal", "hex", "line", NULL);
|
config_get_string(key_file, group, "input-mode", &string, "normal", "hex", "line", NULL);
|
||||||
if (string != NULL)
|
if (string != NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -211,24 +204,30 @@ static void config_parse_keys(GKeyFile *key_file, char *group)
|
||||||
g_free((void *)string);
|
g_free((void *)string);
|
||||||
string = NULL;
|
string = NULL;
|
||||||
}
|
}
|
||||||
config_get_bool(key_file, group, "timestamp", &boolean);
|
config_get_bool(key_file, group, "timestamp", (bool*) &option.timestamp);
|
||||||
if (boolean == true)
|
if (option.timestamp != TIMESTAMP_NONE)
|
||||||
{
|
{
|
||||||
option.timestamp = TIMESTAMP_24HOUR;
|
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", "epoch", "epoch-usec", NULL);
|
||||||
}
|
if (string != NULL)
|
||||||
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", NULL);
|
{
|
||||||
if (string != NULL)
|
option_parse_timestamp(string, &option.timestamp);
|
||||||
{
|
g_free((void *)string);
|
||||||
option_parse_timestamp(string, &option.timestamp);
|
string = NULL;
|
||||||
g_free((void *)string);
|
}
|
||||||
string = NULL;
|
|
||||||
}
|
}
|
||||||
config_get_integer(key_file, group, "timestamp-timeout", &option.timestamp_timeout, 0, INT_MAX);
|
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_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-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-append", &option.log_append);
|
||||||
config_get_bool(key_file, group, "log-strip", &option.log_strip);
|
config_get_bool(key_file, group, "log-strip", &option.log_strip);
|
||||||
config_get_string(key_file, group, "map", &option.map, NULL);
|
config_get_string(key_file, group, "map", &string, NULL);
|
||||||
|
if (string != NULL)
|
||||||
|
{
|
||||||
|
option_parse_mappings(string);
|
||||||
|
g_free((void *)string);
|
||||||
|
string = NULL;
|
||||||
|
}
|
||||||
config_get_string(key_file, group, "color", &string, NULL);
|
config_get_string(key_file, group, "color", &string, NULL);
|
||||||
if (string != NULL)
|
if (string != NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -354,9 +353,11 @@ static int config_file_resolve(void)
|
||||||
|
|
||||||
void config_file_show_profiles(void)
|
void config_file_show_profiles(void)
|
||||||
{
|
{
|
||||||
GKeyFile *keyfile;
|
GString *config_buffer;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
GKeyFile *keyfile;
|
||||||
|
|
||||||
|
// Reset configuration
|
||||||
memset(&config, 0, sizeof(struct config_t));
|
memset(&config, 0, sizeof(struct config_t));
|
||||||
|
|
||||||
// Find config file
|
// Find config file
|
||||||
|
|
@ -366,13 +367,16 @@ void config_file_show_profiles(void)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
keyfile = g_key_file_new();
|
// Load content of configuration file into buffer
|
||||||
|
config_buffer = g_string_new(NULL);
|
||||||
|
config_file_load(config.path, config_buffer, false);
|
||||||
|
|
||||||
if (!g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error))
|
// Load configuration
|
||||||
|
keyfile = g_key_file_new();
|
||||||
|
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
|
||||||
{
|
{
|
||||||
tio_error_print("Failure loading file: %s", error->message);
|
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
return;
|
goto error_load;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all group names
|
// Get all group names
|
||||||
|
|
@ -386,11 +390,20 @@ void config_file_show_profiles(void)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip group with include directive
|
||||||
|
if (strncmp(group[i], CONFIG_GROUP_INCLUDE_PREFIX, strlen(CONFIG_GROUP_INCLUDE_PREFIX)) == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
printf("%s ", group[i]);
|
printf("%s ", group[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_strfreev(group);
|
g_strfreev(group);
|
||||||
|
error_load:
|
||||||
g_key_file_free(keyfile);
|
g_key_file_free(keyfile);
|
||||||
|
g_string_free(config_buffer, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void replace_substring(char *str, const char *substr, const char *replacement)
|
static void replace_substring(char *str, const char *substr, const char *replacement)
|
||||||
|
|
@ -415,13 +428,13 @@ static char *match_and_replace(const char *str, const char *pattern, char *devic
|
||||||
assert(pattern != NULL);
|
assert(pattern != NULL);
|
||||||
assert(device != NULL);
|
assert(device != NULL);
|
||||||
|
|
||||||
char *string = malloc(strlen(device) + PATH_MAX);
|
char *string = calloc(PATH_MAX, 1);
|
||||||
if (string == NULL)
|
if (string == NULL)
|
||||||
{
|
{
|
||||||
tio_debug_printf("Failure allocating string memory\n");
|
tio_debug_printf("Failure allocating string memory\n");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
strcpy(string, device);
|
strncpy(string, device, PATH_MAX - 1);
|
||||||
|
|
||||||
/* Find matches of pattern in str. For each match, replace any '%mN' in the
|
/* 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
|
* copy of the device string with the corresponding match subexpression and
|
||||||
|
|
@ -491,6 +504,95 @@ error:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void config_file_process_line(const char *line, GString *buffer, GList **included_files, bool test)
|
||||||
|
{
|
||||||
|
if (strncmp(line, "[include ", 9) == 0 && line[strlen(line) - 2] == ']')
|
||||||
|
{
|
||||||
|
char include_filename[MAX_LINE_LENGTH];
|
||||||
|
|
||||||
|
// Construct the format string safely
|
||||||
|
char format_string[50];
|
||||||
|
snprintf(format_string, sizeof(format_string), "[include %%%ds]", MAX_LINE_LENGTH - 1);
|
||||||
|
|
||||||
|
int ret = sscanf(line, format_string, include_filename);
|
||||||
|
if (ret != 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the trailing ']' character
|
||||||
|
include_filename[strlen(include_filename) - 1] = '\0';
|
||||||
|
|
||||||
|
if (g_list_find_custom(*included_files, include_filename, (GCompareFunc)strcmp) != NULL)
|
||||||
|
{
|
||||||
|
// Already included, avoid recursion
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to included files list
|
||||||
|
*included_files = g_list_append(*included_files, g_strdup(include_filename));
|
||||||
|
|
||||||
|
// Process the included file
|
||||||
|
config_file_process(include_filename, buffer, included_files, test);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Normal line, add to buffer
|
||||||
|
g_string_append(buffer, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void config_file_process(const char *filename, GString *buffer, GList **included_files, bool test)
|
||||||
|
{
|
||||||
|
if (test)
|
||||||
|
{
|
||||||
|
// Test that configuration file can be parsed
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
GKeyFile *keyfile = g_key_file_new();
|
||||||
|
|
||||||
|
if (g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &error) == false)
|
||||||
|
{
|
||||||
|
tio_error_print("Failure loading file %s: %s", filename, error->message);
|
||||||
|
g_key_file_free(keyfile);
|
||||||
|
g_error_free(error);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *file = fopen(filename, "r");
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
char line[MAX_LINE_LENGTH];
|
||||||
|
while (fgets(line, sizeof(line), file))
|
||||||
|
{
|
||||||
|
config_file_process_line(line, buffer, included_files, test);
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void config_file_load(const char *filename, GString *buffer, bool test)
|
||||||
|
{
|
||||||
|
char current_dir[PATH_MAX] = ".";
|
||||||
|
char *config_file_dir = dirname(strdup(config.path));
|
||||||
|
GList *included_files = NULL;
|
||||||
|
|
||||||
|
getcwd(current_dir, PATH_MAX);
|
||||||
|
|
||||||
|
// Change to the directory of the configuration file
|
||||||
|
chdir(config_file_dir);
|
||||||
|
|
||||||
|
config_file_process(filename, buffer, &included_files, test);
|
||||||
|
|
||||||
|
// Restore current directory
|
||||||
|
chdir(current_dir);
|
||||||
|
|
||||||
|
// Free memory
|
||||||
|
g_list_free_full(included_files, g_free);
|
||||||
|
free(config_file_dir);
|
||||||
|
}
|
||||||
|
|
||||||
void config_file_parse(void)
|
void config_file_parse(void)
|
||||||
{
|
{
|
||||||
// Find config file
|
// Find config file
|
||||||
|
|
@ -505,12 +607,18 @@ void config_file_parse(void)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GString *config_buffer = g_string_new(NULL);
|
||||||
GKeyFile *keyfile = g_key_file_new();
|
GKeyFile *keyfile = g_key_file_new();
|
||||||
|
GList *included_files = NULL;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
|
||||||
if (g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error) == false)
|
config_file_load(config.path, config_buffer, true);
|
||||||
|
|
||||||
|
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
|
||||||
{
|
{
|
||||||
tio_error_print("Failure loading file %s: %s", config.path, error->message);
|
tio_error_print("Failure loading file %s: %s", config.path, error->message);
|
||||||
|
g_string_free(config_buffer, TRUE);
|
||||||
|
g_key_file_free(keyfile);
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
@ -582,7 +690,10 @@ void config_file_parse(void)
|
||||||
g_strfreev(group);
|
g_strfreev(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
g_key_file_free(keyfile);
|
g_key_file_free(keyfile);
|
||||||
|
g_string_free(config_buffer, TRUE);
|
||||||
|
g_list_free_full(included_files, g_free);
|
||||||
|
|
||||||
atexit(&config_exit);
|
atexit(&config_exit);
|
||||||
}
|
}
|
||||||
|
|
@ -622,10 +733,14 @@ void config_list_targets(void)
|
||||||
|
|
||||||
keyfile = g_key_file_new();
|
keyfile = g_key_file_new();
|
||||||
|
|
||||||
if (!g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error))
|
GString *config_buffer = g_string_new(NULL);
|
||||||
|
|
||||||
|
config_file_load(config.path, config_buffer, false);
|
||||||
|
|
||||||
|
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
|
||||||
{
|
{
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
return;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all group names
|
// Get all group names
|
||||||
|
|
@ -634,7 +749,7 @@ void config_list_targets(void)
|
||||||
|
|
||||||
if (num_groups == 0)
|
if (num_groups == 0)
|
||||||
{
|
{
|
||||||
return;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\nConfiguration profiles (%s)\n", config.path);
|
printf("\nConfiguration profiles (%s)\n", config.path);
|
||||||
|
|
@ -648,6 +763,13 @@ void config_list_targets(void)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip group with include directive
|
||||||
|
if (strncmp(group[i], CONFIG_GROUP_INCLUDE_PREFIX, strlen(CONFIG_GROUP_INCLUDE_PREFIX)) == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
printf("%-19s ", group[i]);
|
printf("%-19s ", group[i]);
|
||||||
if (j++ % 4 == 0)
|
if (j++ % 4 == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -660,5 +782,7 @@ void config_list_targets(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
g_strfreev(group);
|
g_strfreev(group);
|
||||||
|
cleanup:
|
||||||
g_key_file_free(keyfile);
|
g_key_file_free(keyfile);
|
||||||
|
g_string_free(config_buffer, TRUE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/error.c
12
src/error.c
|
|
@ -19,19 +19,9 @@
|
||||||
* 02110-1301, USA.
|
* 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define __STDC_WANT_LIB_EXT2__ 1 // To access vasprintf
|
#define _GNU_SOURCE // To access vasprintf
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include "options.h"
|
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
#include "error.h"
|
|
||||||
#include "timestamp.h"
|
|
||||||
|
|
||||||
static char error[2][1000];
|
static char error[2][1000];
|
||||||
static bool in_session = false;
|
static bool in_session = false;
|
||||||
|
|
|
||||||
14
src/fs.c
14
src/fs.c
|
|
@ -32,7 +32,7 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <sys/poll.h>
|
#include <poll.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
|
|
@ -150,14 +150,7 @@ char* fs_search_directory(const char *dir_path, const char *dirname)
|
||||||
// If it's a directory, check if it's the one we're looking for
|
// If it's a directory, check if it's the one we're looking for
|
||||||
if (strcmp(entry->d_name, dirname) == 0)
|
if (strcmp(entry->d_name, dirname) == 0)
|
||||||
{
|
{
|
||||||
char* result = malloc(strlen(path) + 1);
|
char *result = strndup(path, PATH_MAX);
|
||||||
if (result == NULL)
|
|
||||||
{
|
|
||||||
// Error allocating memory
|
|
||||||
closedir(dir);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
strcpy(result, path);
|
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +199,6 @@ double fs_get_creation_time(const char *path)
|
||||||
|
|
||||||
double fs_get_creation_time(const char *path)
|
double fs_get_creation_time(const char *path)
|
||||||
{
|
{
|
||||||
// Use stat on macOS to access creation time
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (stat(path, &st) != 0)
|
if (stat(path, &st) != 0)
|
||||||
|
|
@ -214,7 +206,7 @@ double fs_get_creation_time(const char *path)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return st.st_birthtimespec.tv_sec + st.st_birthtimespec.tv_nsec / 1e9;
|
return st.st_mtimespec.tv_sec + st.st_mtimespec.tv_nsec / 1e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
|
||||||
13
src/log.c
13
src/log.c
|
|
@ -19,20 +19,11 @@
|
||||||
* 02110-1301, USA.
|
* 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define __STDC_WANT_LIB_EXT2__ 1 // To access vasprintf
|
#define _GNU_SOURCE // To access vasprintf
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include "options.h"
|
#include <errno.h>
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
#include "error.h"
|
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
|
|
||||||
#define IS_ESC_CSI_INTERMEDIATE_CHAR(c) ((c >= 0x20) && (c <= 0x3F))
|
#define IS_ESC_CSI_INTERMEDIATE_CHAR(c) ((c >= 0x20) && (c <= 0x3F))
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@
|
||||||
* 02110-1301, USA.
|
* 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include "version.h"
|
||||||
|
#include "config.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "configfile.h"
|
#include "configfile.h"
|
||||||
#include "tty.h"
|
#include "tty.h"
|
||||||
|
|
@ -60,6 +61,10 @@ int main(int argc, char *argv[])
|
||||||
/* Configure tty device */
|
/* Configure tty device */
|
||||||
tty_configure();
|
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 */
|
/* Configure input terminal */
|
||||||
if (isatty(fileno(stdin)))
|
if (isatty(fileno(stdin)))
|
||||||
{
|
{
|
||||||
|
|
@ -101,7 +106,7 @@ int main(int argc, char *argv[])
|
||||||
error_enter_session_mode();
|
error_enter_session_mode();
|
||||||
|
|
||||||
/* Print launch hints */
|
/* Print launch hints */
|
||||||
tio_printf("tio v%s", VERSION);
|
tio_printf("tio %s", VERSION);
|
||||||
if (interactive_mode)
|
if (interactive_mode)
|
||||||
{
|
{
|
||||||
tio_printf("Press ctrl-%c q to quit", option.prefix_key);
|
tio_printf("Press ctrl-%c q to quit", option.prefix_key);
|
||||||
|
|
|
||||||
|
|
@ -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 = configuration_data()
|
||||||
config_h.set_quoted('VERSION', meson.project_version())
|
|
||||||
config_h.set('BAUDRATE_CASES', baudrate_cases)
|
config_h.set('BAUDRATE_CASES', baudrate_cases)
|
||||||
configure_file(output: 'config.h', configuration: config_h)
|
configure_file(output: 'config.h', configuration: config_h)
|
||||||
|
|
||||||
|
|
@ -20,7 +25,9 @@ tio_sources = [
|
||||||
'alert.c',
|
'alert.c',
|
||||||
'xymodem.c',
|
'xymodem.c',
|
||||||
'script.c',
|
'script.c',
|
||||||
'fs.c'
|
'fs.c',
|
||||||
|
'readline.c',
|
||||||
|
version_h
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -40,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'
|
||||||
|
|
|
||||||
25
src/misc.c
25
src/misc.c
|
|
@ -20,25 +20,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _GNU_SOURCE // For FNM_EXTMATCH
|
#define _GNU_SOURCE // For FNM_EXTMATCH
|
||||||
#include "config.h"
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <regex.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <poll.h>
|
||||||
#include <time.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/poll.h>
|
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <termios.h>
|
|
||||||
#include <fnmatch.h>
|
#include <fnmatch.h>
|
||||||
#include "error.h"
|
#include <regex.h>
|
||||||
|
#include <errno.h>
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
#include "options.h"
|
|
||||||
|
|
||||||
void delay(long ms)
|
void delay(long ms)
|
||||||
{
|
{
|
||||||
|
|
@ -132,10 +121,9 @@ unsigned long djb2_hash(const unsigned char *str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to encode a number to base62
|
// 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";
|
const char base62_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
char *output = (char *) malloc(5); // 4 characters + null terminator
|
|
||||||
if (output == NULL)
|
if (output == NULL)
|
||||||
{
|
{
|
||||||
tio_error_print("Memory allocation failed");
|
tio_error_print("Memory allocation failed");
|
||||||
|
|
@ -258,3 +246,8 @@ int execute_shell_command(int fd, const char *command)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clear_line()
|
||||||
|
{
|
||||||
|
print("\r\033[K");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,9 @@ void delay(long ms);
|
||||||
int ctrl_key_code(unsigned char key);
|
int ctrl_key_code(unsigned char key);
|
||||||
bool regex_match(const char *string, const char *pattern);
|
bool regex_match(const char *string, const char *pattern);
|
||||||
unsigned long djb2_hash(const unsigned char *str);
|
unsigned long djb2_hash(const unsigned char *str);
|
||||||
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);
|
int read_poll(int fd, void *data, size_t len, int timeout);
|
||||||
double get_current_time(void);
|
double get_current_time(void);
|
||||||
bool match_patterns(const char *string, const char *patterns);
|
bool match_patterns(const char *string, const char *patterns);
|
||||||
int execute_shell_command(int fd, const char *command);
|
int execute_shell_command(int fd, const char *command);
|
||||||
|
void clear_line();
|
||||||
|
|
|
||||||
159
src/options.c
159
src/options.c
|
|
@ -19,29 +19,18 @@
|
||||||
* 02110-1301, USA.
|
* 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#define _GNU_SOURCE // To access vasprintf
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <regex.h>
|
#include <regex.h>
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <termios.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include "version.h"
|
||||||
#include "options.h"
|
#include "config.h"
|
||||||
#include "error.h"
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
#include "tty.h"
|
|
||||||
#include "rs485.h"
|
#include "rs485.h"
|
||||||
#include "timestamp.h"
|
|
||||||
#include "alert.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "script.h"
|
|
||||||
#include "configfile.h"
|
#include "configfile.h"
|
||||||
|
|
||||||
#define HEX_N_VALUE_MAX 4096
|
#define HEX_N_VALUE_MAX 4096
|
||||||
|
|
@ -99,7 +88,6 @@ struct option_t option =
|
||||||
.local_echo = false,
|
.local_echo = false,
|
||||||
.timestamp = TIMESTAMP_NONE,
|
.timestamp = TIMESTAMP_NONE,
|
||||||
.socket = NULL,
|
.socket = NULL,
|
||||||
.map = "",
|
|
||||||
.color = 256, // Bold
|
.color = 256, // Bold
|
||||||
.input_mode = INPUT_MODE_NORMAL,
|
.input_mode = INPUT_MODE_NORMAL,
|
||||||
.output_mode = OUTPUT_MODE_NORMAL,
|
.output_mode = OUTPUT_MODE_NORMAL,
|
||||||
|
|
@ -123,6 +111,19 @@ struct option_t option =
|
||||||
.hex_n_value = 0,
|
.hex_n_value = 0,
|
||||||
.vt100 = false,
|
.vt100 = false,
|
||||||
.exec = NULL,
|
.exec = NULL,
|
||||||
|
.map_i_nl_cr = false,
|
||||||
|
.map_i_cr_nl = false,
|
||||||
|
.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,
|
||||||
|
.map_o_ltu = false,
|
||||||
|
.map_o_nulbrk = false,
|
||||||
|
.map_i_msb2lsb = false,
|
||||||
|
.map_o_ign_cr = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
void option_print_help(char *argv[])
|
void option_print_help(char *argv[])
|
||||||
|
|
@ -170,11 +171,14 @@ 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");
|
||||||
printf("Options and profiles may be set via configuration file.\n");
|
printf("Options and profiles may be set via configuration file.\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
printf("In session you can press ctrl-%c ? to list available key commands.\n", option.prefix_key);
|
||||||
|
printf("\n");
|
||||||
printf("See the man page for more details.\n");
|
printf("See the man page for more details.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,6 +397,14 @@ const char* option_timestamp_format_to_string(timestamp_t timestamp)
|
||||||
return "iso8601";
|
return "iso8601";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TIMESTAMP_EPOCH:
|
||||||
|
return "epoch";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TIMESTAMP_EPOCH_USEC:
|
||||||
|
return "epoch-usec";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "unknown";
|
return "unknown";
|
||||||
break;
|
break;
|
||||||
|
|
@ -419,6 +431,14 @@ void option_parse_timestamp(const char *arg, timestamp_t *timestamp)
|
||||||
{
|
{
|
||||||
*timestamp = TIMESTAMP_ISO8601;
|
*timestamp = TIMESTAMP_ISO8601;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(arg, "epoch") == 0)
|
||||||
|
{
|
||||||
|
*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);
|
||||||
|
|
@ -706,6 +726,98 @@ void option_parse_script_run(const char *arg, script_run_t *script_run)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void option_parse_mappings(const char *map)
|
||||||
|
{
|
||||||
|
bool token_found = true;
|
||||||
|
char *token = NULL;
|
||||||
|
char *buffer;
|
||||||
|
|
||||||
|
if (map == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse any specified input or output mappings */
|
||||||
|
buffer = strdup(map);
|
||||||
|
while (token_found == true)
|
||||||
|
{
|
||||||
|
if (token == NULL)
|
||||||
|
{
|
||||||
|
token = strtok(buffer,",");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token = strtok(NULL, ",");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token != NULL)
|
||||||
|
{
|
||||||
|
if (strcmp(token,"INLCR") == 0)
|
||||||
|
{
|
||||||
|
option.map_i_nl_cr = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token,"IGNCR") == 0)
|
||||||
|
{
|
||||||
|
option.map_ign_cr = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token,"ICRNL") == 0)
|
||||||
|
{
|
||||||
|
option.map_i_cr_nl = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token,"OCRNL") == 0)
|
||||||
|
{
|
||||||
|
option.map_o_cr_nl = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token,"ODELBS") == 0)
|
||||||
|
{
|
||||||
|
option.map_o_del_bs = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token,"IFFESCC") == 0)
|
||||||
|
{
|
||||||
|
option.map_i_ff_escc = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token,"INLCRNL") == 0)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else if (strcmp(token, "OLTU") == 0)
|
||||||
|
{
|
||||||
|
option.map_o_ltu = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token, "ONULBRK") == 0)
|
||||||
|
{
|
||||||
|
option.map_o_nulbrk = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token, "OIGNCR") == 0)
|
||||||
|
{
|
||||||
|
option.map_o_ign_cr = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(token, "IMSB2LSB") == 0)
|
||||||
|
{
|
||||||
|
option.map_i_msb2lsb = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Error: Unknown mapping flag %s\n", token);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token_found = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
void options_print()
|
void options_print()
|
||||||
{
|
{
|
||||||
tio_printf(" Device: %s", device_name);
|
tio_printf(" Device: %s", device_name);
|
||||||
|
|
@ -730,10 +842,6 @@ void options_print()
|
||||||
tio_printf(" Input mode: %s", option_input_mode_to_string(option.input_mode));
|
tio_printf(" Input mode: %s", option_input_mode_to_string(option.input_mode));
|
||||||
tio_printf(" Output mode: %s", option_output_mode_to_string(option.output_mode));
|
tio_printf(" Output mode: %s", option_output_mode_to_string(option.output_mode));
|
||||||
tio_printf(" Alert: %s", option_alert_state_to_string(option.alert));
|
tio_printf(" Alert: %s", option_alert_state_to_string(option.alert));
|
||||||
if (option.map[0] != 0)
|
|
||||||
{
|
|
||||||
tio_printf(" Map flags: %s", option.map);
|
|
||||||
}
|
|
||||||
if (option.log)
|
if (option.log)
|
||||||
{
|
{
|
||||||
tio_printf(" Log file: %s", log_get_filename());
|
tio_printf(" Log file: %s", log_get_filename());
|
||||||
|
|
@ -904,7 +1012,10 @@ void options_parse(int argc, char *argv[])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 't':
|
case 't':
|
||||||
option.timestamp = TIMESTAMP_24HOUR;
|
if (option.timestamp == TIMESTAMP_NONE)
|
||||||
|
{
|
||||||
|
option.timestamp = TIMESTAMP_24HOUR;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPT_TIMESTAMP_FORMAT:
|
case OPT_TIMESTAMP_FORMAT:
|
||||||
|
|
@ -946,7 +1057,7 @@ void options_parse(int argc, char *argv[])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'm':
|
case 'm':
|
||||||
option.map = optarg;
|
option_parse_mappings(optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'c':
|
case 'c':
|
||||||
|
|
@ -994,7 +1105,7 @@ void options_parse(int argc, char *argv[])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
printf("tio v%s\n", VERSION);
|
printf("tio %s\n", VERSION);
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <limits.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#include <sys/param.h>
|
|
||||||
#include "script.h"
|
#include "script.h"
|
||||||
#include "timestamp.h"
|
#include "timestamp.h"
|
||||||
#include "alert.h"
|
#include "alert.h"
|
||||||
|
|
@ -72,7 +69,6 @@ struct option_t
|
||||||
timestamp_t timestamp;
|
timestamp_t timestamp;
|
||||||
char *log_filename;
|
char *log_filename;
|
||||||
char *log_directory;
|
char *log_directory;
|
||||||
char *map;
|
|
||||||
char *socket;
|
char *socket;
|
||||||
int color;
|
int color;
|
||||||
input_mode_t input_mode;
|
input_mode_t input_mode;
|
||||||
|
|
@ -97,6 +93,19 @@ struct option_t
|
||||||
int hex_n_value;
|
int hex_n_value;
|
||||||
bool vt100;
|
bool vt100;
|
||||||
char *exec;
|
char *exec;
|
||||||
|
bool map_i_nl_cr;
|
||||||
|
bool map_i_cr_nl;
|
||||||
|
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;
|
||||||
|
bool map_o_ltu;
|
||||||
|
bool map_o_nulbrk;
|
||||||
|
bool map_i_msb2lsb;
|
||||||
|
bool map_o_ign_cr;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct option_t option;
|
extern struct option_t option;
|
||||||
|
|
@ -122,3 +131,5 @@ const char *option_auto_connect_state_to_string(auto_connect_t strategy);
|
||||||
|
|
||||||
void option_parse_timestamp(const char *arg, timestamp_t *timestamp);
|
void option_parse_timestamp(const char *arg, timestamp_t *timestamp);
|
||||||
const char* option_timestamp_format_to_string(timestamp_t timestamp);
|
const char* option_timestamp_format_to_string(timestamp_t timestamp);
|
||||||
|
|
||||||
|
void option_parse_mappings(const char *map);
|
||||||
|
|
|
||||||
40
src/print.c
40
src/print.c
|
|
@ -19,11 +19,6 @@
|
||||||
* 02110-1301, USA.
|
* 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "options.h"
|
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
|
|
||||||
bool print_tainted = false;
|
bool print_tainted = false;
|
||||||
|
|
@ -80,3 +75,38 @@ void print_tainted_set()
|
||||||
{
|
{
|
||||||
print_tainted = true;
|
print_tainted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void print(const char *format, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
va_start(args, format);
|
||||||
|
vprintf(format, args);
|
||||||
|
fflush(stdout);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
print_tainted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_padded(char *string, size_t length, char pad_char)
|
||||||
|
{
|
||||||
|
size_t padding = 0;
|
||||||
|
size_t string_length = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
string_length = strlen(string);
|
||||||
|
|
||||||
|
if (string_length < length)
|
||||||
|
{
|
||||||
|
padding += length - string_length;
|
||||||
|
printf("%s", string);
|
||||||
|
for (i=0; i<padding; i++)
|
||||||
|
{
|
||||||
|
putchar(pad_char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("%s", string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "misc.h"
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "timestamp.h"
|
#include "timestamp.h"
|
||||||
|
|
@ -135,8 +134,10 @@ extern char ansi_format[];
|
||||||
#define tio_debug_printf_raw(format, args...)
|
#define tio_debug_printf_raw(format, args...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void print(const char *format, ...);
|
||||||
void print_hex(char c);
|
void print_hex(char c);
|
||||||
void print_normal(char c);
|
void print_normal(char c);
|
||||||
void print_init_ansi_formatting(void);
|
void print_init_ansi_formatting(void);
|
||||||
void tio_printf_array(const char *array);
|
void tio_printf_array(const char *array);
|
||||||
void print_tainted_set(void);
|
void print_tainted_set(void);
|
||||||
|
void print_padded(char *string, size_t length, char pad_char);
|
||||||
|
|
|
||||||
276
src/readline.c
Normal file
276
src/readline.c
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
/*
|
||||||
|
* tio - a serial device I/O tool
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2024 Martin Lund
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "print.h"
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
|
#define RL_LINE_LENGTH_MAX PATH_MAX
|
||||||
|
#define RL_HISTORY_MAX 1000
|
||||||
|
|
||||||
|
static char rl_line[RL_LINE_LENGTH_MAX] = {};
|
||||||
|
static char *rl_history[RL_HISTORY_MAX];
|
||||||
|
static int rl_history_count = 0;
|
||||||
|
static int rl_history_index = 0;
|
||||||
|
static int rl_line_length = 0;
|
||||||
|
static int rl_cursor_pos = 0;
|
||||||
|
static int rl_escape = 0;
|
||||||
|
|
||||||
|
static void print_line(const char *string, int cursor_pos)
|
||||||
|
{
|
||||||
|
clear_line();
|
||||||
|
print("%s", string);
|
||||||
|
print("\r"); // Move the cursor back to the beginning
|
||||||
|
for (int i = 0; i < cursor_pos; ++i)
|
||||||
|
{
|
||||||
|
print("\x1b[C"); // Move the cursor right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void readline_init(void)
|
||||||
|
{
|
||||||
|
rl_history_count = 0;
|
||||||
|
rl_history_index = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < RL_HISTORY_MAX; ++i)
|
||||||
|
{
|
||||||
|
rl_history[i] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
rl_line[0] = 0;
|
||||||
|
rl_line_length = 0;
|
||||||
|
rl_cursor_pos = 0;
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char * readline_get(void)
|
||||||
|
{
|
||||||
|
return rl_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_char(char input_char)
|
||||||
|
{
|
||||||
|
if (rl_line_length < RL_LINE_LENGTH_MAX - 1)
|
||||||
|
{
|
||||||
|
memmove(&rl_line[rl_cursor_pos + 1], &rl_line[rl_cursor_pos], rl_line_length - rl_cursor_pos);
|
||||||
|
rl_line[rl_cursor_pos] = input_char;
|
||||||
|
rl_line_length++;
|
||||||
|
rl_cursor_pos++;
|
||||||
|
rl_line[rl_line_length] = '\0';
|
||||||
|
print_line(rl_line, rl_cursor_pos);
|
||||||
|
}
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_cr(void)
|
||||||
|
{
|
||||||
|
if (rl_line_length > 0)
|
||||||
|
{
|
||||||
|
// Save to history
|
||||||
|
if (rl_history_count < RL_HISTORY_MAX)
|
||||||
|
{
|
||||||
|
rl_history[rl_history_count] = strndup(rl_line, rl_line_length);
|
||||||
|
rl_history_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
free(rl_history[0]);
|
||||||
|
memmove(&rl_history[0], &rl_history[1], (RL_HISTORY_MAX - 1) * sizeof(char*));
|
||||||
|
rl_history[RL_HISTORY_MAX - 1] = strndup(rl_line, rl_line_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rl_line[rl_line_length] = '\0';
|
||||||
|
if (option.local_echo == false)
|
||||||
|
{
|
||||||
|
clear_line();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
rl_line_length = 0;
|
||||||
|
rl_cursor_pos = 0;
|
||||||
|
rl_history_index = rl_history_count;
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_bs(void)
|
||||||
|
{
|
||||||
|
if (rl_cursor_pos > 0)
|
||||||
|
{
|
||||||
|
memmove(&rl_line[rl_cursor_pos - 1], &rl_line[rl_cursor_pos], rl_line_length - rl_cursor_pos);
|
||||||
|
rl_line_length--;
|
||||||
|
rl_cursor_pos--;
|
||||||
|
rl_line[rl_line_length] = '\0';
|
||||||
|
print_line(rl_line, rl_cursor_pos);
|
||||||
|
}
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_escape(void)
|
||||||
|
{
|
||||||
|
rl_escape = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_left_bracket(void)
|
||||||
|
{
|
||||||
|
if (rl_escape == 1)
|
||||||
|
{
|
||||||
|
rl_escape = 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_A(void)
|
||||||
|
{
|
||||||
|
if (rl_escape == 2)
|
||||||
|
{
|
||||||
|
// Up arrow
|
||||||
|
if (rl_history_index > 0)
|
||||||
|
{
|
||||||
|
rl_history_index--;
|
||||||
|
strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1);
|
||||||
|
rl_line_length = strlen(rl_line);
|
||||||
|
rl_cursor_pos = rl_line_length;
|
||||||
|
print_line(rl_line, rl_cursor_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readline_input_char('A');
|
||||||
|
}
|
||||||
|
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_B(void)
|
||||||
|
{
|
||||||
|
if (rl_escape == 2)
|
||||||
|
{
|
||||||
|
// Down arrow
|
||||||
|
if (rl_history_index < rl_history_count - 1)
|
||||||
|
{
|
||||||
|
rl_history_index++;
|
||||||
|
strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1);
|
||||||
|
rl_line_length = strlen(rl_line);
|
||||||
|
rl_cursor_pos = rl_line_length;
|
||||||
|
print_line(rl_line, rl_cursor_pos);
|
||||||
|
}
|
||||||
|
else if (rl_history_index == rl_history_count - 1)
|
||||||
|
{
|
||||||
|
rl_history_index++;
|
||||||
|
rl_line_length = 0;
|
||||||
|
rl_cursor_pos = 0;
|
||||||
|
rl_line[rl_line_length] = '\0';
|
||||||
|
print_line(rl_line, rl_cursor_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readline_input_char('B');
|
||||||
|
}
|
||||||
|
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_C(void)
|
||||||
|
{
|
||||||
|
if (rl_escape == 2)
|
||||||
|
{
|
||||||
|
// Right arrow
|
||||||
|
if (rl_cursor_pos < rl_line_length)
|
||||||
|
{
|
||||||
|
rl_cursor_pos++;
|
||||||
|
print("\x1b[C");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readline_input_char('C');
|
||||||
|
}
|
||||||
|
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_input_D(void)
|
||||||
|
{
|
||||||
|
if (rl_escape == 2)
|
||||||
|
{
|
||||||
|
// Left arrow
|
||||||
|
if (rl_cursor_pos > 0)
|
||||||
|
{
|
||||||
|
rl_cursor_pos--;
|
||||||
|
print("\b");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readline_input_char('D');
|
||||||
|
}
|
||||||
|
|
||||||
|
rl_escape = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void readline_input(char input_char)
|
||||||
|
{
|
||||||
|
switch (input_char)
|
||||||
|
{
|
||||||
|
case '\r': // Carriage return
|
||||||
|
readline_input_cr();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 127: // Backspace
|
||||||
|
readline_input_bs();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 27: // Escape
|
||||||
|
readline_input_escape();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
readline_input_left_bracket();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'A':
|
||||||
|
readline_input_A();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'B':
|
||||||
|
readline_input_B();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'C':
|
||||||
|
readline_input_C();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D':
|
||||||
|
readline_input_D();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
readline_input_char(input_char);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/readline.h
Normal file
26
src/readline.h
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* tio - a serial device I/O tool
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2024 Martin Lund
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void readline_init(void);
|
||||||
|
void readline_input(char input_char);
|
||||||
|
char * readline_get(void);
|
||||||
|
|
@ -19,14 +19,13 @@
|
||||||
* 02110-1301, USA.
|
* 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <stdbool.h>
|
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
#include "error.h"
|
#include "misc.h"
|
||||||
|
|
||||||
#ifdef HAVE_RS485
|
#ifdef HAVE_RS485
|
||||||
|
|
||||||
|
|
|
||||||
469
src/script.c
469
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>
|
||||||
|
|
@ -29,6 +28,7 @@
|
||||||
#include <lauxlib.h>
|
#include <lauxlib.h>
|
||||||
#include <lualib.h>
|
#include <lualib.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
|
@ -37,27 +37,94 @@
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "script.h"
|
#include "script.h"
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
|
#include "timestamp.h"
|
||||||
|
#include "termios.h"
|
||||||
|
|
||||||
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
|
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
|
||||||
|
#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);
|
||||||
|
|
||||||
|
|
@ -73,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;
|
||||||
|
|
@ -90,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] = { };
|
||||||
|
|
@ -144,11 +211,12 @@ 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;
|
||||||
|
|
||||||
if (file == NULL)
|
if (file == NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -159,227 +227,138 @@ static int modem_send(lua_State *L)
|
||||||
{
|
{
|
||||||
case XMODEM_1K:
|
case XMODEM_1K:
|
||||||
tio_printf("Sending file '%s' using XMODEM-1K", file);
|
tio_printf("Sending file '%s' using XMODEM-1K", file);
|
||||||
tio_printf("%s", xymodem_send(device_fd, file, XMODEM_1K) < 0 ? "Aborted" : "Done");
|
ret = xymodem_send(device_fd, file, XMODEM_1K);
|
||||||
|
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case XMODEM_CRC:
|
case XMODEM_CRC:
|
||||||
tio_printf("Sending file '%s' using XMODEM-CRC", file);
|
tio_printf("Sending file '%s' using XMODEM-CRC", file);
|
||||||
tio_printf("%s", xymodem_send(device_fd, file, XMODEM_CRC) < 0 ? "Aborted" : "Done");
|
ret = xymodem_send(device_fd, file, XMODEM_CRC);
|
||||||
break;
|
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
|
||||||
|
break;
|
||||||
|
|
||||||
case YMODEM:
|
case YMODEM:
|
||||||
tio_printf("Sending file '%s' using YMODEM", file);
|
tio_printf("Sending file '%s' using YMODEM", file);
|
||||||
tio_printf("%s", xymodem_send(device_fd, file, YMODEM) < 0 ? "Aborted" : "Done");
|
ret = xymodem_send(device_fd, file, YMODEM);
|
||||||
break;
|
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lua: send(string)
|
// lua: tio.write(string)
|
||||||
static int send(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));
|
||||||
|
|
||||||
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;
|
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 (buffer_size < MAX_BUFFER_SIZE)
|
int size = luaL_checkinteger(L, 1);
|
||||||
{
|
|
||||||
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 timeout = lua_tointeger(L, 2);
|
int timeout = lua_tointeger(L, 2);
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
char *buffer = malloc(size);
|
|
||||||
if (buffer == NULL)
|
|
||||||
{
|
|
||||||
ret = -1; // Error
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
lua_pop(L, 1);
|
||||||
|
lua_pushnil(L);
|
||||||
}
|
}
|
||||||
else if (bytes_read == 0)
|
else
|
||||||
{
|
{
|
||||||
ret = 0; // Timeout
|
maybe_echo(L);
|
||||||
goto error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ssize_t i=0; i<bytes_read; i++)
|
return 1;
|
||||||
{
|
|
||||||
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)
|
// lua: string = tio.readline(timeout)
|
||||||
static int expect(lua_State *L)
|
static int api_readline(lua_State *L) {
|
||||||
{
|
int timeout = lua_tointeger(L, 1); //ms
|
||||||
const char *string = lua_tostring(L, 1);
|
luaL_Buffer b;
|
||||||
long timeout = lua_tointeger(L, 2);
|
char ch;
|
||||||
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)
|
if (timeout == 0)
|
||||||
{
|
{
|
||||||
// Let poll() wait forever
|
timeout = -1; // Wait forever
|
||||||
timeout = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the regular expression
|
luaL_buffinit(L, &b);
|
||||||
ret = regcomp(®ex, string, REG_EXTENDED);
|
luaL_prepbuffer(&b);
|
||||||
if (ret)
|
while (true) {
|
||||||
{
|
int ret = read_poll(device_fd, &ch, 1, timeout);
|
||||||
tio_error_print("Could not compile regex");
|
|
||||||
ret = -1;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main loop to read and match
|
if (ret < 0)
|
||||||
while (true)
|
return luaL_error(L, "%s", strerror(errno));
|
||||||
{
|
|
||||||
ssize_t bytes_read = read_poll(device_fd, &c, 1, timeout);
|
if (ret == 0)
|
||||||
if (bytes_read > 0)
|
|
||||||
{
|
{
|
||||||
putchar(c);
|
luaL_pushresult(&b);
|
||||||
expect_buffer_add(c);
|
maybe_echo(L);
|
||||||
|
lua_pushnil(L);
|
||||||
if (option.log)
|
lua_insert(L, -2);
|
||||||
{
|
return 2;
|
||||||
log_putc(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match against the entire buffer
|
|
||||||
if (match_regex(®ex))
|
|
||||||
{
|
|
||||||
ret = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (ch == '\n')
|
||||||
{
|
{
|
||||||
// Timeout or error
|
luaL_pushresult(&b);
|
||||||
break;
|
maybe_echo(L);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
luaL_addchar(&b, ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
regfree(®ex);
|
|
||||||
|
|
||||||
error:
|
|
||||||
lua_pushnumber(L, ret);
|
|
||||||
lua_pushstring(L, match_string);
|
|
||||||
return 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lua: exit(code)
|
// lua: table = tio.ttysearch()
|
||||||
static int exit_(lua_State *L)
|
static int api_ttysearch(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;
|
||||||
|
|
@ -445,39 +424,36 @@ static void script_buffer_run(lua_State *L, const char *script_buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void script_file_run(lua_State *L, const char *filename)
|
||||||
|
{
|
||||||
|
if (strlen(filename) == 0)
|
||||||
|
{
|
||||||
|
tio_warning_printf("Missing script filename\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (luaL_dofile(L, filename))
|
||||||
|
{
|
||||||
|
tio_warning_printf("lua: %s", lua_tostring(L, -1));
|
||||||
|
lua_pop(L, 1); /* pop error message from the stack */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static const struct luaL_Reg tio_lib[] =
|
static const struct luaL_Reg tio_lib[] =
|
||||||
{
|
{
|
||||||
{ "sleep", sleep_},
|
{ "echo", api_echo},
|
||||||
{ "msleep", msleep},
|
{ "sleep", api_sleep},
|
||||||
|
{ "msleep", api_msleep},
|
||||||
{ "line_set", line_set},
|
{ "line_set", line_set},
|
||||||
{ "modem_send", modem_send},
|
{ "send", api_send},
|
||||||
{ "send", send},
|
{ "write", api_write},
|
||||||
{ "read", read_string},
|
{ "read", api_read},
|
||||||
{ "expect", expect},
|
{ "readline", api_readline},
|
||||||
{ "exit", exit_},
|
{ "ttysearch", api_ttysearch},
|
||||||
{ "tty_search", tty_search_},
|
|
||||||
{NULL, NULL}
|
{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)
|
static void script_load(lua_State *L)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
|
@ -490,42 +466,13 @@ static void script_load(lua_State *L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int lua_register_tio(lua_State *L)
|
static void script_set_global(lua_State *L, const char *name, long value)
|
||||||
{
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
tio_warning_printf("Missing script filename\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (luaL_dofile(L, filename))
|
|
||||||
{
|
|
||||||
tio_warning_printf("lua: %s\n", lua_tostring(L, -1));
|
|
||||||
lua_pop(L, 1); /* pop error message from the stack */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -535,7 +482,15 @@ void script_set_globals(lua_State *L)
|
||||||
script_set_global(L, "YMODEM", YMODEM);
|
script_set_global(L, "YMODEM", YMODEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
void script_run(int fd)
|
#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;
|
lua_State *L;
|
||||||
|
|
||||||
|
|
@ -544,13 +499,25 @@ void script_run(int fd)
|
||||||
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);
|
||||||
|
|
||||||
if (option.script_filename != NULL)
|
if (script_filename != NULL)
|
||||||
|
{
|
||||||
|
tio_printf("Running script %s", script_filename);
|
||||||
|
script_file_run(L, script_filename);
|
||||||
|
}
|
||||||
|
else if (option.script_filename != NULL)
|
||||||
{
|
{
|
||||||
tio_printf("Running script %s", option.script_filename);
|
tio_printf("Running script %s", option.script_filename);
|
||||||
script_file_run(L, option.script_filename);
|
script_file_run(L, option.script_filename);
|
||||||
|
|
|
||||||
|
|
@ -29,5 +29,5 @@ typedef enum
|
||||||
SCRIPT_RUN_END,
|
SCRIPT_RUN_END,
|
||||||
} script_run_t;
|
} script_run_t;
|
||||||
|
|
||||||
void script_run(int fd);
|
void script_run(int fd, const char *script_filename);
|
||||||
const char *script_run_state_to_string(script_run_t state);
|
const char *script_run_state_to_string(script_run_t state);
|
||||||
|
|
|
||||||
24
src/socket.c
24
src/socket.c
|
|
@ -124,6 +124,7 @@ void socket_configure(void)
|
||||||
struct sockaddr_in6 sockaddr_inet6 = {};
|
struct sockaddr_in6 sockaddr_inet6 = {};
|
||||||
struct sockaddr *sockaddr_p;
|
struct sockaddr *sockaddr_p;
|
||||||
socklen_t socklen;
|
socklen_t socklen;
|
||||||
|
int optval;
|
||||||
|
|
||||||
/* Parse socket string */
|
/* Parse socket string */
|
||||||
|
|
||||||
|
|
@ -225,6 +226,16 @@ void socket_configure(void)
|
||||||
exit(EXIT_FAILURE);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/* Bind */
|
/* Bind */
|
||||||
if (bind(sockfd, sockaddr_p, socklen) < 0)
|
if (bind(sockfd, sockaddr_p, socklen) < 0)
|
||||||
{
|
{
|
||||||
|
|
@ -263,7 +274,12 @@ void socket_write(char input_char)
|
||||||
{
|
{
|
||||||
if (clientfds[i] != -1)
|
if (clientfds[i] != -1)
|
||||||
{
|
{
|
||||||
if (write(clientfds[i], &input_char, 1) <= 0)
|
|
||||||
|
#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));
|
tio_error_printf_silent("Failed to write to socket (%s)", strerror(errno));
|
||||||
close(clientfds[i]);
|
close(clientfds[i]);
|
||||||
|
|
@ -343,20 +359,20 @@ bool socket_handle_input(fd_set *rdfs, char *output_char)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If INLCR is set, a received NL character shall be translated into a CR character */
|
/* If INLCR is set, a received NL character shall be translated into a CR character */
|
||||||
if (*output_char == '\n' && map_i_nl_cr)
|
if (*output_char == '\n' && option.map_i_nl_cr)
|
||||||
{
|
{
|
||||||
*output_char = '\r';
|
*output_char = '\r';
|
||||||
}
|
}
|
||||||
else if (*output_char == '\r')
|
else if (*output_char == '\r')
|
||||||
{
|
{
|
||||||
/* If IGNCR is set, a received CR character shall be ignored (not read). */
|
/* If IGNCR is set, a received CR character shall be ignored (not read). */
|
||||||
if (map_ign_cr)
|
if (option.map_ign_cr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If IGNCR is not set and ICRNL is set, a received CR character shall be translated into an NL character. */
|
/* If IGNCR is not set and ICRNL is set, a received CR character shall be translated into an NL character. */
|
||||||
if (map_i_cr_nl)
|
if (option.map_i_cr_nl)
|
||||||
{
|
{
|
||||||
*output_char = '\n';
|
*output_char = '\n';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
|
@ -31,8 +29,6 @@
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "timestamp.h"
|
#include "timestamp.h"
|
||||||
|
|
||||||
#define TIME_STRING_SIZE_MAX 24
|
|
||||||
|
|
||||||
char *timestamp_current_time(void)
|
char *timestamp_current_time(void)
|
||||||
{
|
{
|
||||||
static char time_string[TIME_STRING_SIZE_MAX];
|
static char time_string[TIME_STRING_SIZE_MAX];
|
||||||
|
|
@ -78,16 +74,29 @@ char *timestamp_current_time(void)
|
||||||
tm = localtime(&tv.tv_sec);
|
tm = localtime(&tv.tv_sec);
|
||||||
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_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:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append milliseconds to all timestamps
|
// Append millis-/microseconds to all timestamps
|
||||||
if (len)
|
if (len)
|
||||||
{
|
{
|
||||||
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%03ld", (long)tv.tv_usec / 1000);
|
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
|
// Save previous time value for next run
|
||||||
tv_previous = tv_now;
|
tv_previous = tv_now;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,12 @@ typedef enum
|
||||||
TIMESTAMP_24HOUR_START,
|
TIMESTAMP_24HOUR_START,
|
||||||
TIMESTAMP_24HOUR_DELTA,
|
TIMESTAMP_24HOUR_DELTA,
|
||||||
TIMESTAMP_ISO8601,
|
TIMESTAMP_ISO8601,
|
||||||
|
TIMESTAMP_EPOCH,
|
||||||
|
TIMESTAMP_EPOCH_USEC,
|
||||||
TIMESTAMP_END,
|
TIMESTAMP_END,
|
||||||
} timestamp_t;
|
} timestamp_t;
|
||||||
|
|
||||||
|
#define TIME_STRING_SIZE_MAX 24
|
||||||
|
|
||||||
char *timestamp_current_time(void);
|
char *timestamp_current_time(void);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,13 +71,11 @@ typedef struct
|
||||||
|
|
||||||
extern const char *device_name;
|
extern const char *device_name;
|
||||||
extern bool interactive_mode;
|
extern bool interactive_mode;
|
||||||
extern bool map_i_nl_cr;
|
|
||||||
extern bool map_i_cr_nl;
|
|
||||||
extern bool map_ign_cr;
|
|
||||||
|
|
||||||
void stdout_configure(void);
|
void stdout_configure(void);
|
||||||
void stdin_configure(void);
|
void stdin_configure(void);
|
||||||
void tty_configure(void);
|
void tty_configure(void);
|
||||||
|
void tty_reconfigure(void);
|
||||||
int tty_connect(void);
|
int tty_connect(void);
|
||||||
void tty_wait_for_device(void);
|
void tty_wait_for_device(void);
|
||||||
void list_serial_devices(void);
|
void list_serial_devices(void);
|
||||||
|
|
|
||||||
3
src/version.h.in
Normal file
3
src/version.h.in
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define VERSION "@VERSION@"
|
||||||
401
src/xymodem.c
401
src/xymodem.c
|
|
@ -7,30 +7,36 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <fcntl.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <termios.h>
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/poll.h>
|
#include <poll.h>
|
||||||
#include <termios.h>
|
|
||||||
#include "xymodem.h"
|
#include "xymodem.h"
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
#define SOH 0x01
|
#define SOH 0x01
|
||||||
#define STX 0x02
|
#define STX 0x02
|
||||||
#define ACK 0x06
|
#define ACK 0x06
|
||||||
#define NAK 0x15
|
#define NAK 0x15
|
||||||
#define CAN 0x18
|
#define CAN 0x18
|
||||||
#define EOT "\004"
|
#define EOT 0x04
|
||||||
|
|
||||||
|
#define SOH_STR "\001"
|
||||||
|
#define ACK_STR "\006"
|
||||||
|
#define NAK_STR "\025"
|
||||||
|
#define CAN_STR "\030"
|
||||||
|
#define EOT_STR "\004"
|
||||||
|
|
||||||
#define OK 0
|
#define OK 0
|
||||||
#define ERR (-1)
|
#define ERR (-1)
|
||||||
|
#define ERR_FATAL (-2)
|
||||||
|
#define USER_CAN (-5)
|
||||||
|
|
||||||
|
#define RX_IGNORE 5
|
||||||
|
|
||||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
|
||||||
|
|
@ -166,7 +172,7 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq)
|
||||||
while (seq) {
|
while (seq) {
|
||||||
if (key_hit)
|
if (key_hit)
|
||||||
return ERR;
|
return ERR;
|
||||||
if (write(sio, EOT, 1) < 0) {
|
if (write(sio, EOT_STR, 1) < 0) {
|
||||||
tio_error_print("Write EOT to serial failed");
|
tio_error_print("Write EOT to serial failed");
|
||||||
return ERR;
|
return ERR;
|
||||||
}
|
}
|
||||||
|
|
@ -285,7 +291,7 @@ static int xmodem(int sio, const void *data, size_t len)
|
||||||
while (1) {
|
while (1) {
|
||||||
if (key_hit)
|
if (key_hit)
|
||||||
return ERR;
|
return ERR;
|
||||||
if (write(sio, EOT, 1) < 0) {
|
if (write(sio, EOT_STR, 1) < 0) {
|
||||||
tio_error_print("Write EOT to serial failed");
|
tio_error_print("Write EOT to serial failed");
|
||||||
return ERR;
|
return ERR;
|
||||||
}
|
}
|
||||||
|
|
@ -306,6 +312,345 @@ static int xmodem(int sio, const void *data, size_t len)
|
||||||
return 0; /* not reached */
|
return 0; /* not reached */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int start_receive(int sio)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
struct pollfd fds;
|
||||||
|
fds.events = POLLIN;
|
||||||
|
fds.fd = sio;
|
||||||
|
for (int n = 0; n < 20; n++)
|
||||||
|
{
|
||||||
|
/* Send the 'C' byte until the sender of the file responds with
|
||||||
|
something. The start character will be sent once a second for a number of
|
||||||
|
seconds. If nothing is received in that time then return false to indicate
|
||||||
|
that the transfer did not start. */
|
||||||
|
rc = write(sio, "C", 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
if (errno == EWOULDBLOCK) {
|
||||||
|
usleep(1000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tio_error_print("Write packet to serial failed");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
/* Wait until data is available */
|
||||||
|
rc = poll(&fds, 1, 3000);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("%s", strerror(errno));
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
else if (rc > 0)
|
||||||
|
{
|
||||||
|
if (fds.revents & POLLIN)
|
||||||
|
{
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t update_CRC(uint16_t crc, char data_char)
|
||||||
|
{
|
||||||
|
uint8_t data = data_char;
|
||||||
|
crc = crc ^ ((uint16_t)data << 8);
|
||||||
|
for (int ix = 0; (ix < 8); ix++)
|
||||||
|
{
|
||||||
|
if (crc & 0x8000)
|
||||||
|
{
|
||||||
|
crc = (crc << 1) ^ 0x1021;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int receive_packet(int sio, struct xpacket packet, int fd)
|
||||||
|
{
|
||||||
|
char rxSeq1, rxSeq2 = 0;
|
||||||
|
char resp = 0;
|
||||||
|
uint16_t calcCrc = 0;
|
||||||
|
uint16_t rxCrc = 0;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
struct pollfd fds;
|
||||||
|
fds.events = POLLIN;
|
||||||
|
fds.fd = sio;
|
||||||
|
|
||||||
|
/* Read seq bytes*/
|
||||||
|
rc = read_poll(sio, &rxSeq1, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for first seq byte");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading first seq byte")
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
rc = read_poll(sio, &rxSeq2, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for second seq byte");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading second seq byte")
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
|
||||||
|
/* Read packet Data */
|
||||||
|
for (unsigned ix = 0; (ix < sizeof(packet.data)); ix++)
|
||||||
|
{
|
||||||
|
rc = read_poll(sio, &resp, 1, 3000);
|
||||||
|
/* If the read times out or fails then fail this packet. */
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Timeout waiting for next packet char");
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write cancel packet to serial failed");
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading next packet char")
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write cancel packet to serial failed");
|
||||||
|
}
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
packet.data[ix] = (uint8_t) resp;
|
||||||
|
calcCrc = update_CRC(calcCrc, resp);
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read CRC */
|
||||||
|
rc = read_poll(sio, &resp, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for first CRC byte");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading first CRC byte")
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t uresp = resp;
|
||||||
|
uint16_t uresp16 = uresp;
|
||||||
|
rxCrc = uresp16 << 8;
|
||||||
|
|
||||||
|
rc = read_poll(sio, &resp, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for second CRC byte");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading second CRC byte")
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uresp = resp;
|
||||||
|
uresp16 = uresp;
|
||||||
|
rxCrc |= uresp16;
|
||||||
|
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
|
||||||
|
/* At this point in the code, there should not be anything in the receive buffer
|
||||||
|
because the sender has just sent a complete packet and is waiting on a response. */
|
||||||
|
rc = poll(&fds, 1, 10);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("%s", strerror(errno));
|
||||||
|
tio_error_print("Poll check error after packet finish");
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write cancel packet to serial failed");
|
||||||
|
}
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
else if (rc > 0)
|
||||||
|
{
|
||||||
|
if (fds.revents & POLLIN)
|
||||||
|
{
|
||||||
|
tio_error_print("RX sync error");
|
||||||
|
char dummy = 0;
|
||||||
|
/* Drain buffer */
|
||||||
|
while (read_poll(sio, &dummy, 1, 100) > 0) {}
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t tester = 0xff;
|
||||||
|
uint8_t seq1 = rxSeq1;
|
||||||
|
uint8_t seq2 = rxSeq2;
|
||||||
|
|
||||||
|
if ((calcCrc == rxCrc) && (seq1 == packet.seq - 1) && ((seq1 ^ seq2) == tester))
|
||||||
|
{
|
||||||
|
/* Resend of previously processed packet. */
|
||||||
|
rc = write(sio, ACK_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write acknowlegdement packet to serial failed");
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
return RX_IGNORE;
|
||||||
|
}
|
||||||
|
else if ((calcCrc != rxCrc) || (seq1 != packet.seq) || ((seq1 ^ seq2) != tester))
|
||||||
|
{
|
||||||
|
/* Fail if the CRC or sequence number is not correct or if the two received
|
||||||
|
sequence numbers are not the complement of one another. */
|
||||||
|
tio_error_print("Bad CRC or sequence number");
|
||||||
|
tio_debug_printf("CRC read: %u", rxCrc);
|
||||||
|
tio_debug_printf("CRC calculated: %u", calcCrc);
|
||||||
|
tio_debug_printf("Seq read: %hhu", rxSeq1);
|
||||||
|
tio_debug_printf("Seq should be: %hhu", packet.seq);
|
||||||
|
tio_debug_printf("inv seq: %hhu", rxSeq2);
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* The data is good. Process the packet then ACK it to the sender. */
|
||||||
|
rc = write(fd, packet.data, sizeof(packet.data));
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Problem writing to file");
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write cancel packet to serial failed");
|
||||||
|
}
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
rc = write(sio, ACK_STR, 1);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Write acknowlegdement packet to serial failed");
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xmodem_receive(int sio, int fd)
|
||||||
|
{
|
||||||
|
struct xpacket packet;
|
||||||
|
char resp = 0;
|
||||||
|
int rc;
|
||||||
|
bool complete = false;
|
||||||
|
char status;
|
||||||
|
|
||||||
|
/* Drain pending characters from serial line.*/
|
||||||
|
while(1) {
|
||||||
|
if (key_hit)
|
||||||
|
return -1;
|
||||||
|
rc = read_poll(sio, &resp, 1, 50);
|
||||||
|
if (rc == 0) {
|
||||||
|
if (resp == CAN) return ERR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (rc < 0) {
|
||||||
|
if (rc != USER_CAN) {
|
||||||
|
tio_error_print("Read sync from serial failed");
|
||||||
|
}
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Always work with 128b packets */
|
||||||
|
packet.seq = 1;
|
||||||
|
packet.type = SOH;
|
||||||
|
|
||||||
|
/* Start Receive*/
|
||||||
|
rc = start_receive(sio);
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Timeout waiting for transfer to start");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error starting XMODEM receive");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!complete) {
|
||||||
|
/* Poll for 1 new byte for 3 seconds */
|
||||||
|
rc = read_poll(sio, &resp, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for start of next packet");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading start of next packet")
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
|
||||||
|
switch(resp)
|
||||||
|
{
|
||||||
|
case SOH:
|
||||||
|
/* Start of a packet */
|
||||||
|
rc = receive_packet(sio, packet, fd);
|
||||||
|
if (rc == OK) {
|
||||||
|
packet.seq++;
|
||||||
|
status = '.';
|
||||||
|
} else if (rc == ERR) {
|
||||||
|
rc = write(sio, NAK_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Writing not acknowledge packet to serial failed");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
status = 'N';
|
||||||
|
} else if (rc == ERR_FATAL) {
|
||||||
|
tio_error_print("Receive cancelled due to fatal error");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc == USER_CAN) {
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Writing cancel to serial failed");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
return USER_CAN;
|
||||||
|
} else if (rc == RX_IGNORE) {
|
||||||
|
status = ':';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EOT:
|
||||||
|
/* End of Transfer */
|
||||||
|
rc = write(sio, ACK_STR, 1);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Write acknowlegdement packet to serial failed");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
complete = true;
|
||||||
|
status = '\0';
|
||||||
|
write(STDOUT_FILENO, "|\r\n", 3);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CAN:
|
||||||
|
/* Cancel from sender */
|
||||||
|
tio_error_print("Transmission cancelled from sender");
|
||||||
|
return ERR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
tio_error_print("Unexpected character received waiting for next packet");
|
||||||
|
return ERR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Update "progress bar" */
|
||||||
|
write(STDOUT_FILENO, &status, 1);
|
||||||
|
}
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
int xymodem_send(int sio, const char *filename, modem_mode_t mode)
|
int xymodem_send(int sio, const char *filename, modem_mode_t mode)
|
||||||
{
|
{
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
@ -343,7 +688,7 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode)
|
||||||
|
|
||||||
rc = -1;
|
rc = -1;
|
||||||
if (strlen(filename) > 977) break; /* hdr block overrun */
|
if (strlen(filename) > 977) break; /* hdr block overrun */
|
||||||
p = stpcpy(hdr, filename) + 1;
|
p = stpncpy(hdr, filename, 1024) + 1;
|
||||||
p += sprintf(p, "%ld %lo %o", len, stat.st_mtime, stat.st_mode);
|
p += sprintf(p, "%ld %lo %o", len, stat.st_mtime, stat.st_mode);
|
||||||
|
|
||||||
if (xmodem_1k(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */
|
if (xmodem_1k(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */
|
||||||
|
|
@ -360,3 +705,35 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode)
|
||||||
close(fd);
|
close(fd);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int xymodem_receive(int sio, const char *filename, modem_mode_t mode)
|
||||||
|
{
|
||||||
|
int rc, fd;
|
||||||
|
|
||||||
|
/* Create new file */
|
||||||
|
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
|
||||||
|
if (fd < 0) {
|
||||||
|
tio_error_print("Could not open file");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do transfer */
|
||||||
|
key_hit = 0;
|
||||||
|
if (mode == XMODEM_1K) {
|
||||||
|
tio_error_print("Not supported");
|
||||||
|
rc = -1;
|
||||||
|
}
|
||||||
|
else if (mode == XMODEM_CRC) {
|
||||||
|
rc = xmodem_receive(sio, fd);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tio_error_print("Not supported");
|
||||||
|
rc = -1;
|
||||||
|
}
|
||||||
|
key_hit = 0xff;
|
||||||
|
|
||||||
|
/* Flush serial and release resources */
|
||||||
|
tcflush(sio, TCIOFLUSH);
|
||||||
|
close(fd);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,5 @@ typedef enum {
|
||||||
extern char key_hit;
|
extern char key_hit;
|
||||||
|
|
||||||
int xymodem_send(int sio, const char *filename, modem_mode_t mode);
|
int xymodem_send(int sio, const char *filename, modem_mode_t mode);
|
||||||
|
|
||||||
|
int xymodem_receive(int sio, const char *filename, modem_mode_t mode);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue