Compare commits

..

No commits in common. "master" and "v1.37" have entirely different histories.

67 changed files with 2141 additions and 12852 deletions

27
.circleci/config.yml Normal file
View file

@ -0,0 +1,27 @@
# Use the latest 2.1 version of CircleCI pipeline process engine.
# See: https://circleci.com/docs/2.0/configuration-reference
version: 2.1
# Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
jobs:
build-tio:
# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
docker:
- image: cimg/base:edge
# Add steps to the job
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
steps:
- checkout
- run: sudo apt-get -qq update
- run: sudo apt-get install -y bash-completion git meson libinih-dev
- run: git clone https://github.com/tio/tio.git
- run: cd tio && meson build --prefix $HOME/test/tio && ninja -C build install
# Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows:
build-tio-workflow:
jobs:
- build-tio

View file

@ -1,5 +0,0 @@
BasedOnStyle: llvm
IndentWidth: 4
AllowShortFunctionsOnASingleLine: None
KeepEmptyLinesAtTheStartOfBlocks: false
BreakBeforeBraces: Allman

1
.github/FUNDING.yml vendored
View file

@ -1 +0,0 @@
custom: ["https://www.paypal.me/lundmar"]

View file

@ -1,7 +0,0 @@
#!/usr/bin/env bash
pip3 install meson -U
pip3 install ninja -U
sudo apt-get install -y liblua5.2-dev libglib2.0-dev
meson setup build
meson compile -C build

View file

@ -1,126 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main", "master" ]
schedule:
- cron: '0 0 * * *'
pull_request:
branches: '*'
jobs:
analyze:
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-22.04' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
queries: security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
#- name: Autobuild
# uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- run: |
./.github/workflows/codeql-buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
upload: false
id: step1
# Filter out rules with low severity or high false positve rate
# Also filter out warnings in third-party code
- name: Filter out unwanted errors and warnings
uses: advanced-security/filter-sarif@v1
with:
patterns: |
-**:cpp/path-injection
-**:cpp/world-writable-file-creation
-**:cpp/poorly-documented-function
-**:cpp/potentially-dangerous-function
-**:cpp/use-of-goto
-**:cpp/integer-multiplication-cast-to-long
-**:cpp/comparison-with-wider-type
-**:cpp/leap-year/*
-**:cpp/ambiguously-signed-bit-field
-**:cpp/suspicious-pointer-scaling
-**:cpp/suspicious-pointer-scaling-void
-**:cpp/unsigned-comparison-zero
-**/cmake*/Modules/**
input: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
- name: Upload CodeQL results to code scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.step1.outputs.sarif-output }}
category: "/language:${{matrix.language}}"
- name: Upload CodeQL results as an artifact
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: codeql-results
path: ${{ steps.step1.outputs.sarif-output }}
retention-days: 5
- name: Fail if an error is found
run: |
./.github/workflows/fail_on_error.py \
${{ steps.step1.outputs.sarif-output }}/cpp.sarif

View file

@ -1,34 +0,0 @@
#!/usr/bin/env python3
import json
import sys
# Return whether SARIF file contains error-level results
def codeql_sarif_contain_error(filename):
with open(filename, 'r') as f:
s = json.load(f)
for run in s.get('runs', []):
rules_metadata = run['tool']['driver']['rules']
if not rules_metadata:
rules_metadata = run['tool']['extensions'][0]['rules']
for res in run.get('results', []):
if 'ruleIndex' in res:
rule_index = res['ruleIndex']
elif 'rule' in res and 'index' in res['rule']:
rule_index = res['rule']['index']
else:
continue
try:
rule_level = rules_metadata[rule_index]['defaultConfiguration']['level']
except IndexError as e:
print(e, rule_index, len(rules_metadata))
else:
if rule_level == 'error':
return True
return False
if __name__ == "__main__":
if codeql_sarif_contain_error(sys.argv[1]):
sys.exit(1)

View file

@ -1,30 +0,0 @@
name: MacOS build
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
brew install meson ninja lua
- name: Build
run: |
meson setup build
meson compile -C build --verbose
meson install -C build

View file

@ -1,31 +0,0 @@
name: Ubuntu build
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y bash-completion git meson liblua5.2-dev libglib2.0-dev
- name: Build
run: |
meson setup build --prefix $HOME/opt/tio
meson compile -C build --verbose
meson install -C build

1
.gitignore vendored
View file

@ -1,4 +1,3 @@
/build
/subprojects/libinih
*.swp
.cache

View file

@ -1,2 +0,0 @@
[default]
extend-ignore-words-re = ["tio"]

34
AUTHORS
View file

@ -32,39 +32,5 @@ Mariusz Midor <dexlab@o2.pl>
attila-v <attila_v@index.hu>
Yin Fengwei <fengwei.yin@intel.com>
Liam Beguin <liambeguin@gmail.com>
Peter Collingbourne <pcc@google.com>
g0mb4 <gomba007@gmail.com>
ZeroMemoryEx on GitHub
George Joseph <g.devel@wxy78.net>
Robert Snell <rcsnell@ncf.ca>
Rui Chen <rui@chenrui.dev>
Ralph Siemsen <ralphs@netwinder.org>
Victor Oliveira <rhapsodyv@gmail.com>
Attila Veghelyi <aveghelyi@dension.com>
Vyacheslav Patkov <slava@patkov.ru>
Bill Hass <billhass@umich.edu>
Peter van Dijk <peter@7bits.nl>
Braden Young <braden@somewearlabs.com>
Wes Koerber <wkoerber@acsd4u.com>
HiFiPhile <admin@hifiphile.com>
Paul Ruizendaal <pnr@planet.nl>
Fredrik Svedberg <fredrik@svedberg.us>
Sebastian <sebastian.krahmer@gmail.com>
Mingjie Shen <shen497@purdue.edu>
Brian <bayuan@purdue.edu>
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.

973
ChangeLog Normal file
View file

@ -0,0 +1,973 @@
=== tio v1.37 ===
Changes since tio v1.36:
* Make libinih a fallback dependency
This means that in case meson does not find libinih it will
automatically clone libinih and include it in the build.
The libinih library is reconfigured to be statically built so that no
shared object will be installed.
Sylvain LAFRASSE:
* Fix timestamp parsing in INI conf
* Factorize timestamp parsing to be coherent with command line format in configuration file.
Changes since tio v1.35:
* Add support for defaults in config file
If no section name is specified the configuration will be considered the
default one.
This allows to set e.g. a default color code for sections which do not
configure a color code.
* Handle SIGHUP
Handle SIGHUP so that the registered exit handlers are called to restore
the terminal back to its orignal state.
* Add color configuration support
* Bypass unused result warnings
* Force dependency on libinih
Configuration file support is considered a mandatory feature.
* Update headers
* Update AUTHORS
* Update man page
* Move string_to_long() to misc.c
* Update CircleCI config
* Update tio gif
* Update README
* Update LICENSE date
* Remove redundant COPYING file
Liam Beguin:
* Document configuration file options
* Add support for a configuration file
* misc: add _unused macro
Some parameters are expected to be unused.
Add a basic macro to mute these compiler warnings.
* options: expose string_to_long()
Expose string_to_long() so that other source files can use it.
Changes since tio v1.34:
* Add support for automatically generated log filename
Automatically generate log filename if none is provided.
The auto generated file name is on the form:
"tio_DEVICE_YYYY-MM-DDTHH:MM:SS.log"
* Add support for configurable timestamp format
Also changes default timestamp format from ISO8601 to classic 24-hour
format as this is assumed to be the format that most users would prefer.
And reintroduces strict but optional ISO8601 format.
This feature allows to easily add more timestamp formats in the future.
* Reintroduce asm-generic/ioctls.h
It is needed for ppc builds.
* Add macro hack to workaround older buggy glibc
Robey Pointer:
* Add support for high bps on OS X
Changes since tio v1.33:
* Fix setspeed2 compilation
* Only apply color formatting when using color option
To help the color blind who may use custom terminal foreground /
background colors.
* Update README
* Add '-c, --color' option
Allow user to select which ANSI color code to use to colorize the tio
text. To successfully set the color the color code must be in the range
0..255.
If color code is negative tio will print all available ANSI colors.
The default color is changed to bold white to make tio defaults usable
for most users, including color blind users.
* Fix setspeed2 check
* Fix meson header check string
* Reintroduce long timestamp format
But make the timestamp format RFC3339 compliant instead. The RFC states:
NOTE: ISO 8601 defines date and time separated by "T".
Applications using this syntax may choose, for the sake of
readability, to specify a full-date and full-time separated by
(say) a space character.
This way we keep the information specified by ISO 8601 but make it more
human readable which is better for the console output.
* Update version year
Sylvain LAFRASSE:
* Fix TTY device listing on Darwin. (#136)
* Fix TCGETS2 search on Darwin.
Changes since tio v1.32:
* Show auto connect status in show configuration
* Use '#pragma once' in all headers
* Improve printed output
Get rid of inconsistencies in the printed output (error printing,
colors, etc.).
Prepare for user configurable color.
* Rename option -i to -L
* Shorten timestamp
* Shorten timestamp description
We do not need the date part of the timestamp. It simply takes up too
much precious line space. In case of logging to file, one can easily
conclude the date from the file date information.
* Replace Travis with circleCI
* Replace autotools with meson
To introduce much simpler build configuration which is also easier to
maintain.
* Add list serial devices feature
For convenience, add a --list-devices option which lists the available
serial devices.
* Cleanup: Use dot notation for default options struct
* Update AUTHORS
* Add command to show version
The key sequence ctrl-t v will now show the version of tio.
* Align format of timestamps
* Add Sylvain as official co-maintainer
Sylvain LAFRASSE:
* Add '-t' option description for time stamping.
* Add description for time stamping.
* Resolved tio/tio#84: Added timestamps in log file if enabled.
attila-v:
* Refine timestamps with milliseconds and ISO-8601 format (#129).
* Show milliseconds too in the timestamp (#114) and log file (#124)
* Change timestamp format to ISO-8601.
Yin Fengwei:
* Output newline on stdout with hex print mode
This is to fix the issue #104. The timestamp will always be
printed at the beginning of line:
[10:25:56] Switched to hexadecimal mode
0d 0a 0d [10:25:57] 41 43 52 4e 3a 5c 3e 0d 0a 0d [10:25:58] 41
is changed to:
[12:34:56] 45 72 72 6f 72 3a 20 49 6e 76 61 6c 69 64 20
[12:34:56] 41 43 52 4e 3a 5c 3e
[12:34:56] 41 43 52 4e 3a 5c 3e
[12:34:57] 41 43 52 4e 3a 5c 3e 6c 73
Jakob Haufe:
* Make comparison POSIX compliant
String comparison with == is not POSIX compliant and can fail with e.g.
dash.
Henrik Brix Andersen:
* Add bash completion of tty devices.
* Add -t/--timestamp to bash completion script.
Henner Zeller:
* Local echo: show character by character even if stdout buffered.
Björn Stenberg:
* Show error when failing to open a tty
Alban Bedel:
* Fix out of tree builds
Out of tree builds are currently broken because $(top_srcdir)src/include
is not in the search path. In tree builds are working because autconf add
$(top_builddir)/src/include to the search path for the generated config.h.
As $(top_builddir) and $(top_srcdir) are identical during in tree builds
the search path still end up beeing somehow correct.
To fix this add -I$(srcdir)/include to the CPPFLAGS in Makefile.am.
Fabrice Fontaine:
* src/setspeed2.c: fix redefinition of termio
Include ioctls.h and termbits.h from asm-generic instead of asm to avoid
build failures.
Erik Moqvist
* Exit if output speed cannot be set.
Lars Kellogg-Stedman:
* fflush() after putchar() for print_hex and print_normal
In order for local echo to work properly, we have to either call
fflush(stdout) after every character or just disable line buffering.
This change calls fflush() after putchar().
* Disable line buffering in stdout
In order for local echo to work properly, we have to either call
fflush(stdout) after every character or just disable line buffering.
This change uses setbuf(stdout, NULL) to do the latter.
George Stark:
* dont show line state if ioctl failed
* add serial lines manual control
arichi:
* Flush every local echo char
Flush stdout at every char in case it
happens to be buffered.
Mariusz Midor:
* Newline: handle booth NL and CR
Flag ONLCRNL expects code \n after press Enter, but on some systems \r is send instead.
Changes since tio v1.31:
* Update AUTHORS
* Minor code style cleanups
* Cleanup print macros
* Flush output
Make sure output is transmitted immediately by flushing the output.
Robey Pointer:
* add optional timestamps
with "-t" or "C-t T", toggle a timestamp prefix to each line.
Jakub Wilk:
* Fix typos
Sylvain Lafrasse:
* Added macOS compatibility
* Made O_NONBLOCK flag to open() call specific to macOS only.
* Added macOS-related details.
* Added O_NONBLOCK flag to open() call for macOS (10.13.6) compatibility.
Changes since tio v1.30:
* Update date
* Update AUTHORS
Henner Zeller:
* Clarify the input/output variable names (No-op change)
* Organize options the same sequence they are mentioned in cmdline help.
* Update README.
* Map CR->NL locally on output instead of using tio.c_oflag |= OCRNL.
This mostly is intended to have local echo output exactly what is sent
to the remote endpoint.
A nice side-effect is, that it also fixes tty-implementations, that can't
deal with the OCRNL flag on tio.c_oflag.
* Provide local-echo option.
Can be switched on with -e on the command line.
Can be toggled with Ctrl t e while program is running.
* Write to logfile as soon as we have the data, don't buffer.
Logfiles are important to see what happened, in particular if something
unexpected happened; so we want to make sure that the logfile is flushed
to disk.
Before this change, the logfile was typically written at the end in
a large chunk as the default (large) buffering applied. Now, characters are
written out ASAP, so it is possible to get a live-view with a
tail -f <logfile>
Changes since tio v1.29:
* Update README
* Update man page and bash completion
* Update AUTHORS
qianfan Zhao:
* ONLCRNL: change the method to map NL to CR-NL
Changes since tio v1.28:
* Add mapping flags INLCRNL and ODELBS
The following new mapping flags are added:
INLCRNL: Map NL to CR-NL on input.
ODELBS: Map DEL to BS on output.
Flags requested and tested by Jan Ciger (janoc).
Changes since tio v1.27:
* Update README
* Update AUTHORS
* Add snap status to README.md
* Add README.md to prettify GitHub page
* Add missing header
Petr Vaněk:
* Add missing header file under musl-libc
Musl's inclusion tree slightly differs from glibc, therefore TCGETS2 is
not reachable through sys/ioctl.h, so asm/ioctls.h needs to be included
too.
Jakub Wilk:
* Fix grammar and typos
Changes since tio v1.26:
* Update man page
* Add support for setting non-standard baudrates
Support for non-standard baudrate settings will be automatically enabled
if the termios2 interface is detected available. However, to play it
safe, the old and widely supported termios interface will still be used
when setting standard baudrates.
* Cleanup
* Update AUTHORS
Changes since tio v1.25:
* Reconfigure stdin
Make stdin behave more raw'ish. In particular, don't
translate CR -> NL on input.
* Add special character map feature
Add a --map option which allows mapping special characters, in particular CR and
NL characters which are used in various combinations on various platforms.
* Cleanup
* Update AUTHORS
* Update README
* Mention website
* Update man page
Changes since tio v1.24:
* Fix error applying new stdout settings
On Fedora 26 tio will quit with the following error message:
"Error: Could not apply new stdout settings (Invalid argument)"
In case of Fedora, it turns out that the new stdout settings used are a
bit too aggressive because an empty termios structure is used. To remedy
this we reuse the existing stdout settings and only reconfigure the
specific options we need to make a "raw" stdout configuration.
* Remove unused pkgconfig in configure
* Code cleanup
Remove unused variable.
Changes since tio v1.23:
* Optimize clear screen command
Replaced system call with inline ANSI/VT100 clear screen code sequence
* Fix bash completion installation
Fixed the configure script to avoid that the bash completion script gets
installed outside of the prefix location. The default install location
is now $prefix/share/bash-completion/completions.
Use the configure option '--with-bash-completion-dir=PATH' if you need
to install the bash completion script elsewhere.
Jakub Wilk:
* Add missing commas in conditional sentences
Changes since tio v1.22:
* Update copyright headers
Jakub Wilk:
* Fix typos
Changes since tio v1.21:
* Update man page date
* Update copyright year
* Code cleanup
* Update README and man page
Changes since tio v1.20:
* Add support for hexadecimal mode
A new key command 'ctrl-t h' is introduced which toggles between
hexadecimal mode and normal mode. When in hexadecimal mode data received
will be printed in hexadecimal.
* Do not distribute src/bash_completion/tio
Since the bash completion tio script is now autogenerated from tio.in it
should not be distributed in the tarball.
* Add missing forward flag
* Update AUTHORS file
Adam Borowski:
* 'ctrl-t b' to send serial break.
Jakub Wilk:
* Removed git commit references from ChangeLog
ChangeLog is primary useful for users who don't have the git repository
at hand.
Replace git commit references with version numbers; or if the change
only cleans up another change with no release in between, remove the
changelog item completely.
Changes since tio v1.19:
* Added more error handling of terminal calls
Also removed duplicate terminal flushing calls.
* Revert "Added support for non-standard baud rates"
This reverts a change made in v1.18.
Reverting because supporting non-standard or arbitrary baud rates is
troublesome because the c library provides no means of doing so and even
if bare metal linux kernel interface is used it will not work on all
Linux kernels version.
Changes since tio v1.18:
* Rearranged key commands
Rearranged the key commands:
ctrl-t c (clear screen) is now
ctrl-t l which is similar to the well known shell ctrl-l
ctrl-t i (show settings information) is now
ctrl-t c (show configuration)
Updated man page accordingly.
* Added "ctrl-t c" key command to clear screen
Changes since tio v1.17:
* Updated man page
* Added support for non-standard baud rates
Only enabled when possible, that is, when the BOTHER definition is
available.
It is untested but it should work as described here:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683826
Some Cypress USB<->serial devices supposedly supports arbitrary speeds.
* Generate baudrate switch cases based on detection
Support a single source of baud rate configuration as discussed in
https://github.com/tio/tio/issues/45 .
To do so, autogeneration of the switch cases which do the baud rate
option value check and configuration/conversion in tty_configure() is
introduced via a single macro.
Just to be safe, this change also enables configure detection of all
baud rates, including the ones previously assumed supported by most/all
systems (POSIX).
* Minor cleanup
* Exit when not a tty device in autoconnect mode
Jakub Wilk:
* Added non-standard baud rates that are defined on FreeBSD
* Capitalized "GitHub" in README
Changes since tio v1.16:
* Compacted tty_configure() a bit
* Fixed automatic baud rate enablement
* Minor cleanups
* Added autodetection of available baud rates
Various platforms support different baud rates.
To avoid adding platform specific handling generic baud rate detection
tests are introduced in the configure script. Successfully detected baud
rates are automatically enabled. This applies to both the C code and the
bash completion script.
Note:
Baud rates below 57600 are defined by POSIX-1 and supported by most
platforms so only baud rate 57600 and above are tested.
* Updated bash-completion
* Fixed printf() format type
* Added Travis build configuration
Jakub Wilk:
* Generated bash completion at configure time
* Reduce code duplication in baud rate detection
* Add support for baud rates 200 and 1800
* Fixed baudrate type
Changes since tio v1.15:
* Updated man page
* Updated README
* Removed obsolete packaging files
* Removed use of deprecated bzero()
Changes since tio v1.14:
* Removed + to remove potential confusion
* Added input digit checks
* Fixed license string
* Introduced tty_configure()
Moved tty configuration actions to tty_configure() in tty.c. This way
options.c is strictly about parsing options nothing else.
* Function names cleanup
* Updated AUTHORS file
Added Nick who created the new tio package for Arch Linux.
* Fixed tx/rx counters type
Jakob Haufe:
* Include config.h before standard headers
Large file support was meant to be enabled in v1.11.
This change enables it for real.
Changes since tio v1.13:
* Fixed tio_printf macro
* Fixed launch hints
Fixed launch hints not being printed in no autoconnect mode.
* Added 'ctrl-t ?' to list available commands
* Fixed log mechanism
To avoid echoing only log what is received from tty device.
* Improved tio output
Added titles and indentation to commands output for clearer separation
when firing commands repeatedly.
Also added print of tio version and quit command hint at launch.
* Cleaned up tio print mechanism
Jakub Wilk:
* Fixed grammar
"allow" is a transitive verb, which requires an object,
so "allow to <verb>" is ungrammatical.
* Fixed typo
Changes since tio v1.12:
* Fixed some error prints
* Fixed error printing for no autoconnect mode
Always print errors but only print silent errors when in no autoconnect
mode.
* Added key command for showing session settings
A new key command "ctrl-t i" is added to allow the user to display the
various session settings information (baudrate, databits, log file, etc.).
This is useful in case you have a running session but have forgotten
what the settings are.
Changes since tio v1.11:
* Consolidated command key handling
* Moved delay mechanism into separate function
* Retired obsolete usleep()
Replaced with nanosleep()
* Added simple tx/rx statistics command (ctrl-t s)
To display the total number of bytes transmitted/received simply perform the
'ctrl-t s' command sequence.
This feature can be useful when eg. trying to detect non-printable
characters.
* Further simplification of key handling
Changed so that the "ctrl-t ctrl-t" sequence is now simply "ctrl-t t" to
send the ctrl-t key code. This is inspired by screen which does similar
to send its command key code (ctrl-a a).
This change also eases adding new key commands if needed.
Updated man page accordingly.
* Cleaned up and simplified key handling
Jakub Wilk:
* Insert output delay only if something was output
Changes since tio v1.10:
* Enabled large file support (LFS)
Added autotools AC_SYS_LARGEFILE to support 64 bit file size handling.
* Updated tio title
Changes since tio v1.9:
* Introduced lock on device file
Tio will now test for and obtain an advisory lock on the tty device file
to prevent starting multiple sessions on the same tty device.
* Updated AUTHORS
Jakub Wilk:
* Treat EOF on stdin as error
Changes since tio v1.8:
* Cleanup of error handling
Introduced consistent way of handling errors and printing error messages.
Also upgraded some warnings to errors.
* Updated localtime() error message
* Cleanup
Jakub Wilk:
* Fix error handling for select()
Previously the error handling code for select() was unreachable.
* Removed unneeded quotes from AM_CFLAGS
* Expanded tabs
* Fixed setting "tainted"
Set "tainted" if and only if any character was read from the device.
Ctrl-t is no longer sent to the device on exit, so the trick to avoid
its echo is not necessary.
Characters read from stdin don't directly affect output, so they
shouldn't enable "tainted".
* Used \r in color_printf()
\033[300D is an unusual way to move the cursor back to column 1.
Use straightforward \r instead.
* Added missing \r\n to warning messages
\n alone is not enough, because the terminal is in raw mode.
Changes since tio v1.7:
* Fixed enablement of compiler warnings
* Fixed log_open() prototype
* Fixed index error wrt ctrl-t detection
* Fixed handling of ctrl-t
Before, when exercising the quit key sequence (ctrl-t + q) the ctrl-t code
(0x14) would be sent.
This is now fixed so that it is not sent.
However, in case it is needed to send ctrl-t to the device it is possible by
simply repeating the ctrl-t.
Meaning, ctrl-t + ctrl-t = ctrl-t sent to device.
* Improved error handling
Fixes a memory leak and avoids aggressive busy looping when problems
accessing tty device.
* Removed redundant log_close() call
* Enabled compiler warnings
Jakub Wilk:
* Stopped copying arguments to fixed-size buffers
Don't needlessly copy command-line arguments into fixed-size buffers.
Previously the program crashed if an overlong pathname was provided on
the command line. Also, some systems (such as GNU Hurd) don't define
MAXPATHLEN at all.
* Added const to log_open() prototype
* Completed the ^g to ^t transition
In v1.7 the escape key was changed from ^g to ^t, but some
code and comments still referred to the old key.
* Used HTTPS for tio.github.io
* Man page beautification
* Bumped date in man page
* Improve man page formatting
Use regular font for metacharacters such as "[]", "," or "|";
use italic font for metavariables.
* Fixed hyphen vs minus vs em-dash confusion in man page
- prints as hyphen;
\- prints as minus sign;
\em prints as em-dash.
Changes since tio v1.6:
* Changed escape key from ^g to ^t
After renaming to "tio" it makes sense to change the escape key
accordingly. Hence, the new escape key is ^t.
Meaning, in session, its now ctrl-t + q to quit.
Jakub Wilk:
* Fixed silly "tio or tio" in man page
* Fixed typo

View file

@ -1,4 +1,4 @@
Copyright (c) 2014-2025 Martin Lund <martin.lund@keep-it-simple.com>
Copyright (c) 2014-2022 Martin Lund <martin.lund@keep-it-simple.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License

2510
NEWS

File diff suppressed because it is too large Load diff

556
README.md
View file

@ -1,20 +1,13 @@
# tio - a simple serial terminal I/O tool
[![tio](images/tio-icon.png)]()
# tio - a serial device I/O tool
[![](https://img.shields.io/github/actions/workflow/status/tio/tio/ubuntu.yml?label=Ubuntu)](https://github.com/tio/tio/actions/workflows/ubuntu.yml)
[![](https://img.shields.io/github/actions/workflow/status/tio/tio/macos.yml?label=MacOS)](https://github.com/tio/tio/actions/workflows/macos.yml)
[![](https://github.com/tio/tio/actions/workflows/codeql.yml/badge.svg)](https://github.com/tio/tio/actions/workflows/codeql.yml)
[![](https://img.shields.io/codefactor/grade/github/tio/tio)](https://www.codefactor.io/repository/github/tio/tio)
[![](https://img.shields.io/github/v/release/tio/tio?sort=semver)](https://github.com/tio/tio/releases)
[![](https://img.shields.io/repology/repositories/tio)](https://repology.org/project/tio/versions)
[![CircleCI](https://circleci.com/gh/tio/tio/tree/master.svg?style=shield)](https://circleci.com/gh/tio/tio/tree/master)
[![tio](https://snapcraft.io/tio/badge.svg)](https://snapcraft.io/tio)
## 1. Introduction
tio is a serial device tool which features a straightforward command-line and
configuration file interface to easily connect to serial TTY devices for basic
I/O operations.
tio is a simple serial terminal tool which features a straightforward
command-line interface to easily connect to TTY devices for basic I/O
operations.
<p align="center">
<img src="images/tio-demo.gif">
@ -22,527 +15,96 @@ I/O operations.
### 1.1 Motivation
To make a simpler serial device tool for working with serial TTY devices with
less focus on classic terminal/modem features and more focus on the needs of
To make a simpler serial terminal tool for talking with TTY devices with less
focus on classic terminal/modem features and more focus on the needs of
embedded developers and hackers.
tio was originally created as an alternative to
[screen](https://www.gnu.org/software/screen) for connecting to serial devices
when used in combination with [tmux](https://tmux.github.io).
## 2. Features
* Easily connect to serial TTY devices
* Sensible defaults (115200 8n1)
* Automatic connection management
* Automatic detection of serial ports
* Automatic reconnect
* Automatically connect to first new appearing serial device
* Automatically connect to latest registered serial device
* Connect to same port/device combination via unique topology ID (TID)
* Useful for reconnecting when serial device has no serial device by ID
* Support for non-standard baud rates
* Support for mark and space parity
* X-modem (1K/CRC) and Y-modem file upload
* Support for RS-485 mode
* List available serial devices
* By device
* Including topology ID, uptime, driver, description
* Sorted by uptime (newest device listed last)
* By ID
* By path
* Show RX/TX statistics
* Toggle serial lines
* Pulse serial lines with configurable pulse duration
* Local echo support
* Remapping of characters (nl, cr-nl, bs, lowercase to uppercase, etc.)
* Switchable independent input and output
* Normal mode
* Hex mode (output supports variable width)
* Line mode (input only)
* Timestamp support
* Per line in normal output mode
* Output timeout timestamps in hex output mode
* Support for delayed output
* Per character
* Per line
* Log to file
* Automatic naming of log file (default)
* Configurable directory for saving automatic named log files
* Manual naming of log file
* Overwrite (default) or append to log file
* Strip control characters and escape sequences
* Configuration file support
* Support for configuration profiles
* 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
* Useful for scripting or TTY sharing
* Pipe input and/or output
* Bash completion on options, serial device names, and profile names
* Configurable tio message text color
* Supports NO_COLOR env variable as per [no-color.org](https://no-color.org)
* Visual or audible alert on connect/disconnect
* Remapping of prefix key
* Lua scripting support for automation
* Run script manually or automatically at connect (once/always/never)
* Simple expect/send like functionality with support for regular expressions
* Manipulate port modem lines (useful for microcontroller reset/boot etc.)
* Send files via x/y-modem protocol
* Search for serial devices
* Man page documentation
* Plays nicely with [tmux](https://tmux.github.io) and similar terminal multiplexers
## 3. Usage
For more usage details please see the man page documentation
[here](https://raw.githubusercontent.com/tio/tio/master/man/tio.1.txt).
### 3.1 Command-line
## 2. Usage
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>
Connect to TTY device directly or via configuration profile or topology ID.
Options:
Options:
-b, --baudrate <bps> Baud rate (default: 115200)
-d, --databits 5|6|7|8 Data bits (default: 8)
-f, --flow hard|soft|none Flow control (default: none)
-s, --stopbits 1|2 Stop bits (default: 1)
-p, --parity odd|even|none|mark|space Parity (default: none)
-o, --output-delay <ms> Output character delay (default: 0)
-O, --output-line-delay <ms> Output line delay (default: 0)
--line-pulse-duration <duration> Set line pulse duration
-a, --auto-connect new|latest|direct Automatic connect strategy (default: direct)
--exclude-devices <pattern> Exclude devices by pattern
--exclude-drivers <pattern> Exclude drivers by pattern
--exclude-tids <pattern> Exclude topology IDs by pattern
-n, --no-reconnect Do not reconnect
-p, --parity odd|even|none Parity (default: none)
-o, --output-delay <ms> Output delay (default: 0)
-n, --no-autoconnect Disable automatic connect
-e, --local-echo Enable local echo
--input-mode normal|hex|line Select input mode (default: normal)
--output-mode normal|hex|hexN Select output mode (default: normal)
-t, --timestamp Enable line timestamp
--timestamp-format <format> Set timestamp format (default: 24hour)
--timestamp-timeout <ms> Set timestamp timeout (default: 200)
-l, --list List available serial devices, TIDs, and profiles
-L, --log Enable log to file
--log-file <filename> Set log filename
--log-directory <path> Set log directory path for automatic named logs
--log-append Append to log file
--log-strip Strip control characters and escape sequences
-m, --map <flags> Map characters
-c, --color 0..255|bold|none|list Colorize tio text (default: bold)
-S, --socket <socket> Redirect I/O to socket
--rs-485 Enable RS-485 mode
--rs-485-config <config> Set RS-485 configuration
--alert bell|blink|none Alert on connect/disconnect (default: none)
--mute Mute tio messages
--script <string> Run script from string
--script-file <filename> Run script from file
--script-run once|always|never Run script on connect (default: always)
--exec <command> Execute shell command with I/O redirected to device
-t, --timestamp[=<format>] Enable timestamp (default: 24hour)
-L, --list-devices List available serial devices
-l, --log <filename> Log to file
-m, --map <flags> Map special characters
-c, --color <0..255> Colorize tio text
-v, --version Display version
-h, --help Display help
Options and profiles may be set via configuration file.
See the man page for more details.
In session you can press ctrl-t ? to list available key commands.
See the man page for more details.
In session, press ctrl-t q to quit.
```
By default tio automatically connects to the provided TTY device. If the device
is not present, tio will wait for it to appear and then connect. If the
connection is lost (e.g. device is unplugged), it will wait for the device to
reappear and then reconnect. However, if the `--no-reconnect` option is
The only option which requires a bit of elaboration is perhaps the
`--no-autoconnect` option.
By default tio automatically connects to the provided device if present. If
the device is not present, it will wait for it to appear and then connect. If
the connection is lost (eg. device is unplugged), it will wait for the device
to reappear and then reconnect. However, if the `--no-autoconnect` option is
provided, tio will exit if the device is not present or an established
connection is lost.
#### 3.1.1 Examples
Tio supports various in session key commands. Press ctrl-t ? to list the
available key commands.
Typical use is without options:
```
$ tio /dev/ttyUSB0
```
Tio also features full bash autocompletion support and configuration via ~/.tiorc.
Which corresponds to the commonly used default options:
```
$ tio --baudrate 115200 --databits 8 --flow none --stopbits 1 --parity none /dev/ttyUSB0
```
See the tio man page for more details.
List available serial devices:
```
$ tio --list
Device TID Uptime [s] Driver Description
----------------- ---- ------------- ---------------- --------------------------
/dev/ttyS4 BaaB 19526.576 port 16550A UART
/dev/ttyS5 eV0Z 19525.845 port 16550A UART
/dev/ttyUSB1 bCC2 1023.274 ftdi_sio TTL232R-3V3
/dev/ttyUSB0 SPpw 978.527 ftdi_sio TTL232RG-VREG3V3
/dev/ttyACM0 i5q4 2.079 cdc_acm ST-Link VCP Ctrl
By-id
--------------------------------------------------------------------------------
/dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTCHUV56-if00-port0
/dev/serial/by-id/usb-FTDI_TTL232RG-VREG3V3_FT1NELUB-if00-port0
/dev/serial/by-id/usb-STMicroelectronics_STLINK-V3_004900343438510234313939-if02
## 3. Installation
By-path
--------------------------------------------------------------------------------
/dev/serial/by-path/pci-0000:00:14.0-usb-0:8.1.3.1.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:8.1.3.1.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:6.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usb-0:6.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:6.3:1.2
/dev/serial/by-path/pci-0000:00:14.0-usb-0:6.3:1.2
### 3.1 Installation using package manager
tio comes prepackaged for various GNU/Linux distributions. Please consult your package manager tool to find and install tio.
Configuration profiles (/home/lundmar/.config/tio/config)
--------------------------------------------------------------------------------
rpi3 stm32 esp32 am64-evm
imx8mp-evk nucleo-h743zi2 usb-devices
```
It is recommended to connect serial TTY devices by ID:
```
$ tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTCHUV56-if00-port0
```
Note: Using serial devices by ID helps ensure that tio automatically reconnects
to the same serial device when reattached, even when it enumerates differently.
If no serial device by ID is available it is recommended to connect via
topology ID (TID):
```
$ tio bCC2
```
Note: The TID is unique and will stay the same as long as your USB serial port
device plugs into the same USB topology (same ports, same hubs, same
connections, etc.). This way it is possible for tio to successfully reconnect
to the same device.
Connect automatically to first new appearing serial device:
```
$ tio --auto-connect new
```
Connect automatically to latest registered serial device:
```
$ tio --auto-connect latest
```
It is possible to use exclude options to affect which serial devices are
involved in the automatic connection strategy:
```
$ tio --auto-connect new --exclude-devices "/dev/ttyACM?,/dev/ttyUSB2"
```
And to exclude drivers by pattern:
```
$ tio --auto-connect new --exclude-drivers "cdc_acm,ftdi_sio"
```
Note: Pattern matching supports '*' and '?'. Use comma separation to define
multiple patterns.
To include drivers by specific pattern simply negate the exclude option:
```
$ tio --auto-connect new --exclude-drivers !("cp2102")
```
Log to file with autogenerated filename:
```
$ tio --log /dev/ttyUSB0
```
Log to file with specific filename:
```
$ tio --log --log-file my-log.txt
```
Enable ISO8601 timestamps per line:
```
$ tio --timestamp --timestamp-format iso8601 /dev/ttyUSB0
```
Output to hex with width 16:
```
$ tio --output-mode hex16 /dev/ttyUSB0
```
Redirect I/O to IPv4 network socket on port 4242:
```
$ tio --socket inet:4242 /dev/ttyUSB0
```
Map NL to CR-NL on input from device and DEL to BS on output to device:
```
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
```
Pipe data to the serial device:
```
$ cat data.bin | tio /dev/ttyUSB0
```
Manipulate modem lines on connect:
```
$ tio --script "tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=toggle,RTS=toggle}" /dev/ttyUSB0
```
Pipe command to serial device and wait for line response within 1 second:
```
$ echo "*IDN?" | tio /dev/ttyACM0 --script "tio.expect('\r\n', 1000)" --mute
KORAD KD3305P V4.2 SN:32475045
```
### 3.2 Key commands
Various in session key commands are supported. When tio is started, press
ctrl-t ? to list the available key commands.
```
[15:02:53.269] Key commands:
[15:02:53.269] ctrl-t ? List available key commands
[15:02:53.269] ctrl-t b Send break
[15:02:53.269] ctrl-t c Show configuration
[15:02:53.269] ctrl-t e Toggle local echo mode
[15:02:53.269] ctrl-t f Toggle log to file
[15:02:53.269] ctrl-t F Flush data I/O buffers
[15:02:53.269] ctrl-t g Toggle serial port line
[15:02:53.269] ctrl-t i Toggle input mode
[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 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 p Pulse serial port line
[15:02:53.269] ctrl-t q Quit
[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 t Toggle line timestamp mode
[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 y Send file via Ymodem
[15:02:53.269] ctrl-t ctrl-t Send ctrl-t character
```
If needed, the prefix key (ctrl-t) can be remapped via configuration file.
### 3.3 Configuration file
Options can be set via the configuration file first found in any of the
following locations in the order listed:
- $XDG_CONFIG_HOME/tio/config
- $HOME/.config/tio/config
- $HOME/.tioconfig
The configuration file supports profiles using named sections which can be
activated via the command-line by name or pattern. A profile specifies which
TTY device to connect to and other options.
### 3.3.1 Example
Example configuration file:
```
[default]
baudrate = 115200
databits = 8
parity = none
stopbits = 1
color = 10
[rpi3]
device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
no-reconnect = true
log = true
log-file = rpi3.log
line-pulse-duration = DTR=200,RTS=150
color = 11
[svf2]
device = /dev/ttyUSB0
baudrate = 9600
script = tio.expect("login: "); tio.write("root\n"); tio.expect("Password: "); tio.write("root\n")
color = 12
[esp32]
device = /dev/serial/by-id/usb-0403_6014-if00-port0
script = tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=low,RTS=high}; tio.msleep(100); tio.set{RTS=low}
script-run = once
color = 13
[usb-devices]
pattern = ^usb([0-9]*)
device = /dev/ttyUSB%m1
color = 14
```
To use a specific profile by name simply start tio like so:
```
$ tio rpi3
```
Or by pattern match:
```
$ tio usb12
```
Another more elaborate configuration file example is available [here](examples/config/config).
### 3.4 Lua script API
Tio suppots Lua scripting to easily automate interaction with the tty device.
In addition to the standard Lua API tio makes the following functions
and variables available:
#### `tio.expect(pattern, timeout)`
Waits for the Lua pattern to match or timeout before continuing.
Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
Returns the captures from the pattern or `nil` on timeout.
#### `tio.read(size, timeout)`
Read up to `size` bytes from serial device. If timeout is 0 or not provided it
will wait forever until data is ready to read.
Returns a string up to `size` bytes long on success and `nil` on timeout.
#### `tio.readline(timeout)`
Read line from serial device. If timeout is 0 or not provided it will wait
forever until data is ready to read.
Returns a string on success and `nil` on timeout. On timeout a partially read
line may be returned as a second return value.
#### `tio.write(string)`
Write string to serial device.
Returns the `tio` table.
#### `tio.send(file, protocol)`
Send file using x/y-modem protocol.
Protocol can be any of `XMODEM_1K`, `XMODEM_CRC`, `YMODEM`.
#### `tio.ttysearch()`
Search for serial devices.
Returns a table of number indexed tables, one for each serial device found.
Each of these tables contains the serial device information accessible via the
following string indexed elements "path", "tid", "uptime", "driver",
"description".
Returns `nil` if no serial devices are found.
#### `tio.set{line=state, ...}`
Set state of one or multiple tty modem lines.
Line can be any of `DTR`, `RTS`, `CTS`, `DSR`, `CD`, `RI`
State is `high`, `low`, or `toggle`.
#### `tio.sleep(seconds)`
Sleep for seconds.
#### `tio.msleep(ms)`
Sleep for milliseconds.
#### `tio.alwaysecho`
A boolean value, defaults to `true`.
If `tio.alwaysecho` is `false`, the result of `tio.read`, `tio.readline` or
`tio.expect` will only be returned from the function and not logged or printed.
If `tio.alwaysecho` is set to `true`, reading functions also emit a single
timestamp to stdout and log file per `options.timestamp` and `options.log`.
## 4. Installation
### 4.1 Installation using package manager (Linux)
Packages for various GNU/Linux distributions are available. Please consult your
package manager tool to find and install tio.
If you would like to see tio included in your favorite distribution, please
reach out to its package maintainers team.
### 4.2 Installation using snap (Linux)
### 3.2 Installation using snap
Install latest stable version:
```
$ snap install tio --classic
$ snap install tio
```
Install bleeding edge:
```
$ snap install tio --edge
```
Note: Classic confinement is currently required due to limitations of the snapcraft framework.
See [Issue #187](https://github.com/tio/tio/issues/187) for discussion.
### 4.3 Installation using brew (MacOS, Linux)
If you have [brew](http://brew.sh) installed:
```
$ brew install tio
```
### 4.4 Installation using MSYS2 (Windows)
If you have [MSYS2](https://www.msys2.org) installed:
```
$ pacman -S tio
```
### 4.5 Installation from source
### 3.3 Installation from source
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:
```
$ meson setup build
$ meson compile -C build
$ meson install -C build
$ meson build
$ meson compile -C build
$ meson install -C build
```
See meson\_options.txt for tio specific build options.
Note: The meson install steps may differ depending on your specific system.
### 4.6 Known issues
Getting permission access errors trying to open your serial device?
Add your user to the group which allows serial device access permanently. For example, to add your user to the 'dialout' group do:
```bash
sudo usermod -a -G dialout <username>
```
Switch to the "dialout" group, temporary but immediately for this session.
```bash
newgrp dialout
```
Note: Please do no try to install from source if you are not familiar with
how to build stuff using meson.
## 5. Contributing
## 4. Contributing
This is an open source project - all contributions (bug reports, code, doc,
ideas, etc.) are welcome.
tio is open source. If you want to help out with the project please feel free
to join in.
All contributions (bug reports, code, doc, ideas, etc.) are welcome.
Please use the github issue tracker and pull request features.
@ -552,23 +114,23 @@ consider making a donation of your choice:
[![Donate](images/paypal.png)](https://www.paypal.me/lundmar)
## 6. Support
## 5. Support
Submit bug reports via GitHub: https://github.com/tio/tio/issues
## 7. Website
## 6. Website
Visit [tio.github.io](https://tio.github.io)
## 8. License
## 7. License
tio is GPLv2+. See LICENSE file for more details.
Tio is GPLv2+. See LICENSE file for more details.
## 9. Authors
## 8. Authors
Maintained by Martin Lund \<martin.lund@keep-it-simple.com>
Created by Martin Lund \<martin.lund@keep-it-simple.com>
See the AUTHORS file for full list of contributors.

111
TODO
View file

@ -1,111 +0,0 @@
* 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:
00000000 74 65 73 74 20 74 65 73 74 20 74 65 73 74 20 74 |test test test t|
00000010 65 73 74 64 66 0a 61 0a 66 61 0a 66 0a 61 73 66 |estdf.a.fa.f.asf|
00000020 64 61 64 73 66 61 73 66 64 61 73 64 66 61 64 73 |dadsfasfdasdfads|
00000030 66 0a 61 73 64 66 61 64 73 66 61 73 64 66 61 73 |f.asdfadsfasdfas|
00000040 64 66 0a 61 73 64 66 61 64 73 66 61 73 64 66 61 |df.asdfadsfasdfa|
00000050 73 64 66 66 64 61 73 64 66 0a 0a 31 32 33 31 0a |sdffdasdf..1231.|
00000060 65 32 31 64 73 77 65 64 0a 0a |e21dswed..|
0000006a
* Add support for activity based time stamping in normal output mode
Already supported in hex output mode.
* Allow tio to connect to socket
After some more consideration I think it makes sense to support connecting to a
socket as that will make tio be able to both serve a serial port via a socket
and connect to it - it will be an end to end solution. In short we will be able
to do the following:
Host serial port on socket (existing feature):
$ tio --socket unix:/tmp/tio-socket-0 /dev/ttyUSB0
Connect to same socket (new feature):
$ tio unix:/tmp/tio-socket-0
Besides a bit of refactoring the following required changes spring to mind:
* Socket mode and type of socket should be activated via device name prefix. For example:
* UNIX socket: tio unix:<filename>
* TCPv4 socket: tio inet:<ip>:<port>
* TCPv6 socket: tio inet6:<ip>:<port>
* If no port number defined default to 3333
* Mapping flags INLCR, IGNCR, ICRNL needs implementation for socket mode
* Error messages should just say "device" instead of "tty device" etc.
* Remove other tty'isms (tty_write() should be device_write() etc.)
* In session key commands that do not work in socket mode should either not be listed or print an error messages if used.
* All non-tty features should continue work (auto-connect etc.)
* Shell completion script update
* Man page update
* Split I/O feature
Allow to split input and output so that it is possible to manage these
independently.
The general idea is to redirect the output stream on the socket port number
specified but then redirect the input stream on the same port number + 1.
Example:
$ tio /dev/ttyUSB0 --socket inet:4444,split-io
Will result in output stream being hosted on port 4444 and input stream
hosted on port 4445.
For file sockets something similar can be arranged:
$ tio /dev/ttyUSB0 --socket unix:/tmp/tio-socket-0,split-io
Will result in output stream being hosted via /tmp/tio-socket-0 and input
stream hosted via /tmp/tio-socket-0_input
* Websocket support
Extend the socket feature to redirect serial I/O to websocket on e.g. port
1234 like so:
$ tio --socket ws:1234
Use libwesockets to implement feature.

View file

@ -1,78 +0,0 @@
###############################
# tio - https://tio.github.io #
###############################
# Example tio configuration file
#
# Place file in any of the following locations:
# $XDG_CONFIG_HOME/tio/config
# $HOME/.config/tio/config
# $HOME/.tioconfig
[default]
baudrate = 115200
databits = 8
flow = none
stopbits = 1
parity = none
output-delay = 0
output-line-delay = 0
auto-connect = direct
no-reconnect = false
local-echo = false
input-mode = normal
output-mode = normal
timestamp = false
log = false
log-append = false
log-strip = false
color = bold
rs-485 = false
alert = none
script-run = always
prefix-ctrl-key = t
# Configuration profiles
[rpi3]
baudrate = 115200
device = /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A6009HU3-if00-port0
socket = unix:/tmp/tio-socket-0
color = 9
[am64-evm]
baudrate = 115200
device = /dev/serial/by-id/usb-Silicon_Labs_CP2105_Dual_USB_to_UART_Bridge_Controller_01093176-if01-port0
line-pulse-duration = DTR=200,RTS=300,RI=50
alert = bell
color = 10
[tincan]
baudrate = 9600
device = /dev/serial/by-id/usb-TinCanTools_Flyswatter2_FS20000-if00-port0
log = true
log-file = tincan.log
log-strip = true
color = 11
[usb-devices]
pattern = ^usb([0-9]*)
device = /dev/ttyUSB%m1
color = 12
[rs-485-device]
device = /dev/ttyUSB0
rs-485 = true
rs-485-config = RTS_ON_SEND=1,RTS_AFTER_SEND=1,RTS_DELAY_BEFORE_SEND=60,RTS_DELAY_AFTER_SEND=80,RX_DURING_TX
color = 13
[esp32]
device = /dev/ttyUSB0
color = 14
script = tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=low,RTS=high}; tio.msleep(100); tio.set{RTS=low}
script-run = always
[buspirate]
device = /dev/ttyACM0
map = INLCRNL,ODELBS
color = 15

View file

@ -1,28 +0,0 @@
local logins = {
["foo"] = {
username = "foouser",
password = "foopass",
},
["bar"] = {
username = "baruser",
password = "barpass",
},
["baz"] = {
username = "bazuser",
password = "bazpass",
},
}
local hostname = tio.expect("^(%g+) login:", 10)
if hostname then
local login = logins[hostname]
if (nil ~= login) then
tio.write(login.username .. "\n")
tio.expect("Password:")
tio.write(login.password .. "\n")
else
io.write("\r\nDon't know login info for " .. hostname .. "\r\n")
end
else
io.write("\r\nDidn't find a login prompt\r\n")
end

View file

@ -1,5 +0,0 @@
tio.set{DTR=high, RTS=low}
tio.msleep(100)
tio.set{DTR=low, RTS=high}
tio.msleep(100)
tio.set{RTS=toggle}

View file

@ -1,14 +0,0 @@
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

View file

@ -1,15 +0,0 @@
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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,18 +1,18 @@
.TH "tio" "1" "@version_date@" "tio @version@" "User Commands"
.SH "NAME"
tio \- a serial device I/O tool
tio \- a simple serial terminal I/O tool
.SH "SYNOPSIS"
.PP
.B tio
.RI "[" <options> "] " "<tty-device|profile|tid>"
.RI "[" <options> "] " "<tty-device>"
.SH "DESCRIPTION"
.PP
\fBtio\fR is a serial device tool which features a straightforward command-line
and configuration file interface to easily connect to serial TTY devices for
basic I/O operations.
.B tio
is a simple serial terminal tool which features a straightforward command-line
interface to easily connect to serial/TTY devices for basic I/O operations.
.SH "OPTIONS"
@ -21,107 +21,35 @@ basic I/O operations.
Set baud rate [bps] (default: 115200).
.TP
.BR \-d ", " "\-\-databits " 5 | 6 | 7 | 8
.BR \-d ", " "\-\-databits 5" | 6 | 7 | 8
Set data bits (default: 8).
.TP
.BR \-f ", " "\-\-flow " hard | soft | none
.BR \-f ", " "\-\-flow hard" | soft | none
Set flow control (default: none).
.TP
.BR \-s ", " "\-\-stopbits " 1 | 2
.BR \-s ", " "\-\-stopbits 1" | 2
Set stop bits (default: 1).
.TP
.BR \-p ", " "\-\-parity " odd | even | none | mark | space
.BR \-p ", " "\-\-parity odd" | even | none
Set parity (default: none).
Note: With \fBmark\fR parity the parity bit is always 0. With \fBspace\fR
parity the parity bit is always 1. Not all platforms support \fBmark\fR and
\fBspace\fR parity.
.TP
.BR \-o ", " "\-\-output\-delay " \fI<ms>
Set output delay [ms] inserted between each sent character (default: 0).
.TP
.BR \-O ", " "\-\-output\-line\-delay " \fI<ms>
.BR \-n ", " \-\-no\-autoconnect
Set output delay [ms] inserted between each sent line (default: 0).
Disable automatic connect.
.TP
.BR " \-\-line\-pulse\-duration " \fI<duration>
By default tio automatically connects to the provided device if present. If the device is not present, it will wait for it to appear and then connect. If the connection is lost (eg. device disconnects), it will wait for the device to reappear and then reconnect.
Set the pulse duration [ms] of each serial port line using the following key
value pair format in the duration field: <key>=<value>
Each key represents a serial line. The following keys are available:
.RS
.TP 8n
.IP \fBDTR
Data Terminal Ready
.IP \fBRTS
Request To Send
.IP \fBCTS
Clear To Send
.IP \fBDSR
Data Set Ready
.IP \fBDCD
Data Carrier Detect
.IP \fBRI
Ring Indicator
.P
If defining more than one key value pair, the pairs must be comma separated.
The default pulse duration for each line is 100 ms.
.RE
.TP
.BR "\-a, \-\-auto\-connect new|latest|direct"
Automatically connect to serial device according to one of the following
strategies:
.RS
.TP 10n
.IP "\fBnew"
Automatically connect to first new appearing serial device.
.IP "\fBlatest"
Automatically connect to latest registered serial device.
.IP "\fBdirect"
Connect directly to specified TTY device.
.P
All the listed strategies automatically reconnects according to strategy if
device is not available or connection is lost.
.P
Default value is "direct".
.RE
.TP
.BR " \-\-exclude\-devices \fI<pattern>"
Exclude devices by pattern ('*' and '?' supported).
.TP
.BR " \-\-exclude\-drivers \fI<pattern>"
Exclude drivers by pattern ('*' and '?' supported).
.TP
.BR " \-\-exclude\-tids \fI<pattern>"
Exclude topology IDs by pattern ('*' and '?' supported).
.TP
.BR \-n ", " \-\-no\-reconnect
Do not reconnect.
This means that tio will exit if it fails to connect to device or an
established connection is lost.
However, if the
.B \-\-no\-autoconnect
option is provided, tio will exit if the device is not present or an established connection is lost.
.TP
.BR \-e ", " "\-\-local\-echo
@ -129,252 +57,63 @@ established connection is lost.
Enable local echo.
.TP
.BR \-t ", " \-\-timestamp
.BR \-t ", " \-\-timestamp[=\fI<format>\fR\fB]
Enable line timestamp.
.TP
.BR " \-\-timestamp\-format \fI<format>"
Set timestamp format to any of the following timestamp formats:
Enable timestamp. Optionally you can specify any of the following timestamp formats:
.RS
.TP 16n
.IP "\fB24hour"
24-hour format ("hh:mm:ss.sss")
.IP "\fB24hour-start"
24-hour format relative to start time
.IP "\fB24hour-delta"
24-hour format relative to previous timestamp
.IP "\fBiso8601"
ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
.IP "\fBepoch"
Seconds since Unix epoch (1970-01-01)
.IP "\fBepoch-usec"
Seconds since Unix epoch (1970-01-01) with subdivision in microseconds
.PP
Default format is \fB24hour\fR
.P
Default format is
.B 24hour
.RE
.TP
.BR " \-\-timestamp\-timeout \fI<ms>"
.BR \-L ", " \-\-list\-devices
Set timestamp timeout value in milliseconds.
This value only takes effect in hex output mode where timestamps are only
printed after elapsed timeout time of no output activity from tty device.
Default value is 200.
List available serial devices.
.TP
.BR \-l ", " \-\-list
.BR \-l ", " \-\-log[=\fI<filename>\fR\fB]
List available targets (serial devices, TIDs, configuration profiles).
.TP
.BR \-L ", " \-\-log
Enable log to file.
The log file will be automatically named using the following format
tio_TARGET_YYYY-MM-DDTHH:MM:SS.log. Target being the command line target such
as tty-device, tid, or configuration profile.
The filename can be manually set using the \-\-log-file option.
.TP
.BR " \-\-log\-file \fI<filename>
Set log filename.
.TP
.BR " \-\-log\-directory \fI<path>
Set log directory path in which to save automatically named log files.
.TP
.BR " \-\-log\-append
Append to log file.
.TP
.BR " \-\-log-strip
Strip control characters and escape sequences from log.
Log to file. If no filename is provided the filename will be automatically generated.
.TP
.BR \-m ", " "\-\-map " \fI<flags>
Map (replace, translate) characters on input to the serial device or output
from the serial device. The following mapping flags are supported:
Map (replace, translate) special characters on input or output. The following mapping flags are supported:
.RS
.TP 12n
.IP "\fBICRNL"
Map CR to NL on input (unless IGNCR is set)
Map CR to NL on input (unless IGNCR is set).
.IP "\fBIGNCR"
Ignore CR on input
.IP "\fBIFFESCC"
Map FF to ESC-c on input
Ignore CR on input.
.IP "\fBINLCR"
Map NL to CR on input
Map NL to CR on input.
.IP "\fBINLCRNL"
Map NL to CR-NL on input
.IP "\fBICRCRNL"
Map CR to CR-NL on input
.IP "\fBIMSB2LSB"
Map MSB bit order to LSB on input
Map NL to CR-NL on input.
.IP "\fBOCRNL"
Map CR to NL on output
Map CR to NL on output.
.IP "\fBODELBS"
Map DEL to BS on output
Map DEL to BS on output.
.IP "\fBONLCRNL"
Map NL to CR-NL on output
.IP "\fBOLTU"
Map lowercase characters to uppercase on output
.IP "\fBONULBRK"
Map nul (zero) to send break signal on output
.IP "\fBOIGNCR"
Ignore CR on output
Map NL to CR-NL on output.
.P
If defining more than one flag, the flags must be comma separated.
.RE
.TP
.BR " \-\-input\-mode " normal|hex|line
.BR \-c ", " "\-\-color " \fI<code>
Set input mode.
Colorize tio text using ANSI color code ranging from 0 to 255.
In normal mode input characters are sent immediately as they are typed.
If color code is negative a list of available ANSI colors will be printed.
In hex input mode bytes can be sent by typing the \fBtwo-character
hexadecimal\fR representation of the 1 byte value, e.g.: to send \fI0xA\fR you
must type \fI0a\fR or \fI0A\fR.
In line input mode input characters are sent when you press enter. The only
editing feature supported in this mode is backspace.
Default value is "normal".
.TP
.BR " \-\-output\-mode " normal|hex|hexN
Set output mode.
In hex mode each incoming byte is printed out as a 1 byte hex value.
In hexN mode, N is a number less than or equal to 4096 which defines how many
hex values will be printed before a line break.
Default value is "normal".
.TP
.BR \-c ", " "\-\-color " 0..255|bold|none|list
Colorize tio text using ANSI color code value ranging from 0 to 255 or use
"none" for no color or use "bold" to apply bold formatting to existing system
color.
Use "list" to print a list of available ANSI color codes.
Default value is "bold".
.TP
.BR \-S ", " "\-\-socket \fI<socket>\fR\fB
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 \fBctrl-t\fR sequences
are not recognized), and any input from the serial port is multiplexed to the
terminal and all connected clients.
Sockets remain open while the serial port is disconnected, and writes will block.
Various socket types are supported using the following prefixes in the socket field:
.RS
.TP 20n
.IP "\fBunix:<filename>"
Unix Domain Socket (file)
.IP "\fBinet:<port>"
Internet Socket (network)
.IP "\fBinet6:<port>"
Internet IPv6 Socket (network)
.P
If port is 0 or no port is provided default port 3333 is used.
.P
At present there is a hardcoded limit of 16 clients connected at one time.
.RE
.TP
.BR " \-\-rs\-485"
Enable RS-485 mode.
.TP
.BR " \-\-rs\-485\-config " \fI<config>
Set the RS-485 configuration using the following key or key value pair format in
the configuration field:
.RS
.TP 30n
.IP \fBRTS_ON_SEND=value
Set logical level (0 or 1) for RTS pin when sending
.IP \fBRTS_AFTER_SEND=value
Set logical level (0 or 1) for RTS pin after sending
.IP \fBRTS_DELAY_BEFORE_SEND=value
Set RTS delay (ms) before sending
.IP \fBRTS_DELAY_AFTER_SEND=value
Set RTS delay (ms) after sending
.IP \fBRX_DURING_TX
Receive data even while sending data
.P
If defining more than one key or key value pair, they must be comma separated.
.RE
.TP
.BR "\-\-alert none|bell|blink"
Set alert action on connect/disconnect.
It will sound the bell once or blink once on successful connect. Likewise it
will sound the bell twice or blink twice on disconnect.
Default value is "none".
.TP
.BR "\-\-mute"
Mute tio messages.
.TP
.BR "\-\-script \fI<string>
Run script from string.
.TP
.BR "\-\-script\-file \fI<filename>
Run script from file with filename.
.TP
.BR "\-\-script\-run once|always|never"
Run script on connect once, always, or never.
Default value is "always".
.TP
.BR "\-\-exec \fI<command>
Execute shell command with I/O redirected to device
.TP
.BR "\-\-complete-profiles
Prints profiles (for shell completion)
.TP
.BR \-v ", " \-\-version
@ -383,10 +122,10 @@ Display program version.
.BR \-h ", " \-\-help
Display help.
.SH "KEY COMMANDS"
.SH "KEYS"
.PP
.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 key commands:
In session, the following key sequences are intercepted as tio commands:
.IP "\fBctrl-t ?"
List available key commands
.IP "\fBctrl-t b"
@ -395,147 +134,47 @@ Send serial break (triggers SysRq on Linux, etc.)
Show configuration (baudrate, databits, etc.)
.IP "\fBctrl-t e"
Toggle local echo mode
.IP "\fBctrl-t f"
Toggle log to file
.IP "\fBctrl-t F"
Flush data I/O buffers (discard data written but not transmitted and data received but not read)
.IP "\fBctrl-t g"
Toggle serial port line
.IP "\fBctrl-t i"
Toggle input mode
.IP "\fBctrl-t h"
Toggle hexadecimal mode
.IP "\fBctrl-t l"
Clear screen
.IP "\fBctrl-t L"
Show line states (DTR, RTS, CTS, DSR, DCD, RI)
.IP "\fBctrl-t m"
Change mapping of characters on input or output
.IP "\fBctrl-t o"
Toggle output mode
.IP "\fBctrl-t p"
Pulse serial port line
.IP "\fBctrl-t q"
Quit
.IP "\fBctrl-t r"
Run script
.IP "\fBctrl-t R"
Execute shell command with I/O redirected to device
.IP "\fBctrl-t s"
Show TX/RX statistics
.IP "\fBctrl-t t"
Toggle line timestamp mode
Send ctrl-t key code
.IP "\fBctrl-t L"
Show line states (DTR, RTS, CTS, DSR, DCD, RI)
.IP "\fBctrl-t d"
Toggle DTR
.IP "\fBctrl-t r"
Toggle RTS
.IP "\fBctrl-t v"
Show version
.IP "\fBctrl-t x"
Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol)
.IP "\fBctrl-t y"
Send file using the YMODEM protocol (prompts for file name)
.IP "\fBctrl-t ctrl-t"
Send ctrl-t character
.SH "SCRIPT API"
.SH "CONFIGURATION"
.PP
Tio suppots Lua scripting to easily automate interaction with the tty device.
.TP 16n
Options can be set via a configuration file using the INI format. tio uses the configuration file first found in the following locations in the order listed: $XDG_CONFIG_HOME/tio/tiorc, $HOME/.config/tio/tiorc, $HOME/.tiorc
In addition to the standard Lua API tio makes the following functions
and variables available:
.TP
Sections can be used to group settings and their names are only used for readability.
.TP 6n
.TP
.TP
tio will try to match the user input to a section pattern to get the tty and other options.
.IP "\fBtio.expect(pattern, timeout)"
Waits for the Lua pattern to match or timeout before continuing.
Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
.TP
Options without any section name sets the default options.
Returns the captures from the pattern or nil on timeout.
.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.
Returns a string up to size bytes long on success and nil on timeout.
.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.
Returns a string on success and nil on timeout. On timeout a partially read
line may be returned as a second return value.
.IP "\fBtio.write(string)"
Write string to serial device.
Returns the tio table.
.IP "\fBtio.send(file, protocol)"
Send file using x/y-modem protocol.
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
.IP "\fBtio.ttysearch()"
Search for serial devices.
Returns a table of number indexed tables, one for each serial device found.
Each of these tables contains the serial device information accessible via the
following string indexed elements "path", "tid", "uptime", "driver",
"description".
Returns nil if no serial devices are found.
.IP "\fBtio.set{line=state, ...}"
Set state of one or multiple tty modem lines.
Line can be any of DTR, RTS, CTS, DSR, CD, RI
State is high, low, or toggle.
.IP "\fBtio.sleep(seconds)"
Sleep for seconds.
.IP "\fBtio.msleep(ms)"
Sleep for milliseconds.
.IP "\fBtio.alwaysecho"
A boolean value, defaults to true.
If tio.alwaysecho is false, the result of tio.read, tio.readline or tio.expect
will only be returned from the function and not logged or printed.
If tio.alwaysecho is set to true, reading functions also emit a single
timestamp to stdout and log file per options.timestamp and options.log.
.SH "CONFIGURATION FILE"
.PP
Options can be set via configuration file using the INI format. \fBtio\fR uses
the configuration file first found in the following locations in the order
listed:
.PP
.I $XDG_CONFIG_HOME/tio/config
.PP
.I $HOME/.config/tio/config
.PP
.I $HOME/.tioconfig
.PP
Labels can be used to group settings into named configuration profiles which
can be activated from the command-line when starting tio.
.PP
\fBtio\fR will try to match the user input to a configuration profile by name or by
pattern to get the TTY device and other options.
.PP
Options without any label change the default options.
.PP
Any options set via command-line will override options set in the configuration file.
.PP
.TP
The following configuration file options are available:
.TP 25n
.IP "\fBpattern"
Pattern matching user input. This pattern can be an extended regular expression with a single group.
.IP "\fBdevice"
TTY device to open. If it contains a "%s" it is substituted with the first group match.
pattern matching user input. This pattern can be an extended regular expression with a single group.
.IP "\fBtty"
tty device to open. If tty contains a "%s" it will be substituted with the first group match.
.IP "\fBbaudrate"
Set baud rate
.IP "\fBdatabits"
@ -547,230 +186,83 @@ Set stop bits
.IP "\fBparity"
Set parity
.IP "\fBoutput-delay"
Set output character delay
.IP "\fBoutput-line-delay"
Set output line delay
.IP "\fBline-pulse-duration"
Set line pulse duration
.IP "\fBno-reconnect"
Do not reconnect
Set output delay
.IP "\fBno-autoconnect"
Disable automatic connect
.IP "\fBlog"
Enable log to file
.IP "\fBlog-file"
Set log filename
.IP "\fBlog-directory"
Set log directory path in which to save automatically named log files.
.IP "\fBlog-append"
Append to log file
.IP "\fBlog-strip"
Enable strip of control and escape sequences from log
Log to file
.IP "\fBlocal-echo"
Enable local echo
.IP "\fBtimestamp"
Enable line timestamp
.IP "\fBtimestamp-format"
Set timestamp format
.IP "\fBtimestamp-timeout"
Set timestamp timeout
Prefix each new line with a timestamp
.IP "\fBlog-filename"
Set log filename
.IP "\fBmap"
Map characters on input or output
Map special characters on input or output
.IP "\fBcolor"
Colorize tio text using ANSI color code ranging from 0 to 255
.IP "\fBinput-mode"
Set input mode
.IP "\fBoutput-mode"
Set output mode
.IP "\fBsocket"
Set socket to redirect I/O to
.IP "\fBprefix-ctrl-key"
Set prefix ctrl key (a..z or 'none', default: t)
.IP "\fBrs-485"
Enable RS-485 mode
.IP "\fBrs-485-config"
Set RS-485 configuration
.IP "\fBalert"
Set alert action on connect/disconnect
.IP "\fBmute"
Mute tio messages
.IP "\fBscript"
Run script from string
.IP "\fBscript-file"
Run script from file
.IP "\fBscript-run"
Run script on connect
.IP "\fBexec"
Execute shell command with I/O redirected to device
Colorize tio text using ANSI color code ranging from 0 to 255.
.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 EXAMPLES"
.TP
To change the default configuration simply set options like so:
A Typical section used as a short-hand would be defined as such:
.RS
.nf
.eo
[default]
baudrate = 9600
databits = 8
parity = none
stopbits = 1
color = 10
line-pulse-duration = DTR=200,RTS=400
[ftdi device]
pattern=ftdi
baudrate=115200
tty=/dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
color=11
.ec
.fi
.RE
.TP
Named configuration profiles can be added via labels:
With this section defined in the configuration file the following commands would be equivalent:
tio ftdi
tio -b 115200 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
.TP
A pattern can also be a regular expression:
.RS
.nf
.eo
[rpi3]
device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
baudrate = 115200
color = 11
[usb device]
pattern=usb([0-9]*)
baudrate=115200
tty=/dev/ttyUSB%s
.ec
.fi
.RE
.TP
Activate the configuration profile by name:
Making the following commands equivalent:
$ tio rpi3
tio usb12
.TP
Which is equivalent to:
$ tio -b 115200 -c 11 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
.TP
A configuration profile can also be activated by its pattern which supports regular expressions:
.RS
.nf
.eo
[usb-devices]
pattern = ^usb([0-9]*)
device = /dev/ttyUSB%m1
baudrate = 115200
.ec
.fi
.RE
.TP
Activate the configuration profile by pattern match:
$ tio usb12
.TP
Which becomes equivalent to:
$ tio -b 115200 /dev/ttyUSB12
.TP
It is also possible to combine use of configuration profile and command-line options. For example:
$ tio -l -t usb12
tio -b 115200 /dev/ttyUSB12
.SH "EXAMPLES"
.TP
Typical use is without options:
Typical use is without options. For example:
$ tio /dev/ttyUSB0
tio /dev/ttyUSB0
.TP
Which corresponds to the commonly used default options:
Which corresponds to the commonly used options:
$ tio \-b 115200 \-d 8 \-f none \-s 1 \-p none /dev/ttyUSB0
tio \-b 115200 \-d 8 \-f none \-s 1 \-p none /dev/ttyUSB0
.TP
It is recommended to connect serial TTY devices by ID:
It is recommended to connect serial tty devices by ID. For example:
$ tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
.PP
Using serial devices by ID ensures that tio automatically reconnects to the
correct serial device if it is disconnected and then reconnected.
.TP
Redirect serial device I/O to Unix file socket for scripting:
$ tio -S unix:/tmp/tio-socket0 /dev/ttyUSB0
.TP
Then, to issue a command via the file socket simply do:
$ echo "ls -la" | nc -UN /tmp/tio-socket0 > /dev/null
.TP
Or use the expect command to script an interaction:
.RS
.nf
.eo
#!/usr/bin/expect -f
.sp
set timeout -1
log_user 0
.sp
spawn nc -UN /tmp/tio-socket0
set uart $spawn_id
.sp
send -i $uart "date\n"
expect -i $uart "prompt> "
send -i $uart "ls -la\n"
expect -i $uart "prompt> "
.ec
.fi
.RE
.TP
It is also possible to use tio's own simpler expect/send script functionality to e.g. automate logins:
$ tio --script 'tio.expect("login: "); tio.write("root\\n"); tio.expect("Password: "); tio.write("root\\n")' /dev/ttyUSB0
.TP
Redirect device I/O to network file socket for remote TTY sharing:
$ tio --socket inet:4444 /dev/ttyUSB0
.TP
Then, use netcat to connect to the shared TTY session over network (assuming tio is hosted on IP 10.0.0.42):
$ nc -N 10.0.0.42 4444
.TP
Pipe command to the serial device:
$ echo "ls -la" | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
.TP
Pipe command to serial device and wait for line response within 1 second:
$ echo "*IDN?" | tio /dev/ttyACM0 --script "tio.expect('\\r\\n', 1000)" --mute
.TP
.TP
Likewise, to pipe data from file to the serial device:
$ cat data.bin | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
.TP
Map NL to CR-NL on input from device and DEL to BS on output to device:
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
.TP
Enable RS-485 mode:
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
.TP
Manipulate DTR and RTS lines upon first connect to reset connected microcontroller:
$ tio --script "tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{RTS=toggle}" --script-run once /dev/ttyUSB0
correct serial device if the device is disconnected and then reconnected.
.SH "WEBSITE"
.PP
@ -778,4 +270,4 @@ Visit https://tio.github.io
.SH "AUTHOR"
.PP
Maintained by Martin Lund <martin.lund@keep\-it\-simple.com>.
Written by Martin Lund <martin.lund@keep\-it\-simple.com>.

View file

@ -1,625 +0,0 @@
tio(1) User Commands tio(1)
NAME
tio - a serial device I/O tool
SYNOPSIS
tio [<options>] <tty-device|profile|tid>
DESCRIPTION
tio is a serial device tool which features a straightforward command-line and configuration file interface to easily connect to serial TTY devices for basic I/O operations.
OPTIONS
-b, --baudrate <bps>
Set baud rate [bps] (default: 115200).
-d, --databits 5|6|7|8
Set data bits (default: 8).
-f, --flow hard|soft|none
Set flow control (default: none).
-s, --stopbits 1|2
Set stop bits (default: 1).
-p, --parity odd|even|none|mark|space
Set parity (default: none).
Note: With mark parity the parity bit is always 0. With space parity the parity bit is always 1. Not all platforms support mark and space parity.
-o, --output-delay <ms>
Set output delay [ms] inserted between each sent character (default: 0).
-O, --output-line-delay <ms>
Set output delay [ms] inserted between each sent line (default: 0).
--line-pulse-duration <duration>
Set the pulse duration [ms] of each serial port line using the following key value pair format in the duration field: <key>=<value>
Each key represents a serial line. The following keys are available:
DTR Data Terminal Ready
RTS Request To Send
CTS Clear To Send
DSR Data Set Ready
DCD Data Carrier Detect
RI Ring Indicator
If defining more than one key value pair, the pairs must be comma separated.
The default pulse duration for each line is 100 ms.
-a, --auto-connect new|latest|direct
Automatically connect to serial device according to one of the following strategies:
new Automatically connect to first new appearing serial device.
latest Automatically connect to latest registered serial device.
direct Connect directly to specified TTY device.
All the listed strategies automatically reconnects according to strategy if device is not available or connection is lost.
Default value is "direct".
--exclude-devices <pattern>
Exclude devices by pattern ('*' and '?' supported).
--exclude-drivers <pattern>
Exclude drivers by pattern ('*' and '?' supported).
--exclude-tids <pattern>
Exclude topology IDs by pattern ('*' and '?' supported).
-n, --no-reconnect
Do not reconnect.
This means that tio will exit if it fails to connect to device or an established connection is lost.
-e, --local-echo
Enable local echo.
-t, --timestamp
Enable line timestamp.
--timestamp-format <format>
Set timestamp format to any of the following timestamp formats:
24hour 24-hour format ("hh:mm:ss.sss")
24hour-start 24-hour format relative to start time
24hour-delta 24-hour format relative to previous timestamp
iso8601 ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
epoch Seconds since Unix epoch (1970-01-01)
epoch-usec Seconds since Unix epoch (1970-01-01) with subdivision microseconds
Default format is 24hour
--timestamp-timeout <ms>
Set timestamp timeout value in milliseconds.
This value only takes effect in hex output mode where timestamps are only printed after elapsed timeout time of no output activity from tty device.
Default value is 200.
-l, --list
List available targets (serial devices, TIDs, configuration profiles).
-L, --log
Enable log to file.
The log file will be automatically named using the following format tio_TARGET_YYYY-MM-DDTHH:MM:SS.log. Target being the command line target such as tty-device, tid, or configuration profile.
The filename can be manually set using the --log-file option.
--log-file <filename>
Set log filename.
--log-directory <path>
Set log directory path in which to save automatically named log files.
--log-append
Append to log file.
--log-strip
Strip control characters and escape sequences from log.
-m, --map <flags>
Map (replace, translate) characters on input to the serial device or output from the serial device. The following mapping flags are supported:
ICRNL Map CR to NL on input (unless IGNCR is set)
IGNCR Ignore CR on input
IFFESCC Map FF to ESC-c on input
INLCR Map NL to CR 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
ODELBS Map DEL to BS on output
ONLCRNL Map NL to CR-NL on output
OLTU Map lowercase characters to uppercase on output
ONULBRK Map nul (zero) to send break signal on output
OIGNCR Ignore CR on output
If defining more than one flag, the flags must be comma separated.
--input-mode normal|hex|line
Set input mode.
In normal mode input characters are sent immediately as they are typed.
In hex input mode bytes can be sent by typing the two-character hexadecimal representation of the 1 byte value, e.g.: to send 0xA you must type 0a or 0A.
In line input mode input characters are sent when you press enter. The only editing feature supported in this mode is backspace.
Default value is "normal".
--output-mode normal|hex|hexN
Set output mode.
In hex mode each incoming byte is printed out as a 1 byte hex value.
In hexN mode, N is a number less than or equal to 4096 which defines how many hex values will be printed before a line break.
Default value is "normal".
-c, --color 0..255|bold|none|list
Colorize tio text using ANSI color code value ranging from 0 to 255 or use "none" for no color or use "bold" to apply bold formatting to existing system color.
Use "list" to print a list of available ANSI color codes.
Default value is "bold".
-S, --socket <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 multi
plexed to the terminal and all connected clients.
Sockets remain open while the serial port is disconnected, and writes will block.
Various socket types are supported using the following prefixes in the socket field:
unix:<filename> Unix Domain Socket (file)
inet:<port> Internet Socket (network)
inet6:<port> Internet IPv6 Socket (network)
If port is 0 or no port is provided default port 3333 is used.
At present there is a hardcoded limit of 16 clients connected at one time.
--rs-485
Enable RS-485 mode.
--rs-485-config <config>
Set the RS-485 configuration using the following key or key value pair format in the configuration field:
RTS_ON_SEND=value Set logical level (0 or 1) for RTS pin when sending
RTS_AFTER_SEND=value Set logical level (0 or 1) for RTS pin after sending
RTS_DELAY_BEFORE_SEND=value Set RTS delay (ms) before sending
RTS_DELAY_AFTER_SEND=value Set RTS delay (ms) after sending
RX_DURING_TX Receive data even while sending data
If defining more than one key or key value pair, they must be comma separated.
--alert none|bell|blink
Set alert action on connect/disconnect.
It will sound the bell once or blink once on successful connect. Likewise it will sound the bell twice or blink twice on disconnect.
Default value is "none".
--mute
Mute tio messages.
--script <string>
Run script from string.
--script-file <filename>
Run script from file with filename.
--script-run once|always|never
Run script on connect once, always, or never.
Default value is "always".
--exec <command>
Execute shell command with I/O redirected to device
--complete-profiles
Prints profiles (for shell completion)
-v, --version
Display program version.
-h, --help
Display help.
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 key commands:
ctrl-t ? List available key commands
ctrl-t b Send serial break (triggers SysRq on Linux, etc.)
ctrl-t c Show configuration (baudrate, databits, etc.)
ctrl-t e Toggle local echo mode
ctrl-t f Toggle log to file
ctrl-t F Flush data I/O buffers (discard data written but not transmitted and data received but not read)
ctrl-t g Toggle serial port line
ctrl-t i Toggle input mode
ctrl-t l Clear screen
ctrl-t L Show line states (DTR, RTS, CTS, DSR, DCD, RI)
ctrl-t m Change mapping of characters on input or output
ctrl-t o Toggle output mode
ctrl-t p Pulse serial port line
ctrl-t q Quit
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 t Toggle line timestamp mode
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 y Send file using the YMODEM protocol (prompts for file name)
ctrl-t ctrl-t Send ctrl-t character
SCRIPT API
Tio suppots Lua scripting to easily automate interaction with the tty device.
In addition to the standard Lua API tio makes the following functions available:
expect(string, timeout)
Expect string - waits for string to match or timeout before continuing. Supports regular expressions. Special characters must be escaped with '\\'. Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
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.
read(size, timeout)
Read up to size bytes from serial device. If timeout is 0 or not provided it will wait forever until data is ready to read.
Returns number of bytes read on success, 0 on timeout, or -1 on error.
On success, returns read string as second return value. Also emits a single timestamp to stdout and log file per options.timestamp and options.log.
read_line(timeout)
Read line from serial device. If timeout is 0 or not provided it will wait forever until data is ready to read.
Returns number of bytes read on success, 0 on timeout, or -1 on error.
On success, returns the string that was read as second return value. Also emits a single timestamp to stdout and log file per options.timestamp and options.log.
write(string)
Write string to serial device.
Returns number of bytes written on success or -1 on error.
send(file, protocol)
Send file using x/y-modem protocol.
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
tty_search()
Search for serial devices.
Returns a table of number indexed tables, one for each serial device found. Each of these tables contains the serial device information accessible via the following string indexed elements "path", "tid", "uptime", "dri
ver", "description".
Returns nil if no serial devices are found.
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.
sleep(seconds)
Sleep for seconds.
msleep(ms)
Sleep for milliseconds.
exit(code)
Exit with exit code.
CONFIGURATION FILE
Options can be set via configuration file using the INI format. tio uses the configuration file first found in the following locations in the order listed:
$XDG_CONFIG_HOME/tio/config
$HOME/.config/tio/config
$HOME/.tioconfig
Labels can be used to group settings into named configuration profiles which can be activated from the command-line when starting tio.
tio will try to match the user input to a configuration profile by name or by pattern to get the TTY device and other options.
Options without any label change the default options.
Any options set via command-line will override options set in the configuration file.
The following configuration file options are available:
pattern Pattern matching user input. This pattern can be an extended regular expression with a single group.
device TTY device to open. If it contains a "%s" it is substituted with the first group match.
baudrate Set baud rate
databits Set data bits
flow Set flow control
stopbits Set stop bits
parity Set parity
output-delay Set output character delay
output-line-delay Set output line delay
line-pulse-duration Set line pulse duration
no-reconnect Do not reconnect
log Enable log to file
log-file Set log filename
log-directory Set log directory path in which to save automatically named log files.
log-append Append to log file
log-strip Enable strip of control and escape sequences from log
local-echo Enable local echo
timestamp Enable line timestamp
timestamp-format Set timestamp format
timestamp-timeout Set timestamp timeout
map Map characters on input or output
color Colorize tio text using ANSI color code ranging from 0 to 255
input-mode Set input mode
output-mode Set output mode
socket Set socket to redirect I/O to
prefix-ctrl-key Set prefix ctrl key (a..z or 'none', default: t)
rs-485 Enable RS-485 mode
rs-485-config Set RS-485 configuration
alert Set alert action on connect/disconnect
mute Mute tio messages
script Run script from string
script-file Run script from file
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
To change the default configuration simply set options like so:
[default]
baudrate = 9600
databits = 8
parity = none
stopbits = 1
color = 10
line-pulse-duration = DTR=200,RTS=400
Named configuration profiles can be added via labels:
[rpi3]
device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
baudrate = 115200
color = 11
Activate the configuration profile by name:
$ tio rpi3
Which is equivalent to:
$ 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 regular expressions:
[usb-devices]
pattern = ^usb([0-9]*)
device = /dev/ttyUSB%m1
baudrate = 115200
Activate the configuration profile by pattern match:
$ tio usb12
Which becomes equivalent to:
$ tio -b 115200 /dev/ttyUSB12
It is also possible to combine use of configuration profile and command-line options. For example:
$ tio -l -t usb12
EXAMPLES
Typical use is without options:
$ tio /dev/ttyUSB0
Which corresponds to the commonly used default options:
$ tio -b 115200 -d 8 -f none -s 1 -p none /dev/ttyUSB0
It is recommended to connect serial TTY devices by ID:
$ tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
Using serial devices by ID ensures that tio automatically reconnects to the correct serial device if it is disconnected and then reconnected.
Redirect serial device I/O to Unix file socket for scripting:
$ tio -S unix:/tmp/tio-socket0 /dev/ttyUSB0
Then, to issue a command via the file socket simply do:
$ echo "ls -la" | nc -UN /tmp/tio-socket0 > /dev/null
Or use the expect command to script an interaction:
#!/usr/bin/expect -f
set timeout -1
log_user 0
spawn nc -UN /tmp/tio-socket0
set uart $spawn_id
send -i $uart "date\n"
expect -i $uart "prompt> "
send -i $uart "ls -la\n"
expect -i $uart "prompt> "
It is also possible to use tio's own simpler expect/send script functionality to e.g. automate logins:
$ tio --script 'expect("login: "); write("root\n"); expect("Password: "); write("root\n")' /dev/ttyUSB0
Redirect device I/O to network file socket for remote TTY sharing:
$ tio --socket inet:4444 /dev/ttyUSB0
Then, use netcat to connect to the shared TTY session over network (assuming tio is hosted on IP 10.0.0.42):
$ nc -N 10.0.0.42 4444
Pipe command to the serial device:
$ echo "ls -la" | tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
Pipe command to serial device and wait for line response within 1 second:
$ echo "*IDN?" | tio /dev/ttyACM0 --script "expect('\r\n', 1000)" --mute
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
Map NL to CR-NL on input from device and DEL to BS on output to device:
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
Enable RS-485 mode:
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
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
WEBSITE
Visit https://tio.github.io
AUTHOR
Maintained by Martin Lund <martin.lund@keep-it-simple.com>.
tio 3.9 2025-04-13 tio(1)

View file

@ -1,12 +1,12 @@
project('tio', 'c',
version : '3.9',
license : 'GPL-2.0-or-later',
version : '1.37',
license : [ 'GPL-2'],
meson_version : '>= 0.53.2',
default_options : [ 'warning_level=2', 'buildtype=release', 'c_std=gnu99' ]
)
# The tag date of the project_version(), update when the version bumps.
version_date = '2025-04-13'
version_date = '2022-04-13'
# Test for dynamic baudrate configuration interface
compiler = meson.get_compiler('c')
@ -71,17 +71,5 @@ foreach rate : test_baudrates
endif
endforeach
# Test for RS-485 support on Linux
enable_rs485 = false
if host_machine.system() == 'linux'
if compiler.check_header('linux/serial.h')
enable_rs485 = compiler.has_header_symbol('sys/ioctl.h', 'TIOCSRS485')
endif
endif
subdir('src')
install_man_pages = get_option('install_man_pages')
if install_man_pages
subdir('man')
endif
subdir('man')

View file

@ -1,6 +1,3 @@
option('bashcompletiondir',
type : 'string',
description : 'Directory for bash completion scripts ["no" disables]')
option('install_man_pages',
type : 'boolean', value: true,
description : 'Install man pages')

View file

@ -1,83 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 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 <stdio.h>
#include <unistd.h>
#include "options.h"
#include "alert.h"
void blink_background(void)
{
// Turn on reverse video
printf("\e[?5h");
fflush(stdout);
usleep(200*1000);
// Turn on normal video
printf("\e[?5l");
fflush(stdout);
}
void sound_bell(void)
{
// Audio bell
printf("\a");
fflush(stdout);
}
void alert_connect(void)
{
switch (option.alert)
{
case ALERT_NONE:
break;
case ALERT_BELL:
sound_bell();
break;
case ALERT_BLINK:
blink_background();
break;
default:
break;
}
}
void alert_disconnect(void)
{
switch (option.alert)
{
case ALERT_NONE:
break;
case ALERT_BELL:
sound_bell();
usleep(200*1000);
sound_bell();
break;
case ALERT_BLINK:
blink_background();
usleep(200*1000);
blink_background();
break;
default:
break;
}
}

View file

@ -1,33 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 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
typedef enum
{
ALERT_NONE,
ALERT_BELL,
ALERT_BLINK,
ALERT_END,
} alert_t;
void alert_connect(void);
void alert_disconnect(void);

View file

@ -16,38 +16,13 @@ _tio()
-s --stopbits \
-p --parity \
-o --output-delay \
-o --output-line-delay \
--line-pulse-duration \
-a --auto-connect \
--exclude-devices \
--exclude-drivers \
--exclude-tids \
-n --no-reconnect \
-e --local-echo \
-n --no-autoconnect \
-l --log \
--log-file \
--log-directory \
--log-append \
--log-strip \
-m --map \
-t --timestamp \
--timestamp-format \
--timestamp-timeout \
-L --list \
-c --color \
-S --socket \
--input-mode \
--output-mode \
--rs-485 \
--rs-485-config \
--alert \
--mute \
--script \
--script-file \
--script-run \
--exec \
--complete-profiles \
-v --version \
-t --timestamp \
-L --list-devices \
-c --color \
-h --help"
# Complete the arguments to the options.
@ -61,6 +36,10 @@ _tio()
COMPREPLY=( $(compgen -W "5 6 7 8" -- ${cur}) )
return 0
;;
-h | --local-echo)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
-f | --flow)
COMPREPLY=( $(compgen -W "hard soft none" -- ${cur}) )
return 0
@ -74,51 +53,39 @@ _tio()
return 0
;;
-o | --output-delay)
COMPREPLY=( $(compgen -W "1 10 100" -- ${cur}) )
COMPREPLY=( $(compgen -W "0 1 10 100" -- ${cur}) )
return 0
;;
-O | --output-line-delay)
COMPREPLY=( $(compgen -W "1 10 100" -- ${cur}) )
-n | --no-autoconnect)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
-a | --auto-connect)
COMPREPLY=( $(compgen -W "new latest none" -- ${cur}) )
-l | --log)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
-m | --map)
COMPREPLY=( $(compgen -W "ICRNL IGNCR INLCR IFFESCC INLCRNL ICRCRNL IMSB2LSB OCRNL ODELBS ONLCRNL OLTU ONULBRK OIGNCR" -- ${cur}) )
COMPREPLY=( $(compgen -W "ICRNL IGNCR INLCR INLCRNL OCRNL ODELBS ONLCRNL" -- ${cur}) )
return 0
;;
--timestamp-format)
COMPREPLY=( $(compgen -W "24hour 24hour-start 24hour-delta iso8601 epoch epoch-usec" -- ${cur}) )
-t | --timestamp)
COMPREPLY=( $(compgen -W "24hour 24hour-start iso8601" -- ${cur}) )
return 0
;;
-L | --list-devices)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
-c | --color)
COMPREPLY=( $(compgen -W "$(seq 0 255) none list" -- ${cur}) )
COMPREPLY=( $(compgen -W "$(seq 0 255)" -- ${cur}) )
return 0
;;
-S | --socket)
COMPREPLY=( $(compgen -W "unix: inet: inet6:" -- ${cur}) )
-v | --version)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
--input-mode)
COMPREPLY=( $(compgen -W "normal hex line" -- ${cur}) )
return 0
;;
--output-mode)
COMPREPLY=( $(compgen -W "normal hex" -- ${cur}) )
return 0
;;
--rs-485-config)
COMPREPLY=( $(compgen -W "RTS_ON_SEND RTS_AFTER_SEND RTS_DELAY_BEFORE_SEND RTS_DELAY_AFTER_SEND RX_DURING_TX" -- ${cur}) )
return 0
;;
--alert)
COMPREPLY=( $(compgen -W "none bell blink" -- ${cur}) )
return 0
;;
--script-run)
COMPREPLY=( $(compgen -W "once always never" -- ${cur}) )
-h | --help)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
*)
@ -131,14 +98,12 @@ _tio()
;;
esac
profiles="`tio --complete-profiles`"
if [ -d /dev/serial/by-id ]; then
ttys=$(printf '%s\n' /dev/tty* /dev/serial/by-id/*)
else
ttys=$(printf '%s\n' /dev/tty*)
fi
COMPREPLY=( $(compgen -W "${ttys} ${profiles}" -- ${cur}) )
COMPREPLY=( $(compgen -W "${ttys}" -- ${cur}) )
return 0
}

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2020-2022 Liam Beguin
* Copyright (c) 2022 Martin Lund
@ -19,770 +19,257 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#define _GNU_SOURCE
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <assert.h>
#include <libgen.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <getopt.h>
#include <termios.h>
#include <limits.h>
#include <unistd.h>
#include <regex.h>
#include <glib.h>
#include <ini.h>
#include "options.h"
#include "configfile.h"
#include "timestamp.h"
#include "print.h"
#include "rs485.h"
#include "misc.h"
#include "options.h"
#include "error.h"
#include "print.h"
#define CONFIG_GROUP_NAME_DEFAULT "default"
#define CONFIG_GROUP_INCLUDE_PREFIX "include "
#define MAX_LINE_LENGTH 1024
static struct config_t *c;
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 int get_match(const char *input, const char *pattern, char **match)
{
(void)dest;
GError *error = NULL;
bool mismatch = true;
int ret;
int len = 0;
regex_t re;
regmatch_t m[2];
char err[128];
gchar *string = g_key_file_get_string(key_file, group, key, &error);
if (error != NULL)
/* compile a regex with the pattern */
ret = regcomp(&re, pattern, REG_EXTENDED);
if (ret)
{
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
{
// Key not found - ignore key
g_error_free(error);
return;
}
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
regerror(ret, &re, err, sizeof(err));
printf("reg error: %s\n", err);
return ret;
}
va_list args;
const char* current_arg = allowed_string;
va_start(args, allowed_string);
if (current_arg == NULL)
/* try to match on input */
ret = regexec(&re, input, 2, m, 0);
if (!ret)
{
mismatch = false;
len = m[1].rm_eo - m[1].rm_so;
}
// Iterate through variable arguments
while (current_arg != NULL)
regfree(&re);
if (len)
{
if (strcmp(string, current_arg) == 0)
{
mismatch = false;
break;
}
current_arg = va_arg(args, const char *);
asprintf(match, "%s", &input[m[1].rm_so]);
}
if (mismatch)
{
tio_error_print("%s: Invalid %s value '%s' in %s profile", config.path, key, string, group);
exit(EXIT_FAILURE);
}
va_end(args);
*dest = string;
return len;
}
static void config_get_integer(GKeyFile *key_file, gchar *group, gchar *key, int *dest, int min, int max)
/**
* data_handler() - walk config file to load parameters matching user input
*
* INIH handler used to get all parameters from a given section
*/
static int data_handler(void *user, const char *section, const char *name,
const char *value)
{
(void)dest;
GError *error = NULL;
UNUSED(user);
int value = g_key_file_get_integer(key_file, group, key, &error);
if (error != NULL)
// If section matches user input or unnamed section
if ((!strcmp(section, c->section_name)) || (!strcmp(section, "")))
{
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
// Set configuration parameter if found
if (!strcmp(name, "tty"))
{
// Key not found - ignore key
g_error_free(error);
return;
asprintf(&c->tty, value, c->match);
option.tty_device = c->tty;
}
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
if ((value < min) || (value > max))
else if (!strcmp(name, "baudrate"))
{
tio_error_print("%s: Invalid %s value '%d' in %s profile", config.path, key, value, group);
exit(EXIT_FAILURE);
option.baudrate = string_to_long((char *)value);
}
*dest = value;
else if (!strcmp(name, "databits"))
{
option.databits = atoi(value);
}
else if (!strcmp(name, "flow"))
{
asprintf(&c->flow, "%s", value);
option.flow = c->flow;
}
else if (!strcmp(name, "stopbits"))
{
option.stopbits = atoi(value);
}
else if (!strcmp(name, "parity"))
{
asprintf(&c->parity, "%s", value);
option.parity = c->parity;
}
else if (!strcmp(name, "output-delay"))
{
option.output_delay = atoi(value);
}
else if (!strcmp(name, "no-autoconnect"))
{
option.no_autoconnect = atoi(value);
}
else if (!strcmp(name, "log"))
{
option.log = atoi(value);
}
else if (!strcmp(name, "local-echo"))
{
option.local_echo = atoi(value);
}
else if (!strcmp(name, "timestamp"))
{
option.timestamp = timestamp_option_parse(value);
}
else if (!strcmp(name, "log-filename"))
{
asprintf(&c->log_filename, "%s", value);
option.log_filename = c->log_filename;
}
else if (!strcmp(name, "map"))
{
asprintf(&c->map, "%s", value);
option.map = c->map;
}
else if (!strcmp(name, "color"))
{
option.color = atoi(value);
}
}
return 0;
}
static void config_get_bool(GKeyFile *key_file, gchar *group, gchar *key, bool *dest)
/**
* section_search_handler() - walk config file to find section matching user input
*
* INIH handler used to resolve the section matching the user's input.
* This will look for the pattern element of each section and try to match it
* with the user input.
*/
static int section_search_handler(void *user, const char *section, const char
*varname, const char *varval)
{
(void)dest;
GError *error = NULL;
UNUSED(user);
bool value = g_key_file_get_boolean(key_file, group, key, &error);
if (error != NULL)
if (strcmp(varname, "pattern"))
return 0;
if (!strcmp(varval, c->user))
{
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
{
// Key not found - ignore key
g_error_free(error);
return;
/* pattern matches as plain text */
asprintf(&c->section_name, "%s", section);
}
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
else if (get_match(c->user, varval, &c->match) > 0)
{
/* pattern matches as regex */
asprintf(&c->section_name, "%s", section);
}
*dest = value;
return 0;
}
static void config_parse_keys(GKeyFile *key_file, char *group)
static int resolve_config_file(void)
{
char *string = 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, "databits", &option.databits, 5, 8);
config_get_string(key_file, group, "flow", &string, "none", "hard", "soft", NULL);
if (string != NULL)
{
option_parse_flow(string, &option.flow);
g_free((void *)string);
string = NULL;
}
config_get_integer(key_file, group, "stopbits", &option.stopbits, 1, 2);
config_get_string(key_file, group, "parity", &string, "odd", "even", "none", "mark", "space", NULL);
if (string != NULL)
{
option_parse_parity(string, &option.parity);
g_free((void *)string);
string = NULL;
}
config_get_integer(key_file, group, "output-delay", &option.output_delay, 0, INT_MAX);
config_get_integer(key_file, group, "output-line-delay", &option.output_line_delay, 0, INT_MAX);
config_get_string(key_file, group, "line-pulse-duration", &string, NULL);
if (string != NULL)
{
option_parse_line_pulse_duration(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "auto-connect", &string, "new", "latest", "direct", NULL);
if (string != NULL)
{
option_parse_auto_connect(string, &option.auto_connect);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "exclude-devices", &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_bool(key_file, group, "no-reconnect", &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);
if (string != NULL)
{
option_parse_input_mode(string, &option.input_mode);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "output-mode", &string, NULL);
if (string != NULL)
{
option_parse_output_mode(string, &option.output_mode);
g_free((void *)string);
string = NULL;
}
config_get_bool(key_file, group, "timestamp", (bool*) &option.timestamp);
if (option.timestamp != TIMESTAMP_NONE)
{
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", "epoch", "epoch-usec", NULL);
if (string != NULL)
{
option_parse_timestamp(string, &option.timestamp);
g_free((void *)string);
string = NULL;
}
}
config_get_integer(key_file, group, "timestamp-timeout", &option.timestamp_timeout, 0, INT_MAX);
config_get_bool(key_file, group, "log", &option.log);
config_get_string(key_file, group, "log-file", &option.log_filename, NULL);
config_get_string(key_file, group, "log-directory", &option.log_directory, NULL);
config_get_bool(key_file, group, "log-append", &option.log_append);
config_get_bool(key_file, group, "log-strip", &option.log_strip);
config_get_string(key_file, group, "map", &string, NULL);
if (string != NULL)
{
option_parse_mappings(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "color", &string, NULL);
if (string != NULL)
{
if (strcmp(string, "list") == 0)
{
// Ignore
}
else if (strcmp(string, "none") == 0)
{
option.color = -1; // No color
}
else if (strcmp(string, "bold") == 0)
{
option.color = 256; // Bold
}
else
{
option.color = atoi(string);
if ((option.color < 0) || (option.color > 255))
{
tio_error_print("%s: Invalid color value in %s profile", config.path, group);
exit(EXIT_FAILURE);
}
}
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "socket", &option.socket, NULL);
config_get_bool(key_file, group, "rs-485", &option.rs485);
config_get_string(key_file, group, "rs-385-config", &string, NULL);
if (string != NULL)
{
rs485_parse_config(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "alert", &string, "bell", "blink", "none", NULL);
if (string != NULL)
{
option_parse_alert(string, &option.alert);
g_free((void *)string);
string = NULL;
}
config_get_bool(key_file, group, "mute", &option.mute);
config_get_string(key_file, group, "script", &option.script, NULL);
config_get_string(key_file, group, "script-file", &option.script_filename, NULL);
config_get_string(key_file, group, "script-run", &string, NULL);
if (string != NULL)
{
option_parse_script_run(string, &option.script_run);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "exec", &option.exec, NULL);
config_get_string(key_file, group, "prefix-ctrl-key", &string, NULL);
if (string != NULL)
{
if (strcmp(string, "none") == 0)
{
option.prefix_enabled = false;
}
else if (strlen(string) >= 2)
{
tio_error_print("%s: Invalid prefix-ctrl-key value in %s profile", config.path, group);
exit(EXIT_FAILURE);
}
else if (ctrl_key_code(string[0]) > 0)
{
option.prefix_enabled = true;
option.prefix_code = ctrl_key_code(string[0]);
option.prefix_key = string[0];
}
else
{
tio_error_print("%s: Invalid prefix-ctrl-key value in %s profile", config.path, group);
exit(EXIT_FAILURE);
}
g_free((void *)string);
string = NULL;
}
}
static int config_file_resolve(void)
{
char *xdg = getenv("XDG_CONFIG_HOME");
if (xdg)
{
if (asprintf(&config.path, "%s/tio/config", xdg) != -1)
{
if (access(config.path, F_OK) == 0)
asprintf(&c->path, "%s/tio/tiorc", getenv("XDG_CONFIG_HOME"));
if (!access(c->path, F_OK))
{
return 0;
}
free(config.path);
}
}
char *home = getenv("HOME");
if (home)
{
if (asprintf(&config.path, "%s/.config/tio/config", home) != -1)
{
if (access(config.path, F_OK) == 0)
asprintf(&c->path, "%s/.config/tio/tiorc", getenv("HOME"));
if (!access(c->path, F_OK))
{
return 0;
}
free(config.path);
}
if (asprintf(&config.path, "%s/.tioconfig", home) != -1)
{
if (access(config.path, F_OK) == 0)
asprintf(&c->path, "%s/.tiorc", getenv("HOME"));
if (!access(c->path, F_OK))
{
return 0;
}
free(config.path);
}
}
config.path = NULL;
return -EINVAL;
}
void config_file_show_profiles(void)
void config_file_parse(const int argc, char *argv[])
{
GString *config_buffer;
GError *error = NULL;
GKeyFile *keyfile;
int ret;
int i;
// Reset configuration
memset(&config, 0, sizeof(struct config_t));
c = malloc(sizeof(struct config_t));
memset(c, 0, sizeof(struct config_t));
// Find config file
if (config_file_resolve() != 0)
resolve_config_file();
for (i = 1; i < argc; i++)
{
// None found - stop parsing
return;
}
// Load content of configuration file into buffer
config_buffer = g_string_new(NULL);
config_file_load(config.path, config_buffer, false);
// 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)
if (argv[i][0] != '-')
{
g_error_free(error);
goto error_load;
}
// Get all group names
gsize num_groups;
gchar **group = g_key_file_get_groups(keyfile, &num_groups);
for (gsize i = 0; i < num_groups; i++)
{
// Skip default group
if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0)
{
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]);
}
g_strfreev(group);
error_load:
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
}
static void replace_substring(char *str, const char *substr, const char *replacement)
{
char *pos = strstr(str, substr);
if (pos != NULL)
{
int substrLen = strlen(substr);
int replacementLen = strlen(replacement);
memmove(pos + replacementLen, pos + substrLen, strlen(pos + substrLen) + 1);
memcpy(pos, replacement, replacementLen);
}
}
static char *match_and_replace(const char *str, const char *pattern, char *device)
{
char replacement_str[PATH_MAX] = {};
char m_key[14] = {};
regex_t regex;
assert(str != NULL);
assert(pattern != NULL);
assert(device != NULL);
char *string = calloc(PATH_MAX, 1);
if (string == NULL)
{
tio_debug_printf("Failure allocating string memory\n");
return NULL;
}
strncpy(string, device, PATH_MAX - 1);
/* Find matches of pattern in str. For each match, replace any '%mN' in the
* copy of the device string with the corresponding match subexpression and
* return the new formed device string.
*
* Note: %m0 = Full match expression.
* %m1 = First subexpression
* %m2 = Second subexpression
* %m3 = etc..
*/
if (regcomp(&regex, pattern, REG_EXTENDED) != 0)
{
// Failure to compile regular expression
tio_error_print("Failure compiling regular expression '%s'\n", pattern);
exit(EXIT_FAILURE);
}
regmatch_t matches[regex.re_nsub + 1];
int status = regexec(&regex, str, regex.re_nsub + 1, matches, 0);
if (status == 0)
{
tio_debug_printf("Full match: ");
int j = 0;
for (int i = matches[0].rm_so; i < matches[0].rm_eo; i++)
{
tio_debug_printf_raw("%c", str[i]);
replacement_str[j++] = str[i];
}
replacement_str[j] = '\0';
replace_substring(string, "%m0", replacement_str);
tio_debug_printf_raw("\n");
for (int i = 1; i < ((int)regex.re_nsub + 1) && matches[i].rm_so != -1; i++)
{
tio_debug_printf("Subexpression %d match: ", i);
int k = 0;
for (int l = matches[i].rm_so; l < matches[i].rm_eo; l++)
{
tio_debug_printf_raw("%c", str[l]);
replacement_str[k++] = str[l];
}
replacement_str[k] = '\0';
sprintf(m_key, "%%m%d", i);
replace_substring(string, m_key, replacement_str);
tio_debug_printf_raw("\n");
}
}
else if (status == REG_NOMATCH)
{
tio_debug_printf("No regex match\n");
goto error;
}
else
{
char error_message[100];
regerror(status, &regex, error_message, sizeof(error_message));
tio_debug_printf("Regex match failed: %s", error_message);
goto error;
}
regfree(&regex);
return string;
error:
regfree(&regex);
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)
{
// Find config file
if (config_file_resolve() != 0)
{
// None found - stop parsing
return;
}
if (option.target == NULL)
{
return;
}
GString *config_buffer = g_string_new(NULL);
GKeyFile *keyfile = g_key_file_new();
GList *included_files = NULL;
GError *error = NULL;
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);
g_string_free(config_buffer, TRUE);
g_key_file_free(keyfile);
g_error_free(error);
exit(EXIT_FAILURE);
}
// Parse default group/section
if (g_key_file_has_group(keyfile, CONFIG_GROUP_NAME_DEFAULT))
{
config_parse_keys(keyfile, CONFIG_GROUP_NAME_DEFAULT);
}
// Parse target
if (g_key_file_has_group(keyfile, option.target))
{
config.active_group = strdup(option.target);
config_parse_keys(keyfile, option.target);
}
else
{
// Find group by pattern
gsize num_groups;
gchar **group = g_key_file_get_groups(keyfile, &num_groups);
for (gsize i = 0; i < num_groups; i++)
{
// Skip default group
if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0)
{
continue;
}
// Lookup 'pattern' key
gchar *pattern = g_key_file_get_string(keyfile, group[i], "pattern", &error);
if (error != NULL)
{
g_error_free(error);
error = NULL;
continue;
}
// Lookup 'device' key
gchar *device = g_key_file_get_string(keyfile, group[i], "device", &error);
if (error != NULL)
{
g_error_free(error);
error = NULL;
continue;
}
// Match pattern against target and replace any sub expression
// matches (%mN) in device string and return resulting string
// representing the new pattern based string.
config.device = match_and_replace(option.target, pattern, device);
if (config.device != NULL)
{
// Match found - save device
device = strdup(config.device);
// Parse found group (may replace config.device)
config_parse_keys(keyfile, group[i]);
// Update configuration
config.active_group = strdup(group[i]);
config.device = device; // Restore new device string
c->user = argv[i];
break;
}
}
g_strfreev(group);
if (!c->user)
{
return;
}
// Cleanup
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
g_list_free_full(included_files, g_free);
ret = ini_parse(c->path, section_search_handler, NULL);
if (!c->section_name)
{
debug_printf("unable to match user input to configuration section (%d)\n", ret);
return;
}
atexit(&config_exit);
ret = ini_parse(c->path, data_handler, NULL);
if (ret < 0)
{
fprintf(stderr, "Error: unable to parse configuration file (%d)\n", ret);
exit(EXIT_FAILURE);
}
}
void config_exit(void)
{
free(config.active_group);
free(config.path);
free(config.device);
free(c->tty);
free(c->flow);
free(c->parity);
free(c->log_filename);
free(c->map);
free(c->match);
free(c->section_name);
free(c->path);
free(c);
}
void config_file_print(void)
void config_file_print()
{
if (config.path != NULL)
if (c->path != NULL)
{
tio_printf(" Active configuration file: %s", config.path);
if (config.active_group != NULL)
tio_printf(" Path: %s", c->path);
if (c->section_name != NULL)
{
tio_printf(" Active configuration profile: %s", config.active_group);
tio_printf(" Active config section: %s", c->section_name);
}
}
}
void config_list_targets(void)
{
memset(&config, 0, sizeof(struct config_t));
// Find config file
if (config_file_resolve() != 0)
{
// None found
return;
}
GKeyFile *keyfile;
GError *error = NULL;
keyfile = g_key_file_new();
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);
goto cleanup;
}
// Get all group names
gsize num_groups;
gchar **group = g_key_file_get_groups(keyfile, &num_groups);
if (num_groups == 0)
{
goto cleanup;
}
printf("\nConfiguration profiles (%s)\n", config.path);
printf("--------------------------------------------------------------------------------\n");
int j = 1;
for (gsize i = 0; i < num_groups; i++)
{
// Skip default group
if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0)
{
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]);
if (j++ % 4 == 0)
{
putchar('\n');
}
}
if ((j-1) % 4 != 0)
{
putchar('\n');
}
g_strfreev(group);
cleanup:
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
}

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2020 Liam Beguin
* Copyright (c) 2022 Martin Lund
@ -24,15 +24,19 @@
struct config_t
{
const char *user;
char *path;
char *active_group;
char *device;
char *section_name;
char *match;
char *tty;
char *flow;
char *parity;
char *log_filename;
char *map;
};
extern struct config_t config;
void config_file_print(void);
void config_file_parse(void);
void config_file_print();
void config_file_parse(const int argc, char *argv[]);
void config_exit(void);
void config_file_show_profiles(void);
void config_list_targets(void);

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -19,79 +19,27 @@
* 02110-1301, USA.
*/
#define _GNU_SOURCE // To access vasprintf
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "options.h"
#include "print.h"
#include "error.h"
static char error[2][1000];
static bool in_session = false;
bool error_normal = true;
void switch_error_output_mode(void)
{
error_normal = false;
}
void error_enter_session_mode(void)
{
in_session = true;
}
void error_printf_(const char *format, ...)
{
va_list args;
char *line;
va_start(args, format);
vasprintf(&line, format, args);
if (in_session)
{
if (print_tainted)
{
putchar('\n');
}
ansi_error_printf("[%s] %s", timestamp_current_time(), line);
}
else
{
fprintf(stderr, "%s\n", line);
}
va_end(args);
print_tainted = false;
free(line);
}
void tio_error_printf(const char *format, ...)
{
va_list args;
va_start(args, format);
vsnprintf(error[0], 1000, format, args);
va_end(args);
}
void tio_error_printf_silent(const char *format, ...)
{
va_list args;
va_start(args, format);
vsnprintf(error[1], 1000, format, args);
va_end(args);
}
char error[2][1000];
void error_exit(void)
{
if (error[0][0] != 0)
{
/* Print error */
error_printf_("Error: %s", error[0]);
tio_printf("Error: %s", error[0]);
}
else if ((error[1][0] != 0) && (option.no_reconnect))
else if ((error[1][0] != 0) && (option.no_autoconnect))
{
/* Print silent error */
error_printf_("Error: %s", error[1]);
tio_printf("Error: %s", error[1]);
}
}

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -21,15 +21,9 @@
#pragma once
#include <stdbool.h>
extern bool error_normal;
#define TIO_SUCCESS 0
#define TIO_ERROR 1
void tio_error_printf(const char *format, ...);
void tio_error_printf_silent(const char *format, ...);
extern char error[2][1000];
void error_exit(void);
void error_enter_session_mode(void);
void switch_error_output_mode(void);

226
src/fs.c
View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2022 Martin Lund
* Copyright (c) 2017 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -19,6 +19,10 @@
* 02110-1301, USA.
*/
#pragma once
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
int setspeed(int fd, int baudrate);
int iossiospeed(int fd, int baudrate)
{
return ioctl(fd, IOSSIOSPEED, (char *)&baudrate);
}

204
src/log.c
View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -19,20 +19,20 @@
* 02110-1301, USA.
*/
#define _GNU_SOURCE // To access vasprintf
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <libgen.h>
#include <errno.h>
#include "options.h"
#include "print.h"
#include "fs.h"
#include "error.h"
#define IS_ESC_CSI_INTERMEDIATE_CHAR(c) ((c >= 0x20) && (c <= 0x3F))
#define IS_ESC_END_CHAR(c) ((c >= 0x30) && (c <= 0x7E))
#define IS_CTRL_CHAR(c) ((c >= 0x00) && (c <= 0x1F))
static FILE *fp = NULL;
static char file_buffer[BUFSIZ];
static const char *log_filename = NULL;
static FILE *fp;
static bool log_error = false;
static char *date_time(void)
{
@ -48,173 +48,34 @@ static char *date_time(void)
return date_time_string;
}
int log_open(const char *filename)
void log_open(const char *filename)
{
char *automatic_filename;
char *dir_plus_automatic_filename;
static char automatic_filename[400];
if (filename == NULL)
{
// Generate filename if none provided
if (option.auto_connect == AUTO_CONNECT_DIRECT)
{
// File name format ("tio_TARGET_YYYY-MM-DDTHH:MM:SS.log")
asprintf(&automatic_filename, "tio_%s_%s.log", basename((char *)option.target), date_time());
}
else
{
// If using 'new' or 'latest' autoconnect strategy we simply use strategy
// name to name autogenerated log name as device names may vary
asprintf(&automatic_filename, "tio_%s_%s.log",
option_auto_connect_state_to_string(option.auto_connect),
date_time());
// Generate filename if none provided ("tio_DEVICE_YYYY-MM-DDTHH:MM:SS.log")
sprintf(automatic_filename, "tio_%s_%s.log", basename((char *)option.tty_device), date_time());
filename = automatic_filename;
option.log_filename = automatic_filename;
}
if (option.log_directory != NULL)
fp = fopen(filename, "w+");
if (fp == NULL)
{
if (fs_dir_exists(option.log_directory) == false)
{
tio_error_printf("Log directory not found");
log_error = true;
exit(EXIT_FAILURE);
}
asprintf(&dir_plus_automatic_filename, "%s/%s", option.log_directory, automatic_filename);
filename = dir_plus_automatic_filename;
}
else
{
filename = automatic_filename;
}
}
log_filename = filename;
// Open log file
if (option.log_append)
{
// Append to existing log file
fp = fopen(filename, "a+");
}
else
{
// Truncate existing log file
fp = fopen(filename, "w+");
}
if (fp == NULL)
{
tio_warning_printf("Could not open log file %s (%s)", filename, strerror(errno));
return -1;
}
// Enable line buffering
setvbuf(fp, file_buffer, _IOLBF, BUFSIZ);
return 0;
setvbuf(fp, NULL, _IONBF, 0);
}
bool log_strip(char c)
void log_write(char c)
{
static char previous_char = 0;
static bool esc_sequence = false;
bool strip = false;
/* Detect if character should be stripped or not */
switch (c)
{
case 0xa:
/* Line feed / new line */
/* Reset ESC sequence just in case something went wrong with the
* escape sequence parsing. */
esc_sequence = false;
break;
case 0x1b:
/* Escape */
strip = true;
break;
case 0x5b:
/* Left bracket */
if (previous_char == 0x1b)
{
// Start of ESC sequence
esc_sequence = true;
strip = true;
}
break;
default:
if (IS_CTRL_CHAR(c))
{
/* Strip ASCII control characters */
strip = true;
break;
}
else
if ((esc_sequence) && (IS_ESC_CSI_INTERMEDIATE_CHAR(c)))
{
strip = true;
break;
}
else
if ((esc_sequence) && (IS_ESC_END_CHAR(c)))
{
esc_sequence = false;
strip = true;
break;
}
break;
}
previous_char = c;
return strip;
}
void log_printf(const char *format, ...)
{
if (fp == NULL)
{
return;
}
char *line;
va_list(args);
va_start(args, format);
vasprintf(&line, format, args);
va_end(args);
fwrite(line, strlen(line), 1, fp);
free(line);
}
void log_putc(char c)
{
if (fp == NULL)
{
return;
}
if (option.output_mode == OUTPUT_MODE_HEX)
{
fprintf(fp, "%02x ", (unsigned char) c);
}
else
{
if (option.log_strip)
{
if (log_strip(c) == false)
if (fp != NULL)
{
fputc(c, fp);
}
}
else
{
fputc(c, fp);
}
}
}
void log_close(void)
@ -222,21 +83,22 @@ void log_close(void)
if (fp != NULL)
{
fclose(fp);
tio_printf("Saved log to file %s", log_filename);
fp = NULL;
log_filename = NULL;
}
}
void log_exit(void)
{
if ((option.log) && (log_filename != NULL))
if (option.log)
{
log_close();
}
}
const char *log_get_filename(void)
{
return log_filename;
if (log_error)
{
error_printf("Could not open log file %s (%s)", option.log_filename, strerror(errno));
}
else if (option.log)
{
tio_printf("Saved log to file %s", option.log_filename);
}
}

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -21,9 +21,7 @@
#pragma once
int log_open(const char *filename);
void log_printf(const char *format, ...);
void log_putc(char c);
void log_open(const char *filename);
void log_write(char c);
void log_close(void);
void log_exit(void);
const char * log_get_filename(void);

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -19,11 +19,10 @@
* 02110-1301, USA.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "version.h"
#include "config.h"
#include "options.h"
#include "configfile.h"
#include "tty.h"
@ -31,7 +30,6 @@
#include "error.h"
#include "print.h"
#include "signals.h"
#include "socket.h"
int main(int argc, char *argv[])
{
@ -43,104 +41,53 @@ int main(int argc, char *argv[])
/* Add error exit handler */
atexit(&error_exit);
/* Parse command-line options (1st pass) */
/* Parse configuration file */
config_file_parse(argc, argv);
atexit(&config_exit);
/* Parse command-line options */
options_parse(argc, argv);
if (option.complete_profiles)
/* List available serial devices */
if (option.list_devices)
{
config_file_show_profiles();
list_serial_devices();
return status;
}
/* Parse configuration file */
config_file_parse();
/* Parse command-line options (2nd pass) */
options_parse_final(argc, argv);
/* Configure tty device */
tty_configure();
/* Disable line buffering in stdout. This is necessary if we
* want things like local echo to work correctly. */
setvbuf(stdout, NULL, _IONBF, 0);
/* Configure input terminal */
if (isatty(fileno(stdin)))
{
stdin_configure();
}
else
{
// Enter non interactive mode
interactive_mode = false;
}
/* Switch error output format */
switch_error_output_mode();
/* Configure output terminal */
if (isatty(fileno(stdout)))
{
stdout_configure();
}
else
{
// No color when piping
option.color = -1;
}
/* Add log exit handler */
atexit(&log_exit);
/* Create log file */
if (option.log)
{
log_open(option.log_filename);
}
/* Initialize ANSI text formatting (colors etc.) */
print_init_ansi_formatting();
/* Change error printing mode */
error_enter_session_mode();
/* Enable ANSI text formatting (colors etc.) */
print_enable_ansi_formatting();
/* Print launch hints */
tio_printf("tio %s", VERSION);
if (interactive_mode)
{
tio_printf("Press ctrl-%c q to quit", option.prefix_key);
} else
{
tio_printf("Non-interactive mode enabled");
tio_printf("Press ctrl-c to quit");
}
/* Open socket */
if (option.socket)
{
socket_configure();
}
/* Spawn input handling into separate thread */
tty_input_thread_create();
/* Wait for input to be ready */
tty_input_thread_wait_ready();
tio_printf("tio v%s", VERSION);
tio_printf("Press ctrl-t q to quit");
/* Connect to tty device */
if (option.no_reconnect)
{
tty_search();
if (option.no_autoconnect)
status = tty_connect();
}
else
{
/* Enter connect loop */
while (true)
{
tty_wait_for_device();
tty_connect();
status = tty_connect();
}
}

View file

@ -1,10 +1,5 @@
# Generate version header
version_h = vcs_tag(command : ['git', 'describe', '--tags', '--always', '--dirty'],
input : 'version.h.in',
output :'version.h',
replace_string:'@VERSION@')
config_h = configuration_data()
config_h.set_quoted('VERSION', meson.project_version())
config_h.set('BAUDRATE_CASES', baudrate_cases)
configure_file(output: 'config.h', configuration: config_h)
@ -17,56 +12,25 @@ tio_sources = [
'tty.c',
'print.c',
'configfile.c',
'signals.c',
'socket.c',
'setspeed.c',
'rs485.c',
'timestamp.c',
'alert.c',
'xymodem.c',
'script.c',
'fs.c',
'readline.c',
version_h
'signals.c'
]
tio_dep = dependency('inih', required: true,
fallback : ['libinih', 'inih_dep'],
default_options: ['default_library=static', 'distro_install=false'])
foreach name: ['lua-5.4', 'lua-5.3', 'lua-5.2', 'lua-5.1', 'lua']
lua_dep = dependency(name, version: '>=5.1', required: false)
if lua_dep.found()
break
endif
endforeach
if not lua_dep.found()
error('Lua could not be found!')
endif
tio_dep = [
dependency('threads', required: true),
dependency('glib-2.0', required: true),
lua_dep
]
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']
tio_c_args = ['-Wno-unused-result']
if enable_setspeed2
tio_sources += 'setspeed2.c'
tio_c_args += '-DHAVE_TERMIOS2'
endif
if enable_iossiospeed
tio_sources += 'iossiospeed.c'
tio_c_args += '-DHAVE_IOSSIOSPEED'
endif
if enable_rs485
tio_c_args += '-DHAVE_RS485'
endif
executable('tio',
tio_sources,
c_args: tio_c_args,

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -19,235 +19,92 @@
* 02110-1301, USA.
*/
#define _GNU_SOURCE // For FNM_EXTMATCH
#include <unistd.h>
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/wait.h>
#include <fnmatch.h>
#include <regex.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include "error.h"
#include "print.h"
#include "options.h"
#define TIME_STRING_SIZE_MAX 24
char *current_time(void)
{
static char time_string[TIME_STRING_SIZE_MAX];
static struct timeval tv_start;
static bool first = true;
struct tm *tm;
struct timeval tv;
size_t len;
gettimeofday(&tv, NULL);
if (first)
{
tv_start = tv;
first = false;
}
// Add formatted timestap
switch (option.timestamp)
{
case TIMESTAMP_NONE:
case TIMESTAMP_24HOUR:
// "hh:mm:ss.sss" (24 hour format)
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%H:%M:%S", tm);
break;
case TIMESTAMP_24HOUR_START:
// "hh:mm:ss.sss" (24 hour format relative to start time)
timersub(&tv, &tv_start, &tv);
tv.tv_sec -= 3600; // Why is this needed??
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%H:%M:%S", tm);
break;
case TIMESTAMP_ISO8601:
// "YYYY-MM-DDThh:mm:ss.sss" (ISO-8601)
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%Y-%m-%dT%H:%M:%S", tm);
break;
default:
return NULL;
}
// Append milliseconds to all timestamps
if (len)
{
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%03ld", (long)tv.tv_usec / 1000);
}
return (len < TIME_STRING_SIZE_MAX) ? time_string : NULL;
}
void delay(long ms)
{
struct timespec ts;
if (ms <= 0)
{
return;
}
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
}
int ctrl_key_code(unsigned char key)
long string_to_long(char *string)
{
if ((key >= 'a') && (key <= 'z'))
long result;
char *end_token;
errno = 0;
result = strtol(string, &end_token, 10);
if ((errno != 0) || (*end_token != 0))
{
return key & ~0x60;
}
return -1;
}
bool regex_match(const char *string, const char *pattern)
{
regex_t regex;
int status;
if (regcomp(&regex, pattern, REG_EXTENDED | REG_NOSUB) != 0)
{
// No match
return false;
}
status = regexec(&regex, string, (size_t) 0, NULL, 0);
regfree(&regex);
if (status != 0)
{
// No match
return false;
}
// Match
return true;
}
int read_poll(int fd, void *data, size_t len, int timeout)
{
struct pollfd fds;
int ret = 0;
fds.events = POLLIN;
fds.fd = fd;
/* Wait data available */
ret = poll(&fds, 1, timeout);
if (ret < 0)
{
tio_error_print("%s", strerror(errno));
return ret;
}
else if (ret > 0)
{
if (fds.revents & POLLIN)
{
// Read ready data
return read(fd, data, len);
}
}
/* Timeout */
return ret;
}
// Function to calculate djb2 hash of string
unsigned long djb2_hash(const unsigned char *str)
{
unsigned long hash = 5381;
int c;
while ((c = *str++))
{
hash = ((hash << 5) + hash) + c; // hash * 33 + c
}
return hash;
}
// Function to encode a number to base62
void *base62_encode(unsigned long num, char *output)
{
const char base62_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
if (output == NULL)
{
tio_error_print("Memory allocation failed");
printf("Error: Invalid digit\n");
exit(EXIT_FAILURE);
}
for (int i = 0; i < 4; ++i)
{
output[i] = base62_chars[num % 62];
num /= 62;
}
output[4] = '\0';
return output;
}
// Function to return current time
double get_current_time(void)
{
struct timespec current_time_ts;
if (clock_gettime(CLOCK_REALTIME, &current_time_ts) == -1)
{
// Error
return -1;
}
return current_time_ts.tv_sec + current_time_ts.tv_nsec / 1e9;
}
bool match_patterns(const char *string, const char *patterns)
{
char *pattern;
char *patterns_copy;
if ((string == NULL) || (patterns == NULL))
{
return false;
}
patterns_copy = strdup(patterns);
// Tokenize the patterns string using strtok
pattern = strtok(patterns_copy, ",");
while (pattern != NULL)
{
// Check if the string matches the current pattern
#ifdef FNM_EXTMATCH
if (fnmatch(pattern, string, FNM_EXTMATCH) == 0)
#else
if (fnmatch(pattern, string, 0) == 0)
#endif
{
free(patterns_copy);
return true;
}
// Move to the next pattern
pattern = strtok(NULL, ",");
}
free(patterns_copy);
return false;
}
// Function that forks subprocess, redirects its stdin and stdout to the
// specified filedescriptor, and runs command.
int execute_shell_command(int fd, const char *command)
{
pid_t pid;
int status;
// Fork a child process
pid = fork();
if (pid == -1)
{
// Error occurred
tio_error_print("fork() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
// Child process
tio_printf("Executing shell command '%s'", command);
// Redirect stdout and stderr to the file descriptor
if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1)
{
tio_error_print("dup2() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
// Execute the shell command
execl("/bin/sh", "sh", "-c", command, (char *)NULL);
// If execlp() returns, it means an error occurred
perror("execlp");
tio_error_print("execlp() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
else
{
// Parent process
// Wait for the child process to finish
waitpid(pid, &status, 0);
if (WIFEXITED(status))
{
tio_printf("Command exited with status %d", WEXITSTATUS(status));
return WEXITSTATUS(status);
}
else
{
tio_error_printf("Child process exited abnormally\n");
return -1;
}
}
return 0;
}
void clear_line()
{
print("\r\033[K");
return result;
}

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -20,19 +20,8 @@
*/
#pragma once
#include <stdbool.h>
#include <stdio.h>
#define UNUSED(expr) do { (void)(expr); } while (0)
char * current_time(void);
void delay(long ms);
int ctrl_key_code(unsigned char key);
bool regex_match(const char *string, const char *pattern);
unsigned long djb2_hash(const unsigned char *str);
void *base62_encode(unsigned long num, char *output);
int read_poll(int fd, void *data, size_t len, int timeout);
double get_current_time(void);
bool match_patterns(const char *string, const char *patterns);
int execute_shell_command(int fd, const char *command);
void clear_line();
long string_to_long(char *string);

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -21,115 +21,42 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "script.h"
#include "timestamp.h"
#include "alert.h"
#include "tty.h"
#include <limits.h>
#include <termios.h>
#include <sys/param.h>
typedef enum
enum timestamp_t
{
INPUT_MODE_NORMAL,
INPUT_MODE_HEX,
INPUT_MODE_LINE,
INPUT_MODE_END,
} input_mode_t;
typedef enum
{
OUTPUT_MODE_NORMAL,
OUTPUT_MODE_HEX,
OUTPUT_MODE_END,
} output_mode_t;
TIMESTAMP_NONE,
TIMESTAMP_24HOUR,
TIMESTAMP_24HOUR_START,
TIMESTAMP_ISO8601,
};
const char* timestamp_token(enum timestamp_t timestamp);
enum timestamp_t timestamp_option_parse(const char *arg);
/* Options */
struct option_t
{
char *target;
int baudrate;
const char *tty_device;
unsigned int baudrate;
int databits;
flow_t flow;
char *flow;
int stopbits;
parity_t parity;
char *parity;
int output_delay;
int output_line_delay;
int dtr_pulse_duration;
int rts_pulse_duration;
int cts_pulse_duration;
int dsr_pulse_duration;
int dcd_pulse_duration;
int ri_pulse_duration;
bool no_reconnect;
auto_connect_t auto_connect;
bool no_autoconnect;
bool log;
bool log_append;
bool log_strip;
bool local_echo;
timestamp_t timestamp;
char *log_filename;
char *log_directory;
char *socket;
enum timestamp_t timestamp;
bool list_devices;
const char *log_filename;
const char *map;
int color;
input_mode_t input_mode;
output_mode_t output_mode;
char prefix_code;
char prefix_key;
bool prefix_enabled;
bool mute;
bool rs485;
uint32_t rs485_config_flags;
int32_t rs485_delay_rts_before_send;
int32_t rs485_delay_rts_after_send;
alert_t alert;
bool complete_profiles;
char *script;
char *script_filename;
script_run_t script_run;
int timestamp_timeout;
char *exclude_devices;
char *exclude_drivers;
char *exclude_tids;
int hex_n_value;
bool vt100;
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;
void options_print();
void options_parse(int argc, char *argv[]);
void options_parse_final(int argc, char *argv[]);
int option_string_to_integer(const char *string, int *value, const char *desc, int min, int max);
void option_parse_flow(const char *arg, flow_t *flow);
void option_parse_parity(const char *arg, parity_t *parity);
void option_parse_output_mode(const char *arg, output_mode_t *mode);
void option_parse_input_mode(const char *arg, input_mode_t *mode);
void option_parse_line_pulse_duration(const char *arg);
void option_parse_script_run(const char *arg, script_run_t *script_run);
void option_parse_alert(const char *arg, alert_t *alert);
void option_parse_auto_connect(const char *arg, auto_connect_t *auto_connect);
const char *option_auto_connect_state_to_string(auto_connect_t strategy);
void option_parse_timestamp(const char *arg, timestamp_t *timestamp);
const char* option_timestamp_format_to_string(timestamp_t timestamp);
void option_parse_mappings(const char *map);

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -19,6 +19,9 @@
* 02110-1301, USA.
*/
#include <stdio.h>
#include <stdbool.h>
#include "options.h"
#include "print.h"
bool print_tainted = false;
@ -26,87 +29,34 @@ char ansi_format[30];
void print_hex(char c)
{
print_tainted = true;
if ((c == '\n') || (c == '\r'))
{
printf("%c", c);
}
else
{
printf("%02x ", (unsigned char) c);
}
fflush(stdout);
}
void print_normal(char c)
{
print_tainted = true;
putchar(c);
fflush(stdout);
}
void print_init_ansi_formatting()
void print_enable_ansi_formatting()
{
if (option.color == 256)
if (option.color < 0)
{
// Set bold text with no color changes
// Enable bold text
sprintf(ansi_format, "\e[1m");
}
else
{
// Set bold text with user defined ANSI color
// Enable bold text with user defined ANSI color
sprintf(ansi_format, "\e[1;38;5;%dm", option.color);
}
}
void tio_printf_array(const char *array)
{
int i = 0, j = 0;
tio_printf("");
while (array[i])
{
if (array[i] == '\n')
{
const char *line = &array[j];
char *line_copy = strndup(line, i-j);
tio_printf_raw("%s\r", line_copy);
free(line_copy);
j = i;
}
i++;
}
tio_printf("");
}
void print_tainted_set()
{
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);
}
}

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -21,11 +21,9 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include "misc.h"
#include "error.h"
#include "options.h"
#include "timestamp.h"
extern bool print_tainted;
extern char ansi_format[];
@ -34,110 +32,46 @@ extern char ansi_format[];
#define ansi_printf(format, args...) \
{ \
if (!option.mute) \
{ \
if (option.color < 0) \
fprintf (stdout, "\r" format "\r\n", ## args); \
else \
fprintf (stdout, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \
} \
}
#define ansi_error_printf(format, args...) \
{ \
if (!option.mute) \
{ \
if (option.color < 0) \
fprintf (stderr, "\r" format "\r\n", ## args); \
else \
fprintf (stderr, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \
fflush(stderr); \
} \
fflush(stdout); \
}
#define ansi_printf_raw(format, args...) \
{ \
if (!option.mute) \
{ \
if (option.color < 0) \
fprintf (stdout, format, ## args); \
else \
fprintf (stdout, "%s" format ANSI_RESET, ansi_format, ## args); \
} \
fflush(stdout); \
}
#define tio_warning_printf(format, args...) \
#define warning_printf(format, args...) \
{ \
if (!option.mute) \
{ \
if (print_tainted) \
putchar('\n'); \
if (option.color < 0) \
fprintf (stdout, "\r[%s] Warning: " format "\r\n", timestamp_current_time(), ## args); \
else \
ansi_printf("[%s] Warning: " format, timestamp_current_time(), ## args); \
print_tainted = false; \
} \
}
#define tio_error_print(format, args...) \
{ \
if (!option.mute) \
{ \
if (print_tainted) \
putchar('\n'); \
if (option.color < 0) { \
if (error_normal) \
fprintf (stderr, "Error: " format "\n", ## args); \
else \
fprintf (stderr, "\r[%s] Error: " format "\r\n", timestamp_current_time(), ## args); \
} \
else { \
if (error_normal) \
{ ansi_error_printf("Error: " format, ## args); }\
else \
{ ansi_error_printf("[%s] Error: " format, timestamp_current_time(), ## args); }\
} \
print_tainted = false; \
} \
ansi_printf("[%s] Warning: " format, current_time(), ## args); \
fflush(stdout); \
}
#define tio_printf(format, args...) \
{ \
if (!option.mute) \
{ \
if (print_tainted) \
putchar('\n'); \
ansi_printf("[%s] " format, timestamp_current_time(), ## args); \
ansi_printf("[%s] " format, current_time(), ## args); \
print_tainted = false; \
} \
}
#define tio_printf_raw(format, args...) \
{ \
if (!option.mute) \
{ \
if (print_tainted) \
putchar('\n'); \
ansi_printf_raw("[%s] " format, timestamp_current_time(), ## args); \
print_tainted = false; \
} \
}
#define error_printf(format, args...) \
snprintf(error[0], 1000, format, ## args);
#define error_printf_silent(format, args...) \
snprintf(error[1], 1000, format, ## args);
#ifdef DEBUG
#define tio_debug_printf(format, args...) \
fprintf(stdout, "[debug] " format, ## args)
#define tio_debug_printf_raw(format, args...) \
fprintf(stdout, "" format, ## args)
#define debug_printf(format, args...) \
fprintf (stdout, "[debug] " format, ## args)
#define debug_printf_raw(format, args...) \
fprintf (stdout, "" format, ## args)
#else
#define tio_debug_printf(format, args...)
#define tio_debug_printf_raw(format, args...)
#define debug_printf(format, args...)
#define debug_printf_raw(format, args...)
#endif
void print(const char *format, ...);
void print_hex(char c);
void print_normal(char c);
void print_init_ansi_formatting(void);
void tio_printf_array(const char *array);
void print_tainted_set(void);
void print_padded(char *string, size_t length, char pad_char);
void print_enable_ansi_formatting(void);

View file

@ -1,276 +0,0 @@
/*
* 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;
}
}

View file

@ -1,26 +0,0 @@
/*
* 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);

View file

@ -1,210 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2022 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 <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include "options.h"
#include "print.h"
#include "misc.h"
#ifdef HAVE_RS485
#include <linux/serial.h>
static struct serial_rs485 rs485_config_saved;
static struct serial_rs485 rs485_config;
static bool rs485_config_written = false;
void rs485_parse_config(const char *arg)
{
bool token_found = true;
char *token = NULL;
char *buffer = strdup(arg);
while (token_found == true)
{
if (token == NULL)
{
token = strtok(buffer,",");
}
else
{
token = strtok(NULL, ",");
}
if (token != NULL)
{
char keyname[31];
unsigned int value;
int match_count;
match_count = sscanf(token, "%30[^=]=%d", keyname, &value);
if (match_count == 2)
{
if (!strcmp(keyname, "RTS_ON_SEND"))
{
if (value)
{
/* Set logical level for RTS pin equal to 1 when sending */
option.rs485_config_flags |= SER_RS485_RTS_ON_SEND;
}
else
{
/* Set logical level for RTS pin equal to 0 when sending */
option.rs485_config_flags &= ~(SER_RS485_RTS_ON_SEND);
}
}
else if (!strcmp(keyname, "RTS_AFTER_SEND"))
{
if (value)
{
/* Set logical level for RTS pin equal to 1 after sending */
option.rs485_config_flags |= SER_RS485_RTS_AFTER_SEND;
}
else
{
/* Set logical level for RTS pin equal to 0 after sending */
option.rs485_config_flags &= ~(SER_RS485_RTS_AFTER_SEND);
}
}
else if (!strcmp(keyname, "RTS_DELAY_BEFORE_SEND"))
{
/* Set RTS delay before send */
option.rs485_delay_rts_before_send = value;
}
else if (!strcmp(keyname, "RTS_DELAY_AFTER_SEND"))
{
/* Set RTS delay after send */
option.rs485_delay_rts_after_send = value;
}
}
else if (match_count == 1)
{
if (!strcmp(keyname, "RX_DURING_TX"))
{
/* Receive data even while sending data */
option.rs485_config_flags |= SER_RS485_RX_DURING_TX;
}
}
else
{
token_found = false;
}
}
else
{
token_found = false;
}
}
free(buffer);
}
void rs485_print_config(void)
{
tio_printf(" RS-485 Configuration:");
tio_printf(" RTS_ON_SEND: %s", (rs485_config.flags & SER_RS485_RTS_ON_SEND) ? "high" : "low");
tio_printf(" RTS_AFTER_SEND: %s", (rs485_config.flags & SER_RS485_RTS_AFTER_SEND) ? "high" : "low");
tio_printf(" RTS_DELAY_BEFORE_SEND = %d", rs485_config.delay_rts_before_send);
tio_printf(" RTS_DELAY_AFTER_SEND = %d", rs485_config.delay_rts_after_send);
tio_printf(" RX_DURING_TX: %s", (rs485_config.flags & SER_RS485_RX_DURING_TX) ? "true" : "false");
}
int rs485_mode_enable(int fd)
{
/* Save existing RS-485 configuration */
ioctl (fd, TIOCGRS485, &rs485_config_saved);
/* Prepare new RS-485 configuration */
rs485_config.flags = SER_RS485_ENABLED;
rs485_config.flags |= option.rs485_config_flags;
if (option.rs485_delay_rts_before_send > 0)
{
rs485_config.delay_rts_before_send = option.rs485_delay_rts_before_send;
}
else
{
rs485_config.delay_rts_before_send = rs485_config_saved.delay_rts_before_send;
}
if (option.rs485_delay_rts_after_send > 0)
{
rs485_config.delay_rts_after_send = option.rs485_delay_rts_after_send;
}
else
{
rs485_config.delay_rts_after_send = rs485_config_saved.delay_rts_after_send;
}
/* Write new RS-485 configuration */
if (ioctl(fd, TIOCSRS485, &rs485_config) < 0)
{
tio_warning_printf("RS-485 mode is not supported by your device (%s)", strerror(errno));
return -1;
}
rs485_config_written = true;
return 0;
}
void rs485_mode_restore(int fd)
{
if (rs485_config_written)
{
/* Write saved RS-485 configuration */
if (ioctl(fd, TIOCSRS485, &rs485_config_saved) < 0)
{
tio_warning_printf("TIOCGRS485 ioctl failed (%s)", strerror(errno));
}
}
}
#else
void rs485_parse_config(const char *arg)
{
UNUSED(arg);
return;
}
void rs485_print_config(void)
{
return;
}
int rs485_mode_enable(int fd)
{
UNUSED(fd);
tio_error_printf("RS485 mode is not supported on your system");
exit(EXIT_FAILURE);
}
void rs485_mode_restore(int fd)
{
UNUSED(fd);
return;
}
#endif

View file

@ -1,27 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2022 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 rs485_parse_config(const char *arg);
int rs485_mode_enable(int fd);
void rs485_mode_restore(int fd);
void rs485_print_config(void);

View file

@ -1,547 +0,0 @@
/*
* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <lauxlib.h>
#include <lualib.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include "misc.h"
#include "print.h"
#include "options.h"
#include "tty.h"
#include "xymodem.h"
#include "log.h"
#include "script.h"
#include "fs.h"
#include "timestamp.h"
#include "termios.h"
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
#define READ_LINE_SIZE 4096 // read_line buffer length
static int device_fd;
static char script_init[] =
"tio.set = function(arg)\n"
" local dtr = arg.DTR or -1\n"
" local rts = arg.RTS or -1\n"
" local cts = arg.CTS or -1\n"
" local dsr = arg.DSR or -1\n"
" local cd = arg.CD or -1\n"
" local ri = arg.RI or -1\n"
" tio.line_set(dtr, rts, cts, dsr, cd, ri)\n"
"end\n"
"tio.expect = function(pattern, timeout)\n"
" local str = ''\n"
" while true do\n"
" local c = tio.read(1, timeout)\n"
" if c then\n"
" str = str .. c\n"
" if string.match(str, pattern) then\n"
" return string.match(str, pattern)\n"
" end\n"
" else\n"
" return nil, str\n"
" end\n"
" end\n"
"end\n"
"tio.alwaysecho = true\n"
"setmetatable(tio, tio)\n";
static bool alwaysecho(lua_State *L)
{
bool b;
lua_getglobal(L, "tio");
lua_getfield(L, -1, "alwaysecho");
b = lua_toboolean(L, -1);
lua_pop(L, 2);
return b;
}
static int api_echo(lua_State *L)
{
size_t len = 0;
const char *str = luaL_checklstring(L, 1, &len);
if (option.timestamp)
{
char *pTimeStampNow = timestamp_current_time();
if (pTimeStampNow)
{
tio_printf("%s", str);
if (option.log)
{
log_printf("\n[%s] %s", pTimeStampNow, str);
}
}
} else {
for (size_t i=0; i<len; i++)
{
putchar(str[i]);
if (option.log)
log_putc(str[i]);
}
}
return 0;
}
static void maybe_echo(lua_State *L)
{
if (alwaysecho(L))
{
lua_pushcfunction(L, api_echo);
lua_pushvalue(L, -2);
lua_call(L, 1, 0);
}
}
// lua: tio.sleep(seconds)
static int api_sleep(lua_State *L)
{
long seconds = lua_tointeger(L, 1);
if (seconds < 0)
{
return 0;
}
tio_printf("Sleeping %ld seconds", seconds);
sleep(seconds);
return 0;
}
// lua: tio.msleep(miliseconds)
static int api_msleep(lua_State *L)
{
long mseconds = lua_tointeger(L, 1);
long useconds = mseconds * 1000;
if (useconds < 0)
{
return 0;
}
tio_printf("Sleeping %ld ms", mseconds);
usleep(useconds);
return 0;
}
// lua: tio.line_set(dtr,rts,cts,dsr,cd,ri)
static int line_set(lua_State *L)
{
tty_line_config_t line_config[6] = { };
int dtr = lua_tointeger(L, 1);
int rts = lua_tointeger(L, 2);
int cts = lua_tointeger(L, 3);
int dsr = lua_tointeger(L, 4);
int cd = lua_tointeger(L, 5);
int ri = lua_tointeger(L, 6);
if (dtr != -1)
{
line_config[0].mask = TIOCM_DTR;
line_config[0].value = dtr;
line_config[0].reserved = true;
}
if (rts != -1)
{
line_config[1].mask = TIOCM_RTS;
line_config[1].value = rts;
line_config[1].reserved = true;
}
if (cts != -1)
{
line_config[2].mask = TIOCM_CTS;
line_config[2].value = cts;
line_config[2].reserved = true;
}
if (dsr != -1)
{
line_config[3].mask = TIOCM_DSR;
line_config[3].value = dsr;
line_config[3].reserved = true;
}
if (cd != -1)
{
line_config[4].mask = TIOCM_CD;
line_config[4].value = cd;
line_config[4].reserved = true;
}
if (ri != -1)
{
line_config[5].mask = TIOCM_RI;
line_config[5].value = ri;
line_config[5].reserved = true;
}
tty_line_set(device_fd, line_config);
return 0;
}
// lua: tio.send(file, protocol)
static int api_send(lua_State *L)
{
const char *file = luaL_checkstring(L, 1);
int protocol = luaL_checkinteger(L, 2);
int ret;
if (file == NULL)
{
return 0;
}
switch (protocol)
{
case XMODEM_1K:
tio_printf("Sending file '%s' using XMODEM-1K", file);
ret = xymodem_send(device_fd, file, XMODEM_1K);
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
break;
case XMODEM_CRC:
tio_printf("Sending file '%s' using XMODEM-CRC", file);
ret = xymodem_send(device_fd, file, XMODEM_CRC);
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
break;
case YMODEM:
tio_printf("Sending file '%s' using YMODEM", file);
ret = xymodem_send(device_fd, file, YMODEM);
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
break;
}
return 0;
}
// lua: tio.write(string)
static int api_write(lua_State *L)
{
size_t len = 0;
const char *string = luaL_checklstring(L, 1, &len);
ssize_t ret;
int attempts = 100;
do {
ret = write(device_fd, string, len);
if (ret < 0)
return luaL_error(L, "%s", strerror(errno));
len -= ret;
string += ret;
} while (len > 0 && --attempts);
if (len > 0)
return luaL_error(L, "partial write");
fsync(device_fd); // flush these characters now
tcdrain(device_fd); //ensure we flushed characters to our device
lua_getglobal(L, "tio");
return 1;
}
// lua: tio.read(size, timeout)
static int api_read(lua_State *L)
{
int size = luaL_checkinteger(L, 1);
int timeout = lua_tointeger(L, 2);
if (timeout == 0)
{
timeout = -1; // Wait forever
}
luaL_Buffer buffer;
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)
{
// On timeout return nil instead of an empty string
lua_pop(L, 1);
lua_pushnil(L);
}
else
{
maybe_echo(L);
}
return 1;
}
// lua: string = tio.readline(timeout)
static int api_readline(lua_State *L) {
int timeout = lua_tointeger(L, 1); //ms
luaL_Buffer b;
char ch;
if (timeout == 0)
{
timeout = -1; // Wait forever
}
luaL_buffinit(L, &b);
luaL_prepbuffer(&b);
while (true) {
int ret = read_poll(device_fd, &ch, 1, timeout);
if (ret < 0)
return luaL_error(L, "%s", strerror(errno));
if (ret == 0)
{
luaL_pushresult(&b);
maybe_echo(L);
lua_pushnil(L);
lua_insert(L, -2);
return 2;
}
if (ch == '\n')
{
luaL_pushresult(&b);
maybe_echo(L);
return 1;
}
luaL_addchar(&b, ch);
}
}
// lua: table = tio.ttysearch()
static int api_ttysearch(lua_State *L)
{
UNUSED(L);
GList *iter;
int i = 1;
GList *device_list = tty_search_for_serial_devices();
if (device_list == NULL)
{
return 0;
}
// Create a new table
lua_newtable(L);
// Iterate through found devices
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
{
device_t *device = (device_t *) iter->data;
// Create a new sub-table for each serial device
lua_newtable(L);
// Add elements to the table
lua_pushstring(L, "path");
lua_pushstring(L, device->path);
lua_settable(L, -3);
lua_pushstring(L, "tid");
lua_pushstring(L, device->tid);
lua_settable(L, -3);
lua_pushstring(L, "uptime");
lua_pushnumber(L, device->uptime);
lua_settable(L, -3);
lua_pushstring(L, "driver");
lua_pushstring(L, device->driver);
lua_settable(L, -3);
lua_pushstring(L, "description");
lua_pushstring(L, device->description);
lua_settable(L, -3);
// Set the sub-table as a row in the main table
lua_rawseti(L, -2, i++);
}
// Return table
return 1;
}
static void script_buffer_run(lua_State *L, const char *script_buffer)
{
int error;
error = luaL_loadbuffer(L, script_buffer, strlen(script_buffer), "tio") ||
lua_pcall(L, 0, 0, 0);
if (error)
{
tio_warning_printf("lua: %s\n", lua_tostring(L, -1));
lua_pop(L, 1); /* Pop error message from the stack */
}
}
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[] =
{
{ "echo", api_echo},
{ "sleep", api_sleep},
{ "msleep", api_msleep},
{ "line_set", line_set},
{ "send", api_send},
{ "write", api_write},
{ "read", api_read},
{ "readline", api_readline},
{ "ttysearch", api_ttysearch},
{NULL, NULL}
};
static void script_load(lua_State *L)
{
int error;
error = luaL_loadbuffer(L, script_init, strlen(script_init), "tio") || lua_pcall(L, 0, 0, 0);
if (error)
{
tio_error_print("%s\n", lua_tostring(L, -1));
lua_pop(L, 1); // Pop error message from the stack
}
}
static void script_set_global(lua_State *L, const char *name, long value)
{
lua_pushnumber(L, value);
lua_setglobal(L, name);
}
static void script_set_globals(lua_State *L)
{
script_set_global(L, "toggle", 2);
script_set_global(L, "high", 1);
script_set_global(L, "low", 0);
script_set_global(L, "XMODEM_CRC", XMODEM_CRC);
script_set_global(L, "XMODEM_1K", XMODEM_1K);
script_set_global(L, "YMODEM", YMODEM);
}
#if LUA_VERSION_NUM >= 502
static int luaopen_tio(lua_State *L)
{
luaL_newlib(L, tio_lib);
return 1;
}
#endif
void script_run(int fd, const char *script_filename)
{
lua_State *L;
device_fd = fd;
L = luaL_newstate();
luaL_openlibs(L);
#if LUA_VERSION_NUM >= 502
luaL_requiref(L, "tio", luaopen_tio, 1);
#else
luaL_register(L, "tio", tio_lib);
#endif
lua_pop(L, 1);
// Load lua init script
script_load(L);
// Initialize globals
script_set_globals(L);
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);
script_file_run(L, option.script_filename);
}
else if (option.script != NULL)
{
tio_printf("Running script");
script_buffer_run(L, option.script);
}
lua_close(L);
}
const char *script_run_state_to_string(script_run_t state)
{
switch (state)
{
case SCRIPT_RUN_ONCE:
return "once";
case SCRIPT_RUN_ALWAYS:
return "always";
case SCRIPT_RUN_NEVER:
return "never";
default:
return "Unknown";
}
}

View file

@ -1,33 +0,0 @@
/*
* 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
typedef enum
{
SCRIPT_RUN_ONCE,
SCRIPT_RUN_ALWAYS,
SCRIPT_RUN_NEVER,
SCRIPT_RUN_END,
} script_run_t;
void script_run(int fd, const char *script_filename);
const char *script_run_state_to_string(script_run_t state);

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2017-2022 Martin Lund
*
@ -19,24 +19,13 @@
* 02110-1301, USA.
*/
#include <errno.h>
#ifdef HAVE_TERMIOS2
#define termios asmtermios
#include <sys/ioctl.h>
#undef termios
#include <asm-generic/ioctls.h>
#include <asm-generic/termbits.h>
#elif HAVE_IOSSIOSPEED
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
#endif
#include "misc.h"
#ifdef HAVE_TERMIOS2
int setspeed(int fd, int baudrate)
int setspeed2(int fd, int baudrate)
{
struct termios2 tio;
int status;
@ -53,20 +42,3 @@ int setspeed(int fd, int baudrate)
return status;
}
#elif HAVE_IOSSIOSPEED
int setspeed(int fd, int baudrate)
{
return ioctl(fd, IOSSIOSPEED, (char *)&baudrate);
}
#else
int setspeed(int fd, int baudrate)
{
UNUSED(fd);
UNUSED(baudrate);
errno = EINVAL;
return -1;
}
#endif

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2022 Martin Lund
*
@ -27,25 +27,15 @@
#include "error.h"
#include "print.h"
#include "misc.h"
#include "tty.h"
static void signal_handler(int signum)
{
switch (signum)
{
case SIGHUP:
tio_printf("Received SIGHUP signal!");
break;
case SIGINT:
tio_printf("Received SIGINT signal!");
break;
}
UNUSED(signum);
tio_printf("Received hangup signal!");
exit(EXIT_FAILURE);
}
void signal_handlers_install(void)
{
signal(SIGHUP, signal_handler);
signal(SIGINT, signal_handler);
signal(SIGPIPE, SIG_IGN);
}

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2022 Martin Lund
*

View file

@ -1,384 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
* Copyright (c) 2022 Google LLC
*
* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include "socket.h"
#include "options.h"
#include "print.h"
#include "tty.h"
#define MAX_SOCKET_CLIENTS 16
#define SOCKET_PORT_DEFAULT 3333
static int sockfd;
static int clientfds[MAX_SOCKET_CLIENTS];
static int socket_family = AF_UNSPEC;
static int port_number = SOCKET_PORT_DEFAULT;
static const char *socket_filename(void)
{
/* skip 'unix:' */
return option.socket + 5;
}
static int socket_inet_port(void)
{
/* skip 'inet:' */
int port = atoi(option.socket + 5);
if (port == 0)
{
port = SOCKET_PORT_DEFAULT;
}
return port;
}
static int socket_inet6_port(void)
{
/* skip 'inet6:' */
int port = atoi(option.socket + 6);
if (port == 0)
{
port = SOCKET_PORT_DEFAULT;
}
return port;
}
static void socket_exit(void)
{
if (socket_family == AF_UNIX)
{
unlink(socket_filename());
}
}
static bool socket_stale(const char *path)
{
struct sockaddr_un addr;
bool stale = false;
int sfd;
/* Test if socket file exists */
if (access(path, F_OK) == 0)
{
/* Create test socket */
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd < 0)
{
tio_warning_printf("Failure opening socket (%s)", strerror(errno));
return false;
}
/* Prepare address */
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
/* Perform connect to test if socket is active */
if (connect(sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1)
{
if (errno == ECONNREFUSED)
{
// No one is listening on socket file
stale = true;
}
}
/* Cleanup */
close(sockfd);
}
return stale;
}
void socket_configure(void)
{
struct sockaddr_un sockaddr_unix = {};
struct sockaddr_in sockaddr_inet = {};
struct sockaddr_in6 sockaddr_inet6 = {};
struct sockaddr *sockaddr_p;
socklen_t socklen;
int optval;
/* Parse socket string */
if (strncmp(option.socket, "unix:", 5) == 0)
{
socket_family = AF_UNIX;
if (strlen(socket_filename()) == 0)
{
tio_error_printf("Missing socket filename");
exit(EXIT_FAILURE);
}
if (strlen(socket_filename()) > sizeof(sockaddr_unix.sun_path) - 1)
{
tio_error_printf("Socket file path %s too long", option.socket);
exit(EXIT_FAILURE);
}
}
if (strncmp(option.socket, "inet:", 5) == 0)
{
socket_family = AF_INET;
port_number = socket_inet_port();
if (port_number < 0)
{
tio_error_printf("Invalid port number: %d", port_number);
exit(EXIT_FAILURE);
}
}
if (strncmp(option.socket, "inet6:", 6) == 0)
{
socket_family = AF_INET6;
port_number = socket_inet6_port();
if (port_number < 0)
{
tio_error_printf("Invalid port number: %d", port_number);
exit(EXIT_FAILURE);
}
}
if (socket_family == AF_UNSPEC)
{
tio_error_printf("%s: Invalid socket scheme, must be prefixed with 'unix:', 'inet:', or 'inet6:'", option.socket);
exit(EXIT_FAILURE);
}
/* Configure socket */
switch (socket_family)
{
case AF_UNIX:
sockaddr_unix.sun_family = AF_UNIX;
strncpy(sockaddr_unix.sun_path, socket_filename(), sizeof(sockaddr_unix.sun_path) - 1);
sockaddr_p = (struct sockaddr *) &sockaddr_unix;
socklen = sizeof(sockaddr_unix);
/* Test for stale unix socket file */
if (socket_stale(socket_filename()))
{
tio_printf("Cleaning up old socket file");
unlink(socket_filename());
}
break;
case AF_INET:
sockaddr_inet.sin_family = AF_INET;
sockaddr_inet.sin_addr.s_addr = INADDR_ANY;
sockaddr_inet.sin_port = htons(port_number);
sockaddr_p = (struct sockaddr *) &sockaddr_inet;
socklen = sizeof(sockaddr_inet);
break;
case AF_INET6:
sockaddr_inet6.sin6_family = AF_INET6;
sockaddr_inet6.sin6_addr = in6addr_any;
sockaddr_inet6.sin6_port = htons(port_number);
sockaddr_p = (struct sockaddr *) &sockaddr_inet6;
socklen = sizeof(sockaddr_inet6);
break;
default:
tio_error_printf("Invalid socket family (%d)", socket_family);
exit(EXIT_FAILURE);
break;
}
/* Create socket */
sockfd = socket(socket_family, SOCK_STREAM, 0);
if (sockfd < 0)
{
tio_error_printf("Failed to create socket (%s)", strerror(errno));
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 */
if (bind(sockfd, sockaddr_p, socklen) < 0)
{
tio_error_printf("Failed to bind to socket (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
/* Listen */
if (listen(sockfd, MAX_SOCKET_CLIENTS) < 0)
{
tio_error_printf("Failed to listen on socket (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
memset(clientfds, -1, sizeof(clientfds));
atexit(socket_exit);
if (socket_family == AF_UNIX)
{
tio_printf("Listening on socket %s", socket_filename());
}
else
{
tio_printf("Listening on socket port %d", port_number);
}
}
void socket_write(char input_char)
{
if (!option.socket)
{
return;
}
for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
{
if (clientfds[i] != -1)
{
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
if (send(clientfds[i], &input_char, 1, 0) <= 0)
#else
if (send(clientfds[i], &input_char, 1, MSG_NOSIGNAL) <= 0)
#endif
{
tio_error_printf_silent("Failed to write to socket (%s)", strerror(errno));
close(clientfds[i]);
clientfds[i] = -1;
}
}
}
}
int socket_add_fds(fd_set *rdfs, bool connected)
{
if (!option.socket)
{
return 0;
}
int numclients = 0, maxfd = 0;
for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
{
if (clientfds[i] != -1)
{
/* let clients block if they try to send while we're disconnected */
if (connected)
{
FD_SET(clientfds[i], rdfs);
maxfd = MAX(maxfd, clientfds[i]);
}
numclients++;
}
}
/* don't bother to accept clients if we're already full */
if (numclients != MAX_SOCKET_CLIENTS)
{
FD_SET(sockfd, rdfs);
maxfd = MAX(maxfd, sockfd);
}
return maxfd;
}
bool socket_handle_input(fd_set *rdfs, char *output_char)
{
if (!option.socket)
{
return false;
}
if (FD_ISSET(sockfd, rdfs))
{
int clientfd = accept(sockfd, NULL, NULL);
/* this loop should always succeed because we don't select on sockfd when full */
for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
{
if (clientfds[i] == -1)
{
clientfds[i] = clientfd;
break;
}
}
}
for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
{
if (clientfds[i] != -1 && FD_ISSET(clientfds[i], rdfs))
{
int status = read(clientfds[i], output_char, 1);
if (status == 0)
{
close(clientfds[i]);
clientfds[i] = -1;
continue;
}
if (status < 0)
{
tio_error_printf_silent("Failed to read from socket (%s)", strerror(errno));
close(clientfds[i]);
clientfds[i] = -1;
continue;
}
/* If INLCR is set, a received NL character shall be translated into a CR character */
if (*output_char == '\n' && option.map_i_nl_cr)
{
*output_char = '\r';
}
else if (*output_char == '\r')
{
/* If IGNCR is set, a received CR character shall be ignored (not read). */
if (option.map_ign_cr)
{
return false;
}
/* If IGNCR is not set and ICRNL is set, a received CR character shall be translated into an NL character. */
if (option.map_i_cr_nl)
{
*output_char = '\n';
}
}
return true;
}
}
return false;
}

View file

@ -1,31 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
* Copyright (c) 2022 Google LLC
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
#include <stdbool.h>
#include <sys/select.h>
void socket_configure(void);
void socket_write(char input_char);
int socket_add_fds(fd_set *fds, bool connected);
bool socket_handle_input(fd_set *fds, char *output_char);

View file

@ -1,104 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 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 "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include "error.h"
#include "print.h"
#include "options.h"
#include "timestamp.h"
char *timestamp_current_time(void)
{
static char time_string[TIME_STRING_SIZE_MAX];
static struct timeval tv, tv_now, tv_start, tv_previous;
static bool first = true;
struct tm *tm;
size_t len;
// Get current time value
gettimeofday(&tv_now, NULL);
if (first)
{
tv_start = tv_now;
first = false;
}
// Add formatted timestamp
switch (option.timestamp)
{
case TIMESTAMP_NONE:
case TIMESTAMP_24HOUR:
// "hh:mm:ss.sss" (24 hour format)
tv = tv_now;
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%H:%M:%S", tm);
break;
case TIMESTAMP_24HOUR_START:
// "hh:mm:ss.sss" (24 hour format relative to start time)
timersub(&tv_now, &tv_start, &tv);
tm = gmtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%H:%M:%S", tm);
break;
case TIMESTAMP_24HOUR_DELTA:
// "hh:mm:ss.sss" (24 hour format relative to previous time stamp)
timersub(&tv_now, &tv_previous, &tv);
tm = gmtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%H:%M:%S", tm);
break;
case TIMESTAMP_ISO8601:
// "YYYY-MM-DDThh:mm:ss.sss" (ISO-8601)
tv = tv_now;
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%Y-%m-%dT%H:%M:%S", tm);
break;
case TIMESTAMP_EPOCH:
case TIMESTAMP_EPOCH_USEC:
// "N.sss" (seconds since Unix epoch, 1970-01-01 00:00:00Z)
tv = tv_now;
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%s", tm);
break;
default:
return NULL;
}
// Append millis-/microseconds to all timestamps
if (len)
{
if ( option.timestamp == TIMESTAMP_EPOCH_USEC )
{
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%06ld", (long)tv.tv_usec);
}
else
{
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%03ld", (long)tv.tv_usec / 1000);
}
}
// Save previous time value for next run
tv_previous = tv_now;
return (len < TIME_STRING_SIZE_MAX) ? time_string : NULL;
}

View file

@ -1,39 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 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
typedef enum
{
TIMESTAMP_NONE,
TIMESTAMP_24HOUR,
TIMESTAMP_24HOUR_START,
TIMESTAMP_24HOUR_DELTA,
TIMESTAMP_ISO8601,
TIMESTAMP_EPOCH,
TIMESTAMP_EPOCH_USEC,
TIMESTAMP_END,
} timestamp_t;
#define TIME_STRING_SIZE_MAX 24
char *timestamp_current_time(void);

2773
src/tty.c

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
/*
* tio - a serial device I/O tool
* tio - a simple serial terminal I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
@ -21,66 +21,30 @@
#pragma once
#include <stdbool.h>
#include <glib.h>
#define KEY_QUESTION 0x3f
#define KEY_B 0x62
#define KEY_C 0x63
#define KEY_E 0x65
#define KEY_H 0x68
#define KEY_L 0x6C
#define KEY_Q 0x71
#define KEY_S 0x73
#define KEY_T 0x74
#define KEY_SHIFT_T 0x54
#define KEY_CTRL_T 0x14
#define KEY_V 0x76
#define KEY_D 0x64
#define KEY_R 0x72
#define KEY_SHIFT_L 0x4C
#define LINE_HIGH true
#define LINE_LOW false
#define TOPOLOGY_ID_SIZE 4
typedef enum
{
FLOW_NONE,
FLOW_HARD,
FLOW_SOFT,
} flow_t;
typedef enum
{
PARITY_NONE,
PARITY_ODD,
PARITY_EVEN,
PARITY_MARK,
PARITY_SPACE,
} parity_t;
typedef enum
{
AUTO_CONNECT_DIRECT,
AUTO_CONNECT_NEW,
AUTO_CONNECT_LATEST,
AUTO_CONNECT_END,
} auto_connect_t;
typedef struct
{
char *tid;
double uptime;
char *path;
char *driver;
char *description;
} device_t;
typedef struct
{
int mask;
int value;
bool reserved;
} tty_line_config_t;
extern const char *device_name;
extern bool interactive_mode;
#define NORMAL 0
#define HEX 1
void stdout_configure(void);
void stdout_restore(void);
void stdin_configure(void);
void stdin_restore(void);
void tty_configure(void);
void tty_reconfigure(void);
int tty_connect(void);
void tty_wait_for_device(void);
void list_serial_devices(void);
void tty_input_thread_create(void);
void tty_input_thread_wait_ready(void);
void tty_line_set(int fd, tty_line_config_t line_config[]);
void tty_search(void);
GList *tty_search_for_serial_devices(void);

View file

@ -1,3 +0,0 @@
#pragma once
#define VERSION "@VERSION@"

View file

@ -1,739 +0,0 @@
/*
* Minimalistic implementation of the xmodem-1k and ymodem sender protocol.
* https://en.wikipedia.org/wiki/XMODEM
* https://en.wikipedia.org/wiki/YMODEM
*
* SPDX-License-Identifier: GPL-2.0-or-later OR MIT-0
*
*/
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <poll.h>
#include "xymodem.h"
#include "print.h"
#include "misc.h"
#define SOH 0x01
#define STX 0x02
#define ACK 0x06
#define NAK 0x15
#define CAN 0x18
#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 ERR (-1)
#define ERR_FATAL (-2)
#define USER_CAN (-5)
#define RX_IGNORE 5
#define min(a, b) ((a) < (b) ? (a) : (b))
struct xpacket_1k {
uint8_t type;
uint8_t seq;
uint8_t nseq;
uint8_t data[1024];
uint8_t crc_hi;
uint8_t crc_lo;
} __attribute__((packed));
struct xpacket {
uint8_t type;
uint8_t seq;
uint8_t nseq;
uint8_t data[128];
uint8_t crc_hi;
uint8_t crc_lo;
} __attribute__((packed));
/* See https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks */
static uint16_t crc16(const uint8_t *data, uint16_t size)
{
uint16_t crc, s;
for (crc = 0; size > 0; size--) {
s = *data++ ^ (crc >> 8);
s ^= (s >> 4);
crc = (crc << 8) ^ s ^ (s << 5) ^ (s << 12);
}
return crc;
}
static int xmodem_1k(int sio, const void *data, size_t len, int seq)
{
struct xpacket_1k packet;
const uint8_t *buf = data;
char resp = 0;
int rc, crc;
/* Drain pending characters from serial line. Insist on the
* last drained character being 'C'.
*/
while(1) {
if (key_hit)
return -1;
rc = read_poll(sio, &resp, 1, 50);
if (rc == 0) {
if (resp == 'C') break;
if (resp == CAN) return ERR;
continue;
}
else if (rc < 0) {
tio_error_print("Read sync from serial failed");
return ERR;
}
}
/* Always work with 1K packets */
packet.seq = seq;
packet.type = STX;
while (len) {
size_t sz, z = 0;
char *from, status;
/* Build next packet, pad with 0 to full seq */
z = min(len, sizeof(packet.data));
memcpy(packet.data, buf, z);
memset(packet.data + z, 0, sizeof(packet.data) - z);
crc = crc16(packet.data, sizeof(packet.data));
packet.crc_hi = crc >> 8;
packet.crc_lo = crc;
packet.nseq = 0xff - packet.seq;
/* Send packet */
from = (char *) &packet;
sz = sizeof(packet);
while (sz) {
if (key_hit)
return ERR;
if ((rc = write(sio, from, sz)) < 0 ) {
if (errno == EWOULDBLOCK) {
usleep(1000);
continue;
}
tio_error_print("Write packet to serial failed");
return ERR;
}
from += rc;
sz -= rc;
}
/* Clear response */
resp = 0;
/* 'lrzsz' does not ACK ymodem's fin packet */
if (seq == 0 && packet.data[0] == 0) resp = ACK;
/* Read receiver response, timeout 1 s */
for(int n=0; n < 20; n++) {
if (key_hit)
return ERR;
rc = read_poll(sio, &resp, 1, 50);
if (rc < 0) {
tio_error_print("Read ack/nak from serial failed");
return ERR;
} else if(rc > 0) {
break;
}
}
/* Update "progress bar" */
switch (resp) {
case NAK: status = 'N'; break;
case ACK: status = '.'; break;
case 'C': status = 'C'; break;
case CAN: status = '!'; return ERR;
default: status = '?';
}
write(STDOUT_FILENO, &status, 1);
/* Move to next block after ACK */
if (resp == ACK) {
packet.seq++;
len -= z;
buf += z;
}
}
/* Send EOT at 1 Hz until ACK or CAN received */
while (seq) {
if (key_hit)
return ERR;
if (write(sio, EOT_STR, 1) < 0) {
tio_error_print("Write EOT to serial failed");
return ERR;
}
write(STDOUT_FILENO, "|", 1);
/* 1s timeout */
rc = read_poll(sio, &resp, 1, 1000);
if (rc < 0) {
tio_error_print("Read from serial failed");
return ERR;
} else if(rc == 0) {
continue;
}
if (resp == ACK || resp == CAN) {
write(STDOUT_FILENO, "\r\n", 2);
return (resp == ACK) ? OK : ERR;
}
}
return 0; /* not reached */
}
static int xmodem(int sio, const void *data, size_t len)
{
struct xpacket packet;
const uint8_t *buf = data;
char resp = 0;
int rc, crc;
/* Drain pending characters from serial line. Insist on the
* last drained character being 'C'.
*/
while(1) {
if (key_hit)
return -1;
rc = read_poll(sio, &resp, 1, 50);
if (rc == 0) {
if (resp == 'C') break;
if (resp == CAN) return ERR;
continue;
}
else if (rc < 0) {
tio_error_print("Read sync from serial failed");
return ERR;
}
}
/* Always work with 128b packets */
packet.seq = 1;
packet.type = SOH;
while (len) {
size_t sz, z = 0;
char *from, status;
/* Build next packet, pad with 0 to full seq */
z = min(len, sizeof(packet.data));
memcpy(packet.data, buf, z);
memset(packet.data + z, 0, sizeof(packet.data) - z);
crc = crc16(packet.data, sizeof(packet.data));
packet.crc_hi = crc >> 8;
packet.crc_lo = crc;
packet.nseq = 0xff - packet.seq;
/* Send packet */
from = (char *) &packet;
sz = sizeof(packet);
while (sz) {
if (key_hit)
return ERR;
if ((rc = write(sio, from, sz)) < 0 ) {
if (errno == EWOULDBLOCK) {
usleep(1000);
continue;
}
tio_error_print("Write packet to serial failed");
return ERR;
}
from += rc;
sz -= rc;
}
/* Clear response */
resp = 0;
/* Read receiver response, timeout 1 s */
for(int n=0; n < 20; n++) {
if (key_hit)
return ERR;
rc = read_poll(sio, &resp, 1, 50);
if (rc < 0) {
tio_error_print("Read ack/nak from serial failed");
return ERR;
} else if(rc > 0) {
break;
}
}
/* Update "progress bar" */
switch (resp) {
case NAK: status = 'N'; break;
case ACK: status = '.'; break;
case 'C': status = 'C'; break;
case CAN: status = '!'; return ERR;
default: status = '?';
}
write(STDOUT_FILENO, &status, 1);
/* Move to next block after ACK */
if (resp == ACK) {
packet.seq++;
len -= z;
buf += z;
}
}
/* Send EOT at 1 Hz until ACK or CAN received */
while (1) {
if (key_hit)
return ERR;
if (write(sio, EOT_STR, 1) < 0) {
tio_error_print("Write EOT to serial failed");
return ERR;
}
write(STDOUT_FILENO, "|", 1);
/* 1s timeout */
rc = read_poll(sio, &resp, 1, 1000);
if (rc < 0) {
tio_error_print("Read from serial failed");
return ERR;
} else if(rc == 0) {
continue;
}
if (resp == ACK || resp == CAN) {
write(STDOUT_FILENO, "\r\n", 2);
return (resp == ACK) ? OK : ERR;
}
}
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)
{
size_t len;
int rc, fd;
struct stat stat;
const uint8_t *buf;
/* Open file, map into memory */
fd = open(filename, O_RDONLY);
if (fd < 0) {
tio_error_print("Could not open file");
return ERR;
}
fstat(fd, &stat);
len = stat.st_size;
buf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (!buf) {
close(fd);
tio_error_print("Could not mmap file");
return ERR;
}
/* Do transfer */
key_hit = 0;
if (mode == XMODEM_1K) {
rc = xmodem_1k(sio, buf, len, 1);
}
else if (mode == XMODEM_CRC) {
rc = xmodem(sio, buf, len);
}
else {
/* Ymodem: hdr + file + fin */
while(1) {
char hdr[1024], *p;
rc = -1;
if (strlen(filename) > 977) break; /* hdr block overrun */
p = stpncpy(hdr, filename, 1024) + 1;
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, buf, len, 1) < 0) break; /* xmodem file */
if (xmodem_1k(sio, "", 1, 0) < 0) break; /* empty hdr = fin */
rc = 0; break;
}
}
key_hit = 0xff;
/* Flush serial and release resources */
tcflush(sio, TCIOFLUSH);
munmap((void *)buf, len);
close(fd);
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;
}

View file

@ -1,34 +0,0 @@
/*
* 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
typedef enum {
XMODEM_1K,
XMODEM_CRC,
YMODEM,
} modem_mode_t;
extern char key_hit;
int xymodem_send(int sio, const char *filename, modem_mode_t mode);
int xymodem_receive(int sio, const char *filename, modem_mode_t mode);

4
subprojects/libinih.wrap Normal file
View file

@ -0,0 +1,4 @@
[wrap-git]
directory=libinih
url=https://github.com/benhoyt/inih.git
revision=r55