termapy 0.48.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- termapy-0.48.0/PKG-INFO +1178 -0
- termapy-0.48.0/README.md +1143 -0
- termapy-0.48.0/pyproject.toml +74 -0
- termapy-0.48.0/src/termapy/__init__.py +3 -0
- termapy-0.48.0/src/termapy/__main__.py +5 -0
- termapy-0.48.0/src/termapy/app.py +3681 -0
- termapy-0.48.0/src/termapy/builtins/__init__.py +0 -0
- termapy-0.48.0/src/termapy/builtins/crc/__init__.py +0 -0
- termapy-0.48.0/src/termapy/builtins/crc/sum16.py +16 -0
- termapy-0.48.0/src/termapy/builtins/crc/sum8.py +16 -0
- termapy-0.48.0/src/termapy/builtins/demo/.gitignore +9 -0
- termapy-0.48.0/src/termapy/builtins/demo/__init__.py +0 -0
- termapy-0.48.0/src/termapy/builtins/demo/demo.cfg +44 -0
- termapy-0.48.0/src/termapy/builtins/demo/plugin/__init__.py +0 -0
- termapy-0.48.0/src/termapy/builtins/demo/plugin/cmd.py +42 -0
- termapy-0.48.0/src/termapy/builtins/demo/plugin/probe.py +143 -0
- termapy-0.48.0/src/termapy/builtins/demo/plugin/temp_plot.py +116 -0
- termapy-0.48.0/src/termapy/builtins/demo/proto/at_test.pro +32 -0
- termapy-0.48.0/src/termapy/builtins/demo/proto/bitfield_inline.pro +55 -0
- termapy-0.48.0/src/termapy/builtins/demo/proto/modbus_inline.pro +53 -0
- termapy-0.48.0/src/termapy/builtins/demo/run/at_demo.run +57 -0
- termapy-0.48.0/src/termapy/builtins/demo/run/doc_screenshots.run +78 -0
- termapy-0.48.0/src/termapy/builtins/demo/run/expect_test.run +17 -0
- termapy-0.48.0/src/termapy/builtins/demo/run/gps_demo.run +36 -0
- termapy-0.48.0/src/termapy/builtins/demo/run/smoke_test.run +63 -0
- termapy-0.48.0/src/termapy/builtins/demo/run/status_check.run +6 -0
- termapy-0.48.0/src/termapy/builtins/demo/run/var_demo.run +58 -0
- termapy-0.48.0/src/termapy/builtins/demo/run/welcome.run +22 -0
- termapy-0.48.0/src/termapy/builtins/plugins/cap.py +389 -0
- termapy-0.48.0/src/termapy/builtins/plugins/cfg.py +482 -0
- termapy-0.48.0/src/termapy/builtins/plugins/cls.py +29 -0
- termapy-0.48.0/src/termapy/builtins/plugins/confirm.py +37 -0
- termapy-0.48.0/src/termapy/builtins/plugins/echo.py +70 -0
- termapy-0.48.0/src/termapy/builtins/plugins/edit.py +199 -0
- termapy-0.48.0/src/termapy/builtins/plugins/env_var.py +164 -0
- termapy-0.48.0/src/termapy/builtins/plugins/eol.py +48 -0
- termapy-0.48.0/src/termapy/builtins/plugins/exit.py +29 -0
- termapy-0.48.0/src/termapy/builtins/plugins/grep.py +90 -0
- termapy-0.48.0/src/termapy/builtins/plugins/help.py +430 -0
- termapy-0.48.0/src/termapy/builtins/plugins/include.py +273 -0
- termapy-0.48.0/src/termapy/builtins/plugins/os_cmd.py +63 -0
- termapy-0.48.0/src/termapy/builtins/plugins/ping.py +80 -0
- termapy-0.48.0/src/termapy/builtins/plugins/port.py +185 -0
- termapy-0.48.0/src/termapy/builtins/plugins/print.py +55 -0
- termapy-0.48.0/src/termapy/builtins/plugins/proto.py +913 -0
- termapy-0.48.0/src/termapy/builtins/plugins/run_edit.py +31 -0
- termapy-0.48.0/src/termapy/builtins/plugins/seq.py +74 -0
- termapy-0.48.0/src/termapy/builtins/plugins/show.py +82 -0
- termapy-0.48.0/src/termapy/builtins/plugins/ss.py +48 -0
- termapy-0.48.0/src/termapy/builtins/plugins/stop.py +37 -0
- termapy-0.48.0/src/termapy/builtins/plugins/var.py +379 -0
- termapy-0.48.0/src/termapy/builtins/plugins/ver.py +29 -0
- termapy-0.48.0/src/termapy/builtins/plugins/verbose.py +30 -0
- termapy-0.48.0/src/termapy/builtins/plugins/xfer.py +59 -0
- termapy-0.48.0/src/termapy/builtins/plugins/xmodem_xfer.py +227 -0
- termapy-0.48.0/src/termapy/builtins/plugins/ymodem_xfer.py +156 -0
- termapy-0.48.0/src/termapy/builtins/viz/__init__.py +0 -0
- termapy-0.48.0/src/termapy/builtins/viz/hex_view.py +61 -0
- termapy-0.48.0/src/termapy/builtins/viz/modbus_view.py.old +163 -0
- termapy-0.48.0/src/termapy/builtins/viz/text_view.py +61 -0
- termapy-0.48.0/src/termapy/capture.py +336 -0
- termapy-0.48.0/src/termapy/cli.py +865 -0
- termapy-0.48.0/src/termapy/config.py +547 -0
- termapy-0.48.0/src/termapy/crc_codegen.py +370 -0
- termapy-0.48.0/src/termapy/defaults.py +410 -0
- termapy-0.48.0/src/termapy/demo.py +1516 -0
- termapy-0.48.0/src/termapy/dialogs.py +1447 -0
- termapy-0.48.0/src/termapy/folders.py +82 -0
- termapy-0.48.0/src/termapy/help/commands.md +124 -0
- termapy-0.48.0/src/termapy/help/config.md +136 -0
- termapy-0.48.0/src/termapy/help/custom-buttons.md +50 -0
- termapy-0.48.0/src/termapy/help/data-capture.md +108 -0
- termapy-0.48.0/src/termapy/help/demo.md +103 -0
- termapy-0.48.0/src/termapy/help/device-help.md +95 -0
- termapy-0.48.0/src/termapy/help/file-transfer.md +214 -0
- termapy-0.48.0/src/termapy/help/getting-started.md +138 -0
- termapy-0.48.0/src/termapy/help/img/doc_01_main_tui.svg +202 -0
- termapy-0.48.0/src/termapy/help/img/doc_02_help.svg +205 -0
- termapy-0.48.0/src/termapy/help/img/doc_03_help_proto.svg +206 -0
- termapy-0.48.0/src/termapy/help/img/doc_04_proto_send.svg +203 -0
- termapy-0.48.0/src/termapy/help/img/doc_05_proto_crc.svg +203 -0
- termapy-0.48.0/src/termapy/help/img/doc_06_proto_delay.svg +204 -0
- termapy-0.48.0/src/termapy/help/img/doc_07_crc_python.svg +201 -0
- termapy-0.48.0/src/termapy/help/img/doc_08_variables.svg +203 -0
- termapy-0.48.0/src/termapy/help/img/doc_09_target_help.svg +205 -0
- termapy-0.48.0/src/termapy/help/img/doc_10_config_info.svg +204 -0
- termapy-0.48.0/src/termapy/help/img/new_cfg.png +0 -0
- termapy-0.48.0/src/termapy/help/index.md +22 -0
- termapy-0.48.0/src/termapy/help/installation.md +51 -0
- termapy-0.48.0/src/termapy/help/protocol-testing.md +167 -0
- termapy-0.48.0/src/termapy/help/scripting.md +68 -0
- termapy-0.48.0/src/termapy/help/serial-tools.md +141 -0
- termapy-0.48.0/src/termapy/help/toolbar.md +50 -0
- termapy-0.48.0/src/termapy/help/using-git.md +110 -0
- termapy-0.48.0/src/termapy/help/variables.md +120 -0
- termapy-0.48.0/src/termapy/help/writing-plugins.md +192 -0
- termapy-0.48.0/src/termapy/help.md +591 -0
- termapy-0.48.0/src/termapy/html/404.html +731 -0
- termapy-0.48.0/src/termapy/html/assets/images/favicon.png +0 -0
- termapy-0.48.0/src/termapy/html/assets/javascripts/LICENSE +29 -0
- termapy-0.48.0/src/termapy/html/assets/javascripts/bundle.91a19a9e.min.js +3 -0
- termapy-0.48.0/src/termapy/html/assets/javascripts/workers/search.e2d2d235.min.js +1 -0
- termapy-0.48.0/src/termapy/html/assets/stylesheets/classic/main.96fc3bb8.min.css +1 -0
- termapy-0.48.0/src/termapy/html/assets/stylesheets/classic/palette.7dc9a0ad.min.css +1 -0
- termapy-0.48.0/src/termapy/html/assets/stylesheets/modern/main.53a7feaf.min.css +1 -0
- termapy-0.48.0/src/termapy/html/assets/stylesheets/modern/palette.dfe2e883.min.css +1 -0
- termapy-0.48.0/src/termapy/html/commands.html +1339 -0
- termapy-0.48.0/src/termapy/html/config.html +1256 -0
- termapy-0.48.0/src/termapy/html/custom-buttons.html +961 -0
- termapy-0.48.0/src/termapy/html/data-capture.html +1118 -0
- termapy-0.48.0/src/termapy/html/demo.html +1281 -0
- termapy-0.48.0/src/termapy/html/device-help.html +1082 -0
- termapy-0.48.0/src/termapy/html/file-transfer.html +1514 -0
- termapy-0.48.0/src/termapy/html/getting-started.html +1145 -0
- termapy-0.48.0/src/termapy/html/img/doc_01_main_tui.svg +202 -0
- termapy-0.48.0/src/termapy/html/img/doc_02_help.svg +205 -0
- termapy-0.48.0/src/termapy/html/img/doc_03_help_proto.svg +206 -0
- termapy-0.48.0/src/termapy/html/img/doc_04_proto_send.svg +203 -0
- termapy-0.48.0/src/termapy/html/img/doc_05_proto_crc.svg +203 -0
- termapy-0.48.0/src/termapy/html/img/doc_06_proto_delay.svg +204 -0
- termapy-0.48.0/src/termapy/html/img/doc_07_crc_python.svg +201 -0
- termapy-0.48.0/src/termapy/html/img/doc_08_variables.svg +203 -0
- termapy-0.48.0/src/termapy/html/img/doc_09_target_help.svg +205 -0
- termapy-0.48.0/src/termapy/html/img/doc_10_config_info.svg +204 -0
- termapy-0.48.0/src/termapy/html/img/new_cfg.png +0 -0
- termapy-0.48.0/src/termapy/html/index.html +805 -0
- termapy-0.48.0/src/termapy/html/installation.html +995 -0
- termapy-0.48.0/src/termapy/html/objects.inv +0 -0
- termapy-0.48.0/src/termapy/html/protocol-testing.html +1235 -0
- termapy-0.48.0/src/termapy/html/scripting.html +1049 -0
- termapy-0.48.0/src/termapy/html/search.json +1 -0
- termapy-0.48.0/src/termapy/html/serial-tools.html +1136 -0
- termapy-0.48.0/src/termapy/html/sitemap.xml +3 -0
- termapy-0.48.0/src/termapy/html/toolbar.html +1047 -0
- termapy-0.48.0/src/termapy/html/using-git.html +992 -0
- termapy-0.48.0/src/termapy/html/variables.html +1214 -0
- termapy-0.48.0/src/termapy/html/writing-plugins.html +1440 -0
- termapy-0.48.0/src/termapy/migration.py +158 -0
- termapy-0.48.0/src/termapy/plugins.py +670 -0
- termapy-0.48.0/src/termapy/port_control.py +251 -0
- termapy-0.48.0/src/termapy/proto_debug.py +1161 -0
- termapy-0.48.0/src/termapy/proto_runner.py +284 -0
- termapy-0.48.0/src/termapy/protocol.py +1468 -0
- termapy-0.48.0/src/termapy/protocol_crc.py +292 -0
- termapy-0.48.0/src/termapy/protocol_viz.py +132 -0
- termapy-0.48.0/src/termapy/repl.py +926 -0
- termapy-0.48.0/src/termapy/scripting.py +203 -0
- termapy-0.48.0/src/termapy/serial_engine.py +238 -0
- termapy-0.48.0/src/termapy/serial_port.py +302 -0
- termapy-0.48.0/src/termapy/smoke.txt +41 -0
termapy-0.48.0/PKG-INFO
ADDED
|
@@ -0,0 +1,1178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: termapy
|
|
3
|
+
Version: 0.48.0
|
|
4
|
+
Summary: A TUI serial terminal with ANSI color support, built on Textual and pyserial
|
|
5
|
+
Keywords: serial,terminal,tui,textual,pyserial,embedded,xmodem,ymodem
|
|
6
|
+
Author: Chuck Bass
|
|
7
|
+
Author-email: Chuck Bass <chuck@acrocad.net>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Communications
|
|
20
|
+
Classifier: Topic :: System :: Hardware
|
|
21
|
+
Classifier: Topic :: Terminals :: Serial
|
|
22
|
+
Requires-Dist: textual[syntax]>=0.80
|
|
23
|
+
Requires-Dist: pyserial>=3.5
|
|
24
|
+
Requires-Dist: pyreadline3>=3.4 ; sys_platform == 'win32'
|
|
25
|
+
Requires-Dist: pygments>=2.20.0
|
|
26
|
+
Requires-Dist: xmodem>=0.4.7
|
|
27
|
+
Requires-Dist: ymodem>=1.5.3
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Project-URL: Homepage, https://github.com/hucker/termapy
|
|
30
|
+
Project-URL: Documentation, https://hucker.github.io/termapy/
|
|
31
|
+
Project-URL: Repository, https://github.com/hucker/termapy
|
|
32
|
+
Project-URL: Changelog, https://github.com/hucker/termapy/blob/main/CHANGELOG.md
|
|
33
|
+
Project-URL: Issues, https://github.com/hucker/termapy/issues
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# termapy
|
|
37
|
+
|
|
38
|
+
**Project Status:** [](https://github.com/hucker/termapy/actions/workflows/tests.yml) [](https://codecov.io/gh/hucker/termapy)  [](https://hucker.github.io/termapy/)
|
|
39
|
+
|
|
40
|
+
**Powered by:** [](https://textual.textualize.io/) [](https://pyserial.readthedocs.io/) [](https://github.com/hucker/zensical)
|
|
41
|
+
|
|
42
|
+
**Built with:**  [](https://docs.astral.sh/uv/) [](https://pytest.org/) [](https://coverage.readthedocs.io/)
|
|
43
|
+
|
|
44
|
+
*Pronounced "ter-map-ee"*
|
|
45
|
+
|
|
46
|
+
A serial interface terminal like PuTTY or Tera Term — but it runs in your terminal, installs in seconds, and comes with scripting, protocol testing, and a plugin system built in.
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
## Install and Connect
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
pip install termapy
|
|
54
|
+
termapy --demo
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
uv tool install termapy
|
|
61
|
+
termapy --demo
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
That starts a simulated device — no hardware needed. You're typing commands in seconds.
|
|
65
|
+
|
|
66
|
+
For a real device, just point at your config:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
termapy my_device # finds termapy_cfg/my_device/my_device.cfg
|
|
70
|
+
termapy my_device.cfg # same, explicit extension
|
|
71
|
+
termapy termapy_cfg/my_device # same, explicit path to folder
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For a plain-text terminal (no TUI), use CLI mode:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
termapy --cli my_device # interactive CLI
|
|
78
|
+
termapy --cli smoke_test.run # run a .run script and exit
|
|
79
|
+
termapy --cli my_device --run test.run # explicit config + script
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
There's a lot more — scripting, binary protocol testing, 62 CRC algorithms, custom buttons, plugins, packet visualizers — expand any section below.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
<details>
|
|
87
|
+
<summary><strong>First 60 Seconds</strong> — connect, type, change settings</summary>
|
|
88
|
+
|
|
89
|
+
1. **Connect** — click the port button in the title bar, pick your COM port, click the status button to connect (it turns green)
|
|
90
|
+
2. **Type** — enter commands in the input box at the bottom and press Enter
|
|
91
|
+
3. **Change settings** — click `Cfg` to edit port, baud rate, and other settings through the UI
|
|
92
|
+
|
|
93
|
+
Everything works through the UI — no config files to edit unless you want to.
|
|
94
|
+
|
|
95
|
+
</details>
|
|
96
|
+
|
|
97
|
+
<details>
|
|
98
|
+
|
|
99
|
+
<summary><strong>Why Not Just Use PuTTY?</strong> — what termapy adds</summary>
|
|
100
|
+
|
|
101
|
+
PuTTY works. So does minicom, screen, and CoolTerm. Use them if they do what you need. Here's where termapy goes further:
|
|
102
|
+
|
|
103
|
+
- **Runs anywhere Python does** — same tool on Windows, macOS, Linux. No GUI installer, no system dependencies.
|
|
104
|
+
- **Session logging and screenshots** — every session is logged. Ctrl+S saves an SVG screenshot you can paste into a report or email.
|
|
105
|
+
- **Scripting** — record a sequence of commands in a text file and replay it with one click. Add delays, prompts, and REPL commands.
|
|
106
|
+
- **Data capture** — capture serial text (timed) or binary data (by byte/record count) to files. Binary captures use the same format spec language as protocol testing to decode mixed-type records into CSV/TSV.
|
|
107
|
+
- **Binary protocol testing** — send raw hex, run scripted send/expect tests with pass/fail, decode Modbus and custom protocols with pluggable visualizers.
|
|
108
|
+
- **Plugin system** — add custom commands with a simple Python API. Drop a file in a folder, define a handler, done. Includes examples to get started.
|
|
109
|
+
- **Everything in one folder** — each device config gets its own subfolder with logs, screenshots, scripts, and plugins. Check it into git so the whole team has the same config.
|
|
110
|
+
|
|
111
|
+
See [COMPARISON.md](COMPARISON.md) for a detailed feature comparison against RealTerm, CoolTerm, Tera Term, Docklight, and HTerm.
|
|
112
|
+
|
|
113
|
+
</details>
|
|
114
|
+
|
|
115
|
+
<details>
|
|
116
|
+
<summary><strong>The Basics</strong> — keyboard shortcuts, title bar, REPL commands</summary>
|
|
117
|
+
|
|
118
|
+
### Keyboard Shortcuts
|
|
119
|
+
|
|
120
|
+
| Key | Action |
|
|
121
|
+
| ------- | ----------------------------------- |
|
|
122
|
+
| Ctrl+Q | Quit (also closes any open dialog) |
|
|
123
|
+
| Ctrl+S | Save SVG screenshot |
|
|
124
|
+
| Ctrl+T | Save text screenshot |
|
|
125
|
+
| Ctrl+P | Command palette |
|
|
126
|
+
| Up/Down | Cycle through command history |
|
|
127
|
+
| Escape | Clear input / exit history browsing |
|
|
128
|
+
| Right | Accept type-ahead suggestion |
|
|
129
|
+
|
|
130
|
+
### Title Bar
|
|
131
|
+
|
|
132
|
+
| Button | Action |
|
|
133
|
+
| ------ | ------------------------------------------------------------------- |
|
|
134
|
+
| `?` | Open the help guide |
|
|
135
|
+
| `#` | Toggle line numbers (green when active) |
|
|
136
|
+
| `Cfg` | Open the config picker |
|
|
137
|
+
| `Run` | Open the script picker |
|
|
138
|
+
| Center | Click to edit the current config |
|
|
139
|
+
| Port | Click to select a serial port |
|
|
140
|
+
| Status | Click to connect/disconnect (red = disconnected, green = connected) |
|
|
141
|
+
|
|
142
|
+
### REPL Commands
|
|
143
|
+
|
|
144
|
+
Type `/` to access built-in commands (the prefix is configurable). Type `/help` to list them all.
|
|
145
|
+
|
|
146
|
+
The most common ones:
|
|
147
|
+
|
|
148
|
+
| Command | Description |
|
|
149
|
+
| -------------------- | ---------------------------------- |
|
|
150
|
+
| `/help [cmd]` | List commands or show help for one |
|
|
151
|
+
| `/port.list` | List available serial ports |
|
|
152
|
+
| `/port.open {name}` | Connect to a port |
|
|
153
|
+
| `/port.info` | Show port status and parameters |
|
|
154
|
+
| `/cfg [key [value]]` | Show or change in-memory config |
|
|
155
|
+
| `/ss.svg [name]` | Save SVG screenshot |
|
|
156
|
+
| `/cls` | Clear the terminal |
|
|
157
|
+
| `/run <filename>` | Run a script file |
|
|
158
|
+
| `/echo [on \| off]` | Toggle command echo |
|
|
159
|
+
| `/grep <pattern>` | Search scrollback |
|
|
160
|
+
| `/exit` | Exit termapy |
|
|
161
|
+
|
|
162
|
+
<details>
|
|
163
|
+
<summary>Full command list</summary>
|
|
164
|
+
|
|
165
|
+
| Command | Description |
|
|
166
|
+
| -------------------------------- | -------------------------------------------------------------------------------- |
|
|
167
|
+
| `/help [cmd]` | List commands or show extended help for one |
|
|
168
|
+
| `/help.dev <cmd>` | Show a command handler's Python docstring |
|
|
169
|
+
| `/port [name]` | Open a port by name, or show subcommands |
|
|
170
|
+
| `/port.list` | List available serial ports |
|
|
171
|
+
| `/port.open {name}` | Connect to the serial port (optional port override) |
|
|
172
|
+
| `/port.close` | Disconnect from the serial port |
|
|
173
|
+
| `/port.info` | Show port status, serial parameters, and hardware lines |
|
|
174
|
+
| `/port.baud_rate {value}` | Show or set baud rate (hardware only) |
|
|
175
|
+
| `/port.byte_size {value}` | Show or set data bits (hardware only) |
|
|
176
|
+
| `/port.parity {value}` | Show or set parity (hardware only) |
|
|
177
|
+
| `/port.stop_bits {value}` | Show or set stop bits (hardware only) |
|
|
178
|
+
| `/port.flow_control {m}` | Show or set flow control: none, rtscts, xonxoff, manual |
|
|
179
|
+
| `/port.dtr {0\|1}` | Show or set DTR line |
|
|
180
|
+
| `/port.rts {0\|1}` | Show or set RTS line |
|
|
181
|
+
| `/port.cts` | Show CTS state (read-only) |
|
|
182
|
+
| `/port.dsr` | Show DSR state (read-only) |
|
|
183
|
+
| `/port.ri` | Show RI state (read-only) |
|
|
184
|
+
| `/port.cd` | Show CD state (read-only) |
|
|
185
|
+
| `/port.break {ms}` | Send break signal (default 250ms) |
|
|
186
|
+
| `/cfg [key [value]]` | Show config, show a key, or change in-memory value (with confirmation) |
|
|
187
|
+
| `/cfg.auto <key> <value>` | Set an in-memory config key immediately (no confirmation) |
|
|
188
|
+
| `/cfg.configs` | List all config files |
|
|
189
|
+
| `/cfg.load <name>` | Switch to a different config by name |
|
|
190
|
+
| `/ss.svg [name]` | Save SVG screenshot |
|
|
191
|
+
| `/ss.txt [name]` | Save text screenshot |
|
|
192
|
+
| `/ss.dir` | Show the screenshot folder |
|
|
193
|
+
| `/cls` | Clear the terminal screen |
|
|
194
|
+
| `/run <filename> {-v}` | Run a script file (-v/--verbose for per-line timing); nests up to 5 levels deep |
|
|
195
|
+
| `/run.list` | List .run files in the run/ directory |
|
|
196
|
+
| `/run.load <filename>` | Run a script file (same as /run) |
|
|
197
|
+
| `/delay <duration>` | Wait for a duration (e.g. `500ms`, `1.5s`) |
|
|
198
|
+
| `/confirm {message}` | Show Yes/Cancel dialog; Cancel stops a running script (see `at_demo.run`) |
|
|
199
|
+
| `/stop` | Abort a running script |
|
|
200
|
+
| `/seq` | Show sequence counters |
|
|
201
|
+
| `/seq.reset` | Reset all sequence counters to zero |
|
|
202
|
+
| `/print <text>` | Print a message to the terminal |
|
|
203
|
+
| `/print.r <text>` | Print Rich markup text (e.g. `[bold red]Warning![/]`) |
|
|
204
|
+
| `/show <name>` | Show a file |
|
|
205
|
+
| `/show.cfg` | Show the current config file |
|
|
206
|
+
| `/echo [on \| off]` | Toggle REPL command echo |
|
|
207
|
+
| `/echo.quiet <on \| off>` | Set echo on/off silently (for scripts and on_connect_cmd) |
|
|
208
|
+
| `/edit <file>` | Edit a project file (`run/`/`proto/` path) |
|
|
209
|
+
| `/edit.cfg` | Edit the current config file |
|
|
210
|
+
| `/edit.log` | Open the session log in the system viewer |
|
|
211
|
+
| `/edit.info` | Open the info report in the system viewer |
|
|
212
|
+
| `/show_line_endings [on \| off]` | Toggle visible `\r` `\n` markers for line-ending troubleshooting |
|
|
213
|
+
| `/os <cmd>` | Run a shell command (10s timeout, requires `os_cmd_enabled`) |
|
|
214
|
+
| `/grep <pattern>` | Search scrollback for regex matches (case-insensitive, skips own output) |
|
|
215
|
+
| `/cfg.info {--display}` | Show project summary; `--display` opens full report in system viewer |
|
|
216
|
+
| `/cfg.files` | Show project directory tree |
|
|
217
|
+
| `/proto.send <hex>` | Send raw hex bytes and/or quoted text, display response as hex (see below) |
|
|
218
|
+
| `/proto.run <file>` | Run a binary protocol test script (.pro) with pass/fail |
|
|
219
|
+
| `/proto.list` | List .pro files in the proto/ directory |
|
|
220
|
+
| `/proto.load <file>` | Run a protocol test script (same as /proto.run) |
|
|
221
|
+
| `/proto.hex [on \| off]` | Toggle hex display mode for serial I/O |
|
|
222
|
+
| `/proto.crc.list {pat}` | List available CRC algorithms (optional glob filter) |
|
|
223
|
+
| `/proto.crc.help <name>` | Show CRC algorithm parameters and description |
|
|
224
|
+
| `/proto.crc.calc <n> {d}` | Compute CRC over hex bytes, text, or file; omit data to verify check string |
|
|
225
|
+
| `/proto.status` | Show current protocol mode state |
|
|
226
|
+
| `/var {name}` | List user variables, or show one by name |
|
|
227
|
+
| `/var.set <NAME> <value>` | Set a user variable |
|
|
228
|
+
| `/var.clear` | Clear all user variables |
|
|
229
|
+
| `/env.list {pattern}` | List environment variables (all, by name, or glob) |
|
|
230
|
+
| `/env.set <name> <value>` | Set a session-scoped environment variable |
|
|
231
|
+
| `/env.reload` | Re-snapshot variables from the OS environment |
|
|
232
|
+
| `/cap.text <f> ...` | Capture serial text to file for a timed duration |
|
|
233
|
+
| `/cap.bin <f> ...` | Capture raw binary bytes to a file |
|
|
234
|
+
| `/cap.struct <f> ...` | Capture binary data, decode with format spec to CSV |
|
|
235
|
+
| `/cap.hex <f> ...` | Capture hex text lines, decode with format spec to CSV |
|
|
236
|
+
| `/cap.stop` | Stop an active capture |
|
|
237
|
+
| `/raw <text>` | Send text to serial with no variable expansion or transforms |
|
|
238
|
+
| `/exit` | Exit termapy |
|
|
239
|
+
|
|
240
|
+
</details>
|
|
241
|
+
|
|
242
|
+
Screenshots and logs are saved in the config's subfolder (`termapy_cfg/<name>/`).
|
|
243
|
+
|
|
244
|
+
</details>
|
|
245
|
+
|
|
246
|
+
<details>
|
|
247
|
+
<summary><strong>Project Files</strong> — config layout, version control, env vars, examples</summary>
|
|
248
|
+
|
|
249
|
+
On first run, termapy prompts for a config name and creates one with defaults. If one config exists it loads automatically; if multiple exist, a picker appears. You can edit the config file through the UI (`Cfg` button), or change in-memory settings for the current session with `/cfg baud_rate 9600`.
|
|
250
|
+
|
|
251
|
+
Everything termapy creates — configs, scripts, test files, plugins, logs — lives in one folder. Run `termapy --demo` and you'll see this structure:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
termapy_cfg/
|
|
255
|
+
├── plugin/ # global plugins (all configs)
|
|
256
|
+
└── demo/
|
|
257
|
+
├── demo.cfg # config file
|
|
258
|
+
├── demo.log # session log
|
|
259
|
+
├── .cmd_history.txt # command history
|
|
260
|
+
├── ss/ # screenshots
|
|
261
|
+
├── run/ # script files for /run
|
|
262
|
+
│ ├── at_demo.run
|
|
263
|
+
│ ├── smoke_test.run
|
|
264
|
+
│ └── status_check.run
|
|
265
|
+
├── plugin/ # per-config plugins
|
|
266
|
+
│ └── probe.py
|
|
267
|
+
├── cap/ # data capture output files
|
|
268
|
+
└── proto/ # protocol test scripts
|
|
269
|
+
├── at_test.pro
|
|
270
|
+
├── bitfield_inline.pro
|
|
271
|
+
└── modbus_inline.pro
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Your own configs follow the same layout — create one with `Cfg` → `New` and termapy builds the folder structure automatically.
|
|
275
|
+
|
|
276
|
+
### Version Control
|
|
277
|
+
|
|
278
|
+
Because everything is in one folder, you can commit it to git alongside your firmware source. Point `--cfg-dir` at a folder in your repo:
|
|
279
|
+
|
|
280
|
+
```sh
|
|
281
|
+
termapy --cfg-dir ./termapy_cfg
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Clone on another machine, run the same command — all configs, scripts, and test files are ready to go.
|
|
285
|
+
|
|
286
|
+
Since COM port names differ between machines, use `$(env.NAME)` placeholders in your config so the same file works everywhere. Set a `COMPORT` environment variable on each machine, and reference it with a fallback:
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"port": "$(env.COMPORT|COM4)",
|
|
291
|
+
"baud_rate": 115200,
|
|
292
|
+
"auto_connect": true
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
On a machine with `COMPORT=COM7`, termapy connects to COM7. On a machine without `COMPORT` set, it falls back to COM4. The config file on disk keeps the raw `$(env.COMPORT|COM4)` template — it's expanded in memory at load time, so your checked-in config stays portable.
|
|
297
|
+
|
|
298
|
+
Environment variables work in any string config value, not just `port`:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"port": "$(env.COMPORT|COM4)",
|
|
303
|
+
"title": "$(env.DEVICE_NAME|Dev Board)",
|
|
304
|
+
"log_file": "$(env.LOG_DIR|logs)/session.log"
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
You can also manage environment variables at runtime with REPL commands:
|
|
309
|
+
|
|
310
|
+
| Command | Description |
|
|
311
|
+
| ----------------------- | -------------------------------------------------- |
|
|
312
|
+
| `/env.list {pattern}` | List variables (all, by name, or glob like `COM*`) |
|
|
313
|
+
| `/env.set <name> <val>` | Set a session-scoped variable (in-memory only) |
|
|
314
|
+
| `/env.reload` | Re-snapshot variables from the OS environment |
|
|
315
|
+
|
|
316
|
+
Variables set with `/env.set` are available immediately for `$(env.NAME)` expansion in REPL commands but do not modify the OS environment or the config file.
|
|
317
|
+
|
|
318
|
+
#### User Variables (`$(NAME)`)
|
|
319
|
+
|
|
320
|
+
User variables let you define values once and reuse them across commands and scripts. This is especially useful when a test references the same address, register, or port in multiple places — change it once at the top instead of everywhere.
|
|
321
|
+
|
|
322
|
+
Assign a variable by typing `$(name) = value` (no `/` prefix needed):
|
|
323
|
+
|
|
324
|
+
```text
|
|
325
|
+
$(slave) = 01
|
|
326
|
+
$(reg) = 0064
|
|
327
|
+
$(count) = 05
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Use variables in any command — REPL or serial:
|
|
331
|
+
|
|
332
|
+
```text
|
|
333
|
+
/proto.send $(slave) 03 00 $(reg) 00 $(count)
|
|
334
|
+
/print Reading $(count) registers from $(slave) at $(reg)
|
|
335
|
+
AT+ADDR=$(slave)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
A typical workflow is a setup script that configures a test, then a test script that uses the variables:
|
|
339
|
+
|
|
340
|
+
```text
|
|
341
|
+
# setup_modbus.run — run this first to configure the test
|
|
342
|
+
$(SLAVE) = 01
|
|
343
|
+
$(BASE_REG) = 0064
|
|
344
|
+
$(NUM_REGS) = 05
|
|
345
|
+
/print Configured: slave=$(SLAVE) base=$(BASE_REG) count=$(NUM_REGS)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
```text
|
|
349
|
+
# test_registers.run — uses variables from setup
|
|
350
|
+
/proto.send $(SLAVE) 03 00 $(BASE_REG) 00 $(NUM_REGS)
|
|
351
|
+
/delay 500ms
|
|
352
|
+
/proto.send $(SLAVE) 06 00 $(BASE_REG) 04 D2
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Run `/run setup_modbus.run` then `/run test_registers.run` — the variables persist across interactive `/run` calls.
|
|
356
|
+
|
|
357
|
+
| Command | Description |
|
|
358
|
+
| -------------------- | -------------------------------------- |
|
|
359
|
+
| `$(NAME) = value` | Set a variable (no `/` prefix needed) |
|
|
360
|
+
| `/var` | List all defined variables |
|
|
361
|
+
| `/var NAME` | Show one variable's value (or $(NAME)) |
|
|
362
|
+
| `/var.set NAME val` | Set a variable (explicit command form) |
|
|
363
|
+
| `/var.clear` | Clear all variables |
|
|
364
|
+
|
|
365
|
+
**Scope:** Variables persist for the interactive session. They are automatically cleared when a script is launched from the Scripts button or Run menu, but *not* when `/run` is typed interactively or called within a script. This lets you run a setup script to define variables, then run a test script that uses them. Use `/var.clear` to reset manually.
|
|
366
|
+
|
|
367
|
+
**Naming:** Variable names are case-sensitive (`$(PORT)` and `$(port)` are different variables). Names must start with a letter or underscore and contain only letters, digits, and underscores.
|
|
368
|
+
|
|
369
|
+
**Built-in time variables:**
|
|
370
|
+
|
|
371
|
+
| Variable | Set when | Updates? |
|
|
372
|
+
| --- | --- | --- |
|
|
373
|
+
| `$(LAUNCH_DATETIME)` | App starts | Never - frozen |
|
|
374
|
+
| `$(SESSION_DATETIME)` | Script launched (Scripts button / Run menu) | Per script launch |
|
|
375
|
+
| `$(DATETIME)` | Every expansion | Always current clock |
|
|
376
|
+
|
|
377
|
+
Each group also has `_DATE` and `_TIME` variants (e.g. `$(LAUNCH_DATE)`, `$(SESSION_TIME)`).
|
|
378
|
+
|
|
379
|
+
**vs. environment variables:** `$(env.NAME)` pulls from the OS environment and works in config files. `$(NAME)` is for user-defined session variables in commands and scripts. Both use the `$(...)` syntax — `env.` is required to access environment variables explicitly.
|
|
380
|
+
|
|
381
|
+
**Escaping:** Use `\$` to prevent expansion of a single reference, or `/raw` to skip expansion for an entire line.
|
|
382
|
+
|
|
383
|
+
Add a `.gitignore` for session files you don't need to track:
|
|
384
|
+
|
|
385
|
+
```gitignore
|
|
386
|
+
# termapy_cfg — keep configs and scripts, ignore session files
|
|
387
|
+
termapy_cfg/*/*.log
|
|
388
|
+
termapy_cfg/*/.cmd_history.txt
|
|
389
|
+
termapy_cfg/*/ss/
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
To specify a config file directly:
|
|
393
|
+
|
|
394
|
+
```sh
|
|
395
|
+
termapy my_device.cfg
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
To override the config directory:
|
|
399
|
+
|
|
400
|
+
```sh
|
|
401
|
+
termapy --cfg-dir /path/to/configs
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Config Validation
|
|
405
|
+
|
|
406
|
+
Termapy validates config files on load and when saving from the editor. Invalid serial settings (baud rate, parity, data bits, stop bits, flow control, encoding) and unknown keys (typos) produce yellow warnings in the log window. Non-standard baud rates are flagged but allowed — some hardware uses custom rates.
|
|
407
|
+
|
|
408
|
+
To validate a config from the command line without launching the UI:
|
|
409
|
+
|
|
410
|
+
```sh
|
|
411
|
+
termapy --check my_device.cfg
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
This prints a JSON result to stdout and exits:
|
|
415
|
+
|
|
416
|
+
```json
|
|
417
|
+
{"status": "ok"}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
```json
|
|
421
|
+
{"status": "warn", "warnings": ["baud_rate: 115201 is not a standard rate (110, 300, ...)"]}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The `--check` flag is read-only — it never modifies the config file.
|
|
425
|
+
|
|
426
|
+
### Config Examples
|
|
427
|
+
|
|
428
|
+
When you create a new config, termapy writes a complete `.cfg` file with all defaults (~30 lines). Here are some of the settings you can change:
|
|
429
|
+
|
|
430
|
+
```json
|
|
431
|
+
{
|
|
432
|
+
"port": "COM4",
|
|
433
|
+
"baud_rate": 115200,
|
|
434
|
+
"auto_connect": true,
|
|
435
|
+
"auto_reconnect": true,
|
|
436
|
+
"title": "Sensor A",
|
|
437
|
+
"border_color": "blue",
|
|
438
|
+
"on_connect_cmd": "rev \n help dev"
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Custom Buttons
|
|
443
|
+
|
|
444
|
+
The demo project's "Info" button runs the `/cfg.info` command via a custom button:
|
|
445
|
+
|
|
446
|
+
```json
|
|
447
|
+
{"enabled": true, "name": "Info", "command": "/cfg.info", "tooltip": "Project info"}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+

|
|
451
|
+
|
|
452
|
+
Add toolbar buttons that send commands, run scripts, or chain multiple actions. Use `\n` to separate multiple commands:
|
|
453
|
+
|
|
454
|
+
```json
|
|
455
|
+
{
|
|
456
|
+
"custom_buttons": [
|
|
457
|
+
{"enabled": true, "name": "Reset", "command": "ATZ", "tooltip": "Reset device"},
|
|
458
|
+
{"enabled": true, "name": "Init", "command": "ATZ\\nAT+BAUD=115200\\n/sleep 500ms\\nAT+INFO", "tooltip": "Full init sequence"},
|
|
459
|
+
{"enabled": true, "name": "Status", "command": "/run status_check.run", "tooltip": "Run status script"}
|
|
460
|
+
]
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Hardware Line Control
|
|
465
|
+
|
|
466
|
+
Set `flow_control` to `"manual"` to get DTR, RTS, and Break buttons in the toolbar — useful for devices that use these lines for reset or bootloader entry:
|
|
467
|
+
|
|
468
|
+
```json
|
|
469
|
+
{
|
|
470
|
+
"port": "COM4",
|
|
471
|
+
"baud_rate": 115200,
|
|
472
|
+
"flow_control": "manual",
|
|
473
|
+
"title": "Hardware Debug"
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
<details>
|
|
478
|
+
<summary>Full config reference</summary>
|
|
479
|
+
|
|
480
|
+
```json
|
|
481
|
+
{
|
|
482
|
+
"config_version": 5,
|
|
483
|
+
"port": "COM4",
|
|
484
|
+
"baud_rate": 115200,
|
|
485
|
+
"byte_size": 8,
|
|
486
|
+
"parity": "N",
|
|
487
|
+
"stop_bits": 1,
|
|
488
|
+
"flow_control": "none",
|
|
489
|
+
"encoding": "utf-8",
|
|
490
|
+
"cmd_delay_ms": 0,
|
|
491
|
+
"line_ending": "\r",
|
|
492
|
+
"send_bare_enter": false,
|
|
493
|
+
"auto_connect": false,
|
|
494
|
+
"auto_reconnect": false,
|
|
495
|
+
"on_connect_cmd": "",
|
|
496
|
+
"echo_input": false,
|
|
497
|
+
"echo_input_fmt": "[purple]> {cmd}[/]",
|
|
498
|
+
"log_file": "",
|
|
499
|
+
"show_timestamps": false,
|
|
500
|
+
"show_line_endings": false,
|
|
501
|
+
"max_grep_lines": 100,
|
|
502
|
+
"title": "",
|
|
503
|
+
"border_color": "",
|
|
504
|
+
"max_lines": 10000,
|
|
505
|
+
"cmd_prefix": "/",
|
|
506
|
+
"config_read_only": false,
|
|
507
|
+
"os_cmd_enabled": false,
|
|
508
|
+
"show_traceback": false,
|
|
509
|
+
"custom_buttons": []
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
| Field | Default | Description |
|
|
514
|
+
| -------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
515
|
+
| `config_version` | `5` | Schema version — managed automatically by the migration system, do not edit |
|
|
516
|
+
| `port` | `""` | Serial port name -- auto-detected when only one port available (supports `$(env.NAME\|fallback)`) |
|
|
517
|
+
| `baud_rate` | `115200` | Baud rate |
|
|
518
|
+
| `byte_size` | `8` | Data bits (5, 6, 7, 8) |
|
|
519
|
+
| `parity` | `"N"` | Parity: `"N"`, `"E"`, `"O"`, `"M"`, `"S"` |
|
|
520
|
+
| `stop_bits` | `1` | Stop bits (1, 1.5, 2) |
|
|
521
|
+
| `flow_control` | `"none"` | `"none"`, `"rtscts"` (hardware), `"xonxoff"` (software), or `"manual"` (shows DTR/RTS/Break buttons) |
|
|
522
|
+
| `encoding` | `"utf-8"` | Character encoding for serial data. Common values: `"utf-8"`, `"latin-1"`, `"ascii"`, `"cp437"` |
|
|
523
|
+
| `cmd_delay_ms` | `0` | Delay in milliseconds between commands in autoconnect sequences and multi-command input (`cmd1 \n cmd2`) |
|
|
524
|
+
| `line_ending` | `"\r"` | Appended to each command. `"\r"` CR, `"\r\n"` CRLF, `"\n"` LF |
|
|
525
|
+
| `send_bare_enter` | `false` | Send the line ending when Enter is pressed with no input (for "press enter to continue" prompts) |
|
|
526
|
+
| `auto_connect` | `false` | Connect to the port on startup |
|
|
527
|
+
| `auto_reconnect` | `false` | Retry every 2.5s if the port drops or fails to open (does not control startup) |
|
|
528
|
+
| `on_connect_cmd` | `""` | Commands to send after connecting, separated by `\n`. Waits for idle between each |
|
|
529
|
+
| `echo_input` | `false` | Echo sent commands locally |
|
|
530
|
+
| `echo_input_fmt` | `"[purple]> {cmd}[/]"` | Rich markup format for echoed commands. `{cmd}` is replaced with the command text |
|
|
531
|
+
| `log_file` | `""` | Session log path. If empty, uses `<name>.log` in the config's subfolder |
|
|
532
|
+
| `show_timestamps` | `false` | Prefix each line in the terminal display with `[HH:MM:SS.mmm]` |
|
|
533
|
+
| `show_line_endings` | `false` | Show dim `\r` and `\n` markers in serial output for line-ending debugging (see note below) |
|
|
534
|
+
| `show_line_numbers` | `false` | Show line numbers in serial output |
|
|
535
|
+
| `hex_mode` | `false` | Display serial I/O as hex bytes instead of text |
|
|
536
|
+
| `max_grep_lines` | `100` | Maximum number of matching lines shown by `/grep` |
|
|
537
|
+
| `proto_frame_gap_ms` | `50` | Silence gap (ms) to detect end of a binary protocol frame |
|
|
538
|
+
| `title` | `""` | Title bar center text. Defaults to the config filename |
|
|
539
|
+
| `border_color` | `""` | Title bar and output border color. Any CSS color name or hex value |
|
|
540
|
+
| `max_lines` | `10000` | Maximum lines in the scrollback buffer |
|
|
541
|
+
| `cmd_prefix` | `"/"` | Prefix for local REPL commands (e.g. `/help`, `/cls`) |
|
|
542
|
+
| `config_read_only` | `false` | Disable the Edit button in config/script/proto pickers (`/cfg` still changes in-memory values) |
|
|
543
|
+
| `os_cmd_enabled` | `false` | Enable the `/os` REPL command to run shell commands |
|
|
544
|
+
| `show_traceback` | `false` | Include full stack trace in serial exception output (for debugging) |
|
|
545
|
+
| `custom_buttons` | `[]` | Array of custom button objects (see Custom Buttons above) |
|
|
546
|
+
|
|
547
|
+
**Note on `show_line_endings`:** This is a debug mode for troubleshooting line-ending mismatches (`\r` vs `\n` vs `\r\n`). When enabled, dim `\r` and `\n` markers appear inline in serial output before the characters are consumed by line splitting. Sent commands also show the configured line ending. Since the markers use ANSI escape sequences, they may interfere with device ANSI color output — turn `show_line_endings` off when not actively debugging.
|
|
548
|
+
|
|
549
|
+
</details>
|
|
550
|
+
|
|
551
|
+
</details>
|
|
552
|
+
|
|
553
|
+
<details>
|
|
554
|
+
<summary><strong>Scripting</strong> — automate command sequences with text files</summary>
|
|
555
|
+
|
|
556
|
+

|
|
557
|
+
|
|
558
|
+
Create text files with one command per line and run them from the Run button or with the `/run` or the Scripts button. IN the file ines starting with `/` are REPL commands, lines starting with `#` are comments and everything else is sent to the device.
|
|
559
|
+
|
|
560
|
+
```text
|
|
561
|
+
# Quick status check
|
|
562
|
+
AT+STATUS
|
|
563
|
+
/delay 300ms
|
|
564
|
+
AT+TEMP
|
|
565
|
+
/delay 300ms
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
Scripts support delays (`/delay 500ms`), screen clearing (`/cls`), confirmation prompts (`/confirm Reset device?`), screenshots, and sequence counters with auto-increment for batch testing. See the demo scripts (`at_demo.run`, `smoke_test.run`) for examples.
|
|
569
|
+
|
|
570
|
+
</details>
|
|
571
|
+
|
|
572
|
+
<details>
|
|
573
|
+
<summary><strong>Data Capture</strong> — timed text capture, structured binary capture to CSV</summary>
|
|
574
|
+
|
|
575
|
+
Capture serial data to files without interrupting normal terminal display.
|
|
576
|
+
|
|
577
|
+
**Text capture** — timed, writes decoded text lines:
|
|
578
|
+
|
|
579
|
+
```sh
|
|
580
|
+
/cap.text log.txt timeout=3s cmd=AT+INFO # capture 3 seconds of text
|
|
581
|
+
/cap.text session.txt timeout=10s mode=append # append, just listen (no command)
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**Binary capture** — raw bytes to file:
|
|
585
|
+
|
|
586
|
+
```sh
|
|
587
|
+
/cap.bin raw.bin bytes=256 cmd=read_all
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**Structured capture** — binary data decoded via format spec to CSV:
|
|
591
|
+
|
|
592
|
+
```sh
|
|
593
|
+
# Single-type column — 50 big-endian unsigned 16-bit values
|
|
594
|
+
/cap.struct data.csv fmt=Val:U1-2 records=50 cmd=AT+BINDUMP u16 50
|
|
595
|
+
|
|
596
|
+
# Mixed-type record — string + u8 + u16 + u32 + float (little-endian)
|
|
597
|
+
/cap.struct mixed.csv fmt=Label:S1-10 Counter:U11 Val16:U13-12 Val32:U17-14 Temp:F21-18 records=20 cmd=AT+BINDUMP 20
|
|
598
|
+
|
|
599
|
+
# Tab-separated output with echo to terminal
|
|
600
|
+
/cap.struct log.tsv fmt=A:U1-2 B:F3-6 records=100 sep=tab echo=on cmd=read
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
The `fmt=` parameter uses the same format spec language as `/proto` — type codes `H` (hex), `U` (unsigned), `I` (signed), `S` (string), `F` (float), `B` (bit) with 1-based byte ranges. Byte range order determines endianness: `U1-2` = big-endian, `U2-1` = little-endian. Named columns (`Temp:U1-2`) produce a CSV header row; unnamed columns (`U1-2`) omit it.
|
|
604
|
+
|
|
605
|
+
| Format spec | C type | Meaning |
|
|
606
|
+
|-------------|------------|--------------------------------|
|
|
607
|
+
| `U1` | `uint8_t` | 1 unsigned byte |
|
|
608
|
+
| `U1-2` | `uint16_t` | 2-byte unsigned, big-endian |
|
|
609
|
+
| `U2-1` | `uint16_t` | 2-byte unsigned, little-endian |
|
|
610
|
+
| `U1-4` | `uint32_t` | 4-byte unsigned, big-endian |
|
|
611
|
+
| `U1-8` | `uint64_t` | 8-byte unsigned, big-endian |
|
|
612
|
+
| `I1` | `int8_t` | 1 signed byte |
|
|
613
|
+
| `I1-2` | `int16_t` | 2-byte signed, big-endian |
|
|
614
|
+
| `I1-4` | `int32_t` | 4-byte signed, big-endian |
|
|
615
|
+
| `I1-8` | `int64_t` | 8-byte signed, big-endian |
|
|
616
|
+
| `F1-4` | `float` | 4-byte IEEE 754 float |
|
|
617
|
+
| `F1-8` | `double` | 8-byte IEEE 754 double |
|
|
618
|
+
| `S1-10` | `char[10]` | 10-byte ASCII string |
|
|
619
|
+
| `H1-4` | | 4 bytes as hex (e.g. `0A1BFF03`) |
|
|
620
|
+
|
|
621
|
+
Auto-numbered filenames: use `$(n000)` for a 3-digit rotating sequence (000–999), tracked across sessions in a counter file.
|
|
622
|
+
|
|
623
|
+
```sh
|
|
624
|
+
/cap.text log_$(n000).txt timeout=3s cmd=AT+INFO # log_000.txt, log_001.txt, ...
|
|
625
|
+
/cap.struct data_$(n00).csv fmt=V:U1-2 records=100 cmd=read
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
A progress bar and Stop button overlay the toolbar during capture. The `Cap` button opens the cap/ folder.
|
|
629
|
+
|
|
630
|
+
</details>
|
|
631
|
+
|
|
632
|
+
<details>
|
|
633
|
+
<summary><strong>Binary Protocol Testing</strong> — hex send/receive, .pro test scripts, CRC</summary>
|
|
634
|
+
|
|
635
|
+
Send raw hex bytes and see the response:
|
|
636
|
+
|
|
637
|
+
```sh
|
|
638
|
+
/proto.send 01 03 00 00 00 01 84 0A
|
|
639
|
+
TX: 01 03 00 00 00 01 84 0A
|
|
640
|
+
RX: 01 03 02 00 07 F9 86
|
|
641
|
+
(7 bytes, 12ms)
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Mix hex and quoted text:
|
|
645
|
+
|
|
646
|
+
```text
|
|
647
|
+
/proto.send "AT+RST\r\n"
|
|
648
|
+
/proto.send FF 00 "hello" 0D 0A
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
No line ending is appended — you send exactly the bytes you specify. Toggle `/proto.hex` to show all normal serial I/O as hex bytes.
|
|
652
|
+
|
|
653
|
+
### Proto Test Scripts
|
|
654
|
+
|
|
655
|
+
Write `.pro` files (TOML format) for repeatable send/expect testing with pass/fail:
|
|
656
|
+
|
|
657
|
+
```toml
|
|
658
|
+
name = "Modbus Register Test"
|
|
659
|
+
frame_gap = "20ms"
|
|
660
|
+
|
|
661
|
+
[[test]]
|
|
662
|
+
name = "Read 1 register"
|
|
663
|
+
send = "01 03 00 00 00 01 84 0A"
|
|
664
|
+
expect = "01 03 02 00 07 F9 86"
|
|
665
|
+
|
|
666
|
+
[[test]]
|
|
667
|
+
name = "Write register 5 = 1234"
|
|
668
|
+
send = "01 06 00 05 04 D2 1B 56"
|
|
669
|
+
expect = "01 06 00 05 04 D2 1B 56"
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Run with `/proto.run <file>` or from the proto debug screen, which adds repeat count, delay between runs, stop-on-error, scrolling results, and visualizer column data.
|
|
673
|
+
|
|
674
|
+
<!-- TODO: screenshot — proto debug screen showing test results with pass/fail coloring and visualizer columns -->
|
|
675
|
+
|
|
676
|
+
### Inline Format Specs
|
|
677
|
+
|
|
678
|
+
Add `send_fmt` and `expect_fmt` to any test step to decode raw bytes into named columns. The proto debug screen displays the decoded values side by side with pass/fail highlighting — turning opaque hex into readable fields.
|
|
679
|
+
|
|
680
|
+
```toml
|
|
681
|
+
[[test]]
|
|
682
|
+
name = "Read 2 registers"
|
|
683
|
+
send = "01 03 00 00 00 02 C4 0B"
|
|
684
|
+
send_fmt = "Title:Modbus_TX Slave:H1 Func:H2 Addr:U3-4 Count:U5-6 CRC:crc16-modbus_le"
|
|
685
|
+
expect = "01 03 04 00 07 00 14 4B FD"
|
|
686
|
+
expect_fmt = "Title:Modbus_Response Slave:H1 Func:H2 Bytes:U3 R0:U4-5 R1:U6-7 CRC:crc16-modbus_le"
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
Each column is `Name:TypeBytes` where the type controls how bytes are displayed:
|
|
690
|
+
|
|
691
|
+
| Type | Description | Example | Display |
|
|
692
|
+
| ------ | ------------------------- | --------------------- | ------------------ |
|
|
693
|
+
| `H` | Hex (uppercase) | `H1` / `H3-4` | `0A` / `0A 2B` |
|
|
694
|
+
| `h` | Hex (lowercase) | `h1` | `0a` |
|
|
695
|
+
| `U` | Unsigned int (big-endian) | `U3-4` | `7` |
|
|
696
|
+
| `I` | Signed int (big-endian) | `I3-4` | `-1` |
|
|
697
|
+
| `S` | ASCII string | `S3-10` | `HELLO` |
|
|
698
|
+
| `B` | Bit field (integer) | `B4-5.0-2` | `3` |
|
|
699
|
+
| `b` | Bit field (binary string) | `b4-5.0-15` | `0000101000101011` |
|
|
700
|
+
| `F` | IEEE 754 float | `F3-6` | `3.14` |
|
|
701
|
+
| `_` | Padding (skip bytes) | `_3-4` | *(hidden)* |
|
|
702
|
+
| `crc*` | CRC auto-check | `CRC:crc16-modbus_le` | `OK` / `FAIL` |
|
|
703
|
+
|
|
704
|
+
Byte indices are 1-based. Ranges use `-` (e.g. `U3-4` = bytes 3–4). **Byte order is controlled by the index direction** — this is how you handle big-endian vs little-endian protocols:
|
|
705
|
+
|
|
706
|
+
- `U3-4` — big-endian (byte 3 is MSB, byte 4 is LSB)
|
|
707
|
+
- `U4-3` — little-endian (byte 4 is LSB, byte 3 is MSB)
|
|
708
|
+
- `U5-8` — 32-bit big-endian (4 bytes, MSB first)
|
|
709
|
+
- `U8-5` — 32-bit little-endian (4 bytes, LSB first)
|
|
710
|
+
|
|
711
|
+
This works for all multi-byte types (`U`, `I`, `H`, `F`, `B`). CRC columns auto-compute and verify the checksum over the preceding bytes. Append `_le` or `_be` to the CRC algorithm name for the byte order of the checksum itself:
|
|
712
|
+
|
|
713
|
+
- `CRC:crc16-modbus_le` — CRC-16/Modbus stored little-endian (low byte first, as Modbus RTU requires)
|
|
714
|
+
- `CRC:crc16-modbus_be` — same algorithm but stored big-endian (high byte first)
|
|
715
|
+
|
|
716
|
+
The demo project includes two `.pro` files that exercise inline format specs: `modbus_inline.pro` (register reads/writes with Modbus decoding) and `bitfield_inline.pro` (bit field extraction and binary display). Run them from the Proto button in `--demo` mode.
|
|
717
|
+
|
|
718
|
+
Here is the test that generates the Modbus response shown below:
|
|
719
|
+
|
|
720
|
+
```toml
|
|
721
|
+
[[test]]
|
|
722
|
+
name = "Read 5 registers from addr 100"
|
|
723
|
+
send = "01 03 00 64 00 05 C4 16"
|
|
724
|
+
send_fmt = "Title:Modbus_TX Slave:H1 Func:H2 Addr:U3-4 Count:U5-6 CRC:crc16-modbus_le"
|
|
725
|
+
expect = "01 03 0A 05 1B 05 28 05 35 05 42 05 4F 8C 46"
|
|
726
|
+
expect_fmt = "Title:Modbus_Response Slave:H1 Func:H2 Bytes:U3 R0:U4-5 R1:U6-7 R2:U8-9 R3:U10-11 R4:U12-13 CRC:crc16-modbus_le"
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+

|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
Finally here is the serial log output of a protocol test:
|
|
733
|
+
|
|
734
|
+
```text
|
|
735
|
+
================================================================================
|
|
736
|
+
[2026-03-12 22:28:14] Script: Demo AT Command Test | Tests: 4 | Repeat: 1
|
|
737
|
+
================================================================================
|
|
738
|
+
[PASS] AT basic
|
|
739
|
+
TX: 41 54 0D
|
|
740
|
+
EXP: 4F 4B 0D 0A
|
|
741
|
+
RX: 4F 4B 0D 0A
|
|
742
|
+
Time: 77ms
|
|
743
|
+
[Hex] TX spec: Hex:h1-*
|
|
744
|
+
[Hex] TX: Hex=41 54 0D
|
|
745
|
+
[Hex] RX spec: Hex:h1-*
|
|
746
|
+
[Hex] RX: Hex=4F 4B 0D 0A
|
|
747
|
+
[PASS] LED on
|
|
748
|
+
TX: 41 54 2B 4C 45 44 20 6F 6E 0D
|
|
749
|
+
EXP: 4F 4B 0D 0A
|
|
750
|
+
RX: 4F 4B 0D 0A
|
|
751
|
+
Time: 128ms
|
|
752
|
+
[Hex] TX spec: Hex:h1-*
|
|
753
|
+
[Hex] TX: Hex=41 54 2B 4C 45 44 20 6F 6E 0D
|
|
754
|
+
[Hex] RX spec: Hex:h1-*
|
|
755
|
+
[Hex] RX: Hex=4F 4B 0D 0A
|
|
756
|
+
[PASS] LED off
|
|
757
|
+
TX: 41 54 2B 4C 45 44 20 6F 66 66 0D
|
|
758
|
+
EXP: 4F 4B 0D 0A
|
|
759
|
+
RX: 4F 4B 0D 0A
|
|
760
|
+
Time: 99ms
|
|
761
|
+
[Hex] TX spec: Hex:h1-*
|
|
762
|
+
[Hex] TX: Hex=41 54 2B 4C 45 44 20 6F 66 66 0D
|
|
763
|
+
[Hex] RX spec: Hex:h1-*
|
|
764
|
+
[Hex] RX: Hex=4F 4B 0D 0A
|
|
765
|
+
[PASS] Unknown command
|
|
766
|
+
TX: 49 4E 56 41 4C 49 44 0D
|
|
767
|
+
EXP: 45 52 52 4F 52 3A 20 55 6E 6B 6E 6F 77 6E 20 63 6F 6D 6D 61 6E 64 20 27 49 4E 56 41 4C 49 44 27 0D 0A
|
|
768
|
+
RX: 45 52 52 4F 52 3A 20 55 6E 6B 6E 6F 77 6E 20 63 6F 6D 6D 61 6E 64 20 27 49 4E 56 41 4C 49 44 27 0D 0A
|
|
769
|
+
Time: 72ms
|
|
770
|
+
[Hex] TX spec: Hex:h1-*
|
|
771
|
+
[Hex] TX: Hex=49 4E 56 41 4C 49 44 0D
|
|
772
|
+
[Hex] RX spec: Hex:h1-*
|
|
773
|
+
[Hex] RX: Hex=45 52 52 4F 52 3A 20 55 6E 6B 6E 6F 77 6E 20 63 6F 6D 6D 61 6E 64 20 27 49 4E 56 41 4C 49 44 27 0D 0A
|
|
774
|
+
Summary: 4/4 PASS (4 tests)
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### CRC Algorithms
|
|
778
|
+
|
|
779
|
+
62 named CRC algorithms from the [reveng catalogue](https://reveng.sourceforge.io/crc-catalogue/all.htm) are built in. Browse with `/proto.crc.list`, inspect with `/proto.crc.help <name>`, compute with `/proto.crc.calc`.
|
|
780
|
+
|
|
781
|
+
</details>
|
|
782
|
+
|
|
783
|
+
<details>
|
|
784
|
+
<summary><strong>Demo Mode</strong> — simulated device for trying everything without hardware</summary>
|
|
785
|
+
|
|
786
|
+
`termapy --demo` launches a completely simulated COM port — no hardware needed. The simulated device (`BASSOMATIC-77`) responds to AT commands, NMEA/GPS sentences, and binary Modbus RTU frames, so you can exercise every termapy feature: serial I/O, scripting, protocol testing, and plugins.
|
|
787
|
+
|
|
788
|
+
> **Note:** The demo command sets (AT, NMEA, Modbus) are not validated protocol implementations. They simulate familiar interfaces so you can explore termapy's features without hardware.
|
|
789
|
+
|
|
790
|
+
<!-- TODO: screenshot — demo mode showing AT command output with the device responding -->
|
|
791
|
+
|
|
792
|
+
#### ASCII Commands
|
|
793
|
+
|
|
794
|
+
The device supports a full AT command set — type commands and get responses just like a real device:
|
|
795
|
+
|
|
796
|
+
| Command | Description |
|
|
797
|
+
| ------------------ | ------------------------------------------------- |
|
|
798
|
+
| `AT` | Connection test (returns `OK`) |
|
|
799
|
+
| `AT+PROD-ID` | Product identifier (returns `BASSOMATIC-77`) |
|
|
800
|
+
| `AT+INFO` | Device info (version, uptime, free memory) |
|
|
801
|
+
| `AT+TEMP` | Read temperature sensor |
|
|
802
|
+
| `AT+LED on\|off` | Control LED |
|
|
803
|
+
| `AT+NAME?` | Query device name |
|
|
804
|
+
| `AT+NAME=val` | Set device name (max 32 chars) |
|
|
805
|
+
| `AT+BAUD?` | Query baud rate |
|
|
806
|
+
| `AT+BAUD=val` | Set baud rate (9600, 19200, 38400, 57600, 115200) |
|
|
807
|
+
| `AT+STATUS` | Device status (LED, uptime, connections) |
|
|
808
|
+
| `AT+RESET` | Reset device (simulates boot sequence) |
|
|
809
|
+
| `mem <addr> [len]` | Hex memory dump (deterministic, max 256 bytes) |
|
|
810
|
+
| `help` | List all commands |
|
|
811
|
+
|
|
812
|
+
#### GPS / NMEA Commands
|
|
813
|
+
|
|
814
|
+
The device responds to standard NMEA queries and PMTK configuration commands. Position is fixed at the 50-yard line of Lumen Field, Seattle.
|
|
815
|
+
|
|
816
|
+
| Command | Description |
|
|
817
|
+
| --------------- | --------------------------------------------- |
|
|
818
|
+
| `$GPGGA` | Position fix (lat, lon, altitude, satellites) |
|
|
819
|
+
| `$GPRMC` | Recommended minimum nav (pos, speed, date) |
|
|
820
|
+
| `$GPGSA` | DOP and active satellites |
|
|
821
|
+
| `$GPGSV` | Satellites in view (elevation, azimuth, SNR) |
|
|
822
|
+
| `$PMTK220,1000` | Set update rate (acknowledged, no effect) |
|
|
823
|
+
| `$PMTK314,...` | Configure sentence output (acknowledged) |
|
|
824
|
+
|
|
825
|
+
#### Binary Protocol Testing
|
|
826
|
+
|
|
827
|
+
The device also speaks Modbus RTU (binary), so you can try protocol test files and visualizers. Use `/proto.send` with hex bytes (CRC included):
|
|
828
|
+
|
|
829
|
+
```sh
|
|
830
|
+
/proto.send 01 03 00 00 00 01 84 0A # read 1 register from addr 0
|
|
831
|
+
/proto.send 01 06 00 05 04 D2 1B 56 # write register 5 = 1234
|
|
832
|
+
/proto.send 01 03 00 05 00 01 94 0B # read back register 5
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
Modbus RTU supports function 0x03 (read holding registers) and 0x06 (write single register) with CRC16 enforced.
|
|
836
|
+
|
|
837
|
+
#### Bundled Scripts, Tests, and Plugins
|
|
838
|
+
|
|
839
|
+
The demo comes with everything wired up so you can try each feature:
|
|
840
|
+
|
|
841
|
+
- **Scripts** — `at_demo.run`, `smoke_test.run`, `status_check.run` — run via the Scripts button or `/run`
|
|
842
|
+
- **Proto test files** — `at_test.pro`, `bitfield_inline.pro`, `modbus_inline.pro` — run via the Proto button for pass/fail results
|
|
843
|
+
- **Plugins** — `/probe` sends a command sequence and reports results; `/cmd` adds a custom shortcut
|
|
844
|
+
|
|
845
|
+
</details>
|
|
846
|
+
|
|
847
|
+
<details>
|
|
848
|
+
<summary><strong>CLI Mode</strong> — plain-text terminal, no TUI</summary>
|
|
849
|
+
|
|
850
|
+
`termapy --cli` runs a plain-text serial terminal in your existing terminal window - no Textual UI, no mouse, just keyboard input and text output. All built-in plugins, scripting, and serial I/O work the same as the TUI.
|
|
851
|
+
|
|
852
|
+
```sh
|
|
853
|
+
termapy --cli my_device # interactive terminal
|
|
854
|
+
termapy --cli --demo # demo device, no hardware needed
|
|
855
|
+
termapy --cli smoke_test.run # run a .run script and exit
|
|
856
|
+
termapy --cli my_device --no-color # strip ANSI color codes
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
Passing a `.run` file to `--cli` automatically infers the config from the file's location and runs it. Passing a config name or path opens an interactive session.
|
|
860
|
+
|
|
861
|
+
**Features:**
|
|
862
|
+
|
|
863
|
+
- Rich colored output (toggle with `/color on|off` or `--no-color`)
|
|
864
|
+
- Command history shared with TUI (up/down arrows, persisted across sessions)
|
|
865
|
+
- Tab completion for REPL commands
|
|
866
|
+
- Script execution with `/run` (same scripts work in both TUI and CLI)
|
|
867
|
+
- `/delay` with progress bar for waits over 3 seconds (Ctrl+C to cancel)
|
|
868
|
+
- All `/port`, `/cfg`, `/var`, `/env`, `/proto.crc`, `/edit` commands work
|
|
869
|
+
|
|
870
|
+
**TUI-only features** (not available in CLI mode):
|
|
871
|
+
|
|
872
|
+
- `/ss.svg`, `/ss.txt` - screenshots (prints "not supported" message)
|
|
873
|
+
- `/grep` - scrollback search (no scrollback buffer in CLI)
|
|
874
|
+
- `/edit.cfg` - opens in system editor instead of built-in config editor
|
|
875
|
+
- Mouse interaction, modal dialogs, custom buttons
|
|
876
|
+
|
|
877
|
+
**Exit:** `/exit`, `/quit`, or Ctrl+C.
|
|
878
|
+
|
|
879
|
+
</details>
|
|
880
|
+
|
|
881
|
+
<details>
|
|
882
|
+
<summary><strong>Extending Termapy</strong> — plugins, subcommands, visualizers</summary>
|
|
883
|
+
|
|
884
|
+
### Plugins
|
|
885
|
+
|
|
886
|
+
Add custom REPL commands by dropping a `.py` file in a plugin folder. No classes to subclass, no registration:
|
|
887
|
+
|
|
888
|
+
```python
|
|
889
|
+
# hello.py — drop into termapy_cfg/plugin/ or termapy_cfg/<config>/plugin/
|
|
890
|
+
from termapy.plugins import Command, PluginContext
|
|
891
|
+
|
|
892
|
+
def _handler(ctx: PluginContext, args: str):
|
|
893
|
+
name = args.strip() or "world"
|
|
894
|
+
ctx.write(f"Hello, {name}!")
|
|
895
|
+
|
|
896
|
+
# ── COMMAND (must be at end of file) ──────────────────────────────────────────
|
|
897
|
+
COMMAND = Command(
|
|
898
|
+
name="hello",
|
|
899
|
+
args="{name}", # {braces} = optional, <angle> = required, "" = no args
|
|
900
|
+
help="Say hello.",
|
|
901
|
+
handler=_handler,
|
|
902
|
+
)
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
**Plugin locations** (loaded in order, later overrides earlier):
|
|
906
|
+
|
|
907
|
+
1. **Built-in** — shipped with termapy, always available
|
|
908
|
+
2. **Global** — `termapy_cfg/plugin/*.py`, shared across all configs
|
|
909
|
+
3. **Per-config** — `termapy_cfg/<name>/plugin/*.py`, specific to one config
|
|
910
|
+
4. **App hooks** — frontend-specific commands (`/ss`, `/delay`, `/run`, etc.)
|
|
911
|
+
|
|
912
|
+
<details>
|
|
913
|
+
<summary>Subcommands</summary>
|
|
914
|
+
|
|
915
|
+
Use `sub_commands` for related operations. Users invoke them with dot notation (`/tool.run`):
|
|
916
|
+
|
|
917
|
+
```python
|
|
918
|
+
from termapy.plugins import Command
|
|
919
|
+
|
|
920
|
+
def _run(ctx, args):
|
|
921
|
+
ctx.write(f"Running {args}...")
|
|
922
|
+
|
|
923
|
+
def _status(ctx, args):
|
|
924
|
+
ctx.write("All good.")
|
|
925
|
+
|
|
926
|
+
# ── COMMAND (must be at end of file) ──────────────────────────────────────────
|
|
927
|
+
COMMAND = Command(
|
|
928
|
+
name="tool",
|
|
929
|
+
help="A tool with subcommands.",
|
|
930
|
+
sub_commands={
|
|
931
|
+
"run": Command(args="<file>", help="Run a file.", handler=_run),
|
|
932
|
+
"status": Command(help="Show status.", handler=_status),
|
|
933
|
+
},
|
|
934
|
+
)
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
The user types `/tool.run myfile` or `/tool.status`.
|
|
938
|
+
|
|
939
|
+
</details>
|
|
940
|
+
|
|
941
|
+
<details>
|
|
942
|
+
<summary>PluginContext API</summary>
|
|
943
|
+
|
|
944
|
+
The `ctx` object passed to every handler:
|
|
945
|
+
|
|
946
|
+
| Method / Attribute | Description |
|
|
947
|
+
| --------------------------- | ------------------------------------------------------------------ |
|
|
948
|
+
| `ctx.write(text, color)` | Print to the terminal (color is optional) |
|
|
949
|
+
| `ctx.write_markup(text)` | Print Rich markup text (e.g. `[bold red]Warning![/]`) |
|
|
950
|
+
| `ctx.cfg` | Current config dict (read-only access) |
|
|
951
|
+
| `ctx.config_path` | Path to the current `.cfg` config file |
|
|
952
|
+
| `ctx.port()` | The raw pyserial object, or `None` when disconnected |
|
|
953
|
+
| `ctx.is_connected()` | Check if the serial port is open |
|
|
954
|
+
| `ctx.log(prefix, text)` | Write to session log: `">"` TX, `"<"` RX, `"#"` status |
|
|
955
|
+
| `ctx.serial_write(data)` | Send bytes to the serial port (auto-logged as TX to session log) |
|
|
956
|
+
| `ctx.serial_wait_idle()` | Wait until serial output settles |
|
|
957
|
+
| `ctx.serial_read_raw()` | Read raw bytes with timeout framing (returns `bytes`) |
|
|
958
|
+
| `ctx.serial_io()` | Context manager for exclusive serial I/O (`with ctx.serial_io():`) |
|
|
959
|
+
| `ctx.ss_dir` | Screenshot directory (`Path`) |
|
|
960
|
+
| `ctx.scripts_dir` | Scripts directory (`Path`) |
|
|
961
|
+
| `ctx.confirm(message)` | Show Yes/Cancel dialog, return `bool` (scripts only) |
|
|
962
|
+
| `ctx.notify(text)` | Show a toast notification |
|
|
963
|
+
| `ctx.clear_screen()` | Clear the terminal output |
|
|
964
|
+
| `ctx.save_screenshot(path)` | Save an SVG screenshot to a file |
|
|
965
|
+
| `ctx.get_screen_text()` | Get terminal content as plain text |
|
|
966
|
+
| `ctx.open_file(path)` | Open a file or folder in the system viewer/editor |
|
|
967
|
+
|
|
968
|
+
There is also `ctx.engine` which exposes internal engine state (sequence counters, echo, config save, etc.). This is used by built-in commands and may change between versions — external plugins should avoid it.
|
|
969
|
+
|
|
970
|
+
</details>
|
|
971
|
+
|
|
972
|
+
<details>
|
|
973
|
+
<summary>Example plugins</summary>
|
|
974
|
+
|
|
975
|
+
See `examples/plugins/` for working examples:
|
|
976
|
+
|
|
977
|
+
- **hello.py** — minimal greeting command
|
|
978
|
+
- **at_test.py** — send AT commands over serial
|
|
979
|
+
- **timestamp.py** — print the current date/time
|
|
980
|
+
- **ping.py** — send a command and measure response time
|
|
981
|
+
|
|
982
|
+
A more complete example ships with `--demo`: the `probe.py` plugin demonstrates the drain → write → read → parse cycle for device interaction. Run `/help probe` or `/help.dev probe` to see its documentation.
|
|
983
|
+
|
|
984
|
+
</details>
|
|
985
|
+
|
|
986
|
+
### Binary Format Specs
|
|
987
|
+
|
|
988
|
+
Embedded protocols send raw bytes. Format specs decode them into human-readable fields - so you see "Temp: 200" and "CRC: OK" instead of `00 C8 ... XX XX`. Used in protocol testing (`.pro` files), data capture (`/cap.struct`, `/cap.hex`), and the proto debug screen.
|
|
989
|
+
|
|
990
|
+
A format spec is a one-line definition of your packet layout. Each field has a name, a type, and a byte range:
|
|
991
|
+
|
|
992
|
+
```text
|
|
993
|
+
"ID:H1 Temp:U2-3 Signed:I4-5 Status:H6"
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
Given the bytes `01 00 C8 FF FE 0A`, this decodes to:
|
|
997
|
+
|
|
998
|
+
```text
|
|
999
|
+
ID = 01 (byte 1 as hex)
|
|
1000
|
+
Temp = 200 (bytes 2-3 as unsigned int, big-endian)
|
|
1001
|
+
Signed = -2 (bytes 4-5 as signed int, big-endian)
|
|
1002
|
+
Status = 0A (byte 6 as hex)
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
In protocol tests, termapy decodes both expected and actual bytes using your spec, then shows per-column pass/fail:
|
|
1006
|
+
|
|
1007
|
+
```text
|
|
1008
|
+
Expected: 01 00 C8 FF FE 0A -> ID:01 Temp:200 Signed:-2 Status:0A
|
|
1009
|
+
Actual: 01 00 C9 FF FE 0A -> ID:01 Temp:201 Signed:-2 Status:0A
|
|
1010
|
+
match MISMATCH match match
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
<details>
|
|
1014
|
+
<summary>Supported types</summary>
|
|
1015
|
+
|
|
1016
|
+
| Code | Meaning | Example | Output |
|
|
1017
|
+
| ------ | ---------------- | ------------------- | ------------- |
|
|
1018
|
+
| `H` | Hex bytes | `H1`, `H3-4` | `0A`, `01FF` |
|
|
1019
|
+
| `U` | Unsigned integer | `U1`, `U3-4` | `10`, `256` |
|
|
1020
|
+
| `I` | Signed integer | `I1`, `I3-4` | `-1`, `+127` |
|
|
1021
|
+
| `S` | ASCII string | `S5-12` | `Hello...` |
|
|
1022
|
+
| `F` | IEEE 754 float | `F1-4` | `3.14` |
|
|
1023
|
+
| `B` | Bit field | `B1.3`, `B1-2.7-9` | `1`, `5` |
|
|
1024
|
+
| `_` | Padding (hidden) | `_:_3-4` | *(skipped)* |
|
|
1025
|
+
| `crc*` | CRC verify | `CRC:crc16m_le` | pass/fail |
|
|
1026
|
+
|
|
1027
|
+
Integers support 1, 2, 3, 4, and 8 byte widths. Floats are 4-byte (F32) or 8-byte (F64).
|
|
1028
|
+
|
|
1029
|
+
</details>
|
|
1030
|
+
|
|
1031
|
+
<details>
|
|
1032
|
+
<summary>Endianness</summary>
|
|
1033
|
+
|
|
1034
|
+
Byte order in the spec IS the endianness - no flags needed:
|
|
1035
|
+
|
|
1036
|
+
- `U2-3` = bytes 2 then 3 = big-endian: `00 C8` = 200
|
|
1037
|
+
- `U3-2` = bytes 3 then 2 = little-endian: `C8 00` = 51200
|
|
1038
|
+
- `I4-5` = big-endian signed: `FF FE` = -2
|
|
1039
|
+
- `I5-4` = little-endian signed: `FE FF` = -257
|
|
1040
|
+
|
|
1041
|
+
You read the spec the same way you read the protocol datasheet. Modbus devices are big-endian (`U2-3`), x86-based devices are little-endian (`U3-2`).
|
|
1042
|
+
|
|
1043
|
+
</details>
|
|
1044
|
+
|
|
1045
|
+
<details>
|
|
1046
|
+
<summary>Real-world examples</summary>
|
|
1047
|
+
|
|
1048
|
+
**Modbus RTU response** (read 2 holding registers):
|
|
1049
|
+
|
|
1050
|
+
```text
|
|
1051
|
+
"Slave:H1 Func:H2 Len:U3 Reg0:U4-5 Reg1:U6-7 CRC:crc16-modbus_le"
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
Decodes: `01 03 04 00 C8 01 F4 XX XX` -> Slave:01 Func:03 Len:4 Reg0:200 Reg1:500 CRC:pass
|
|
1055
|
+
|
|
1056
|
+
**GPS binary packet** (mixed types):
|
|
1057
|
+
|
|
1058
|
+
```text
|
|
1059
|
+
"Sync:H1-2 MsgID:U3 Lat:F4-7 Lon:F8-11 Alt:F12-15 Sats:U16 _:_17 CRC:crc8-maxim"
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
**Sensor with bit flags** (status byte with packed bits):
|
|
1063
|
+
|
|
1064
|
+
```text
|
|
1065
|
+
"Temp:U1-2 Humid:U3 MotorOn:B4.0 AlarmHi:B4.1 AlarmLo:B4.2 Mode:B4.5-7"
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
Decodes byte 4 into individual bit fields: MotorOn=1, AlarmHi=0, AlarmLo=0, Mode=3
|
|
1069
|
+
|
|
1070
|
+
**Simple checksum** (not CRC - custom sum):
|
|
1071
|
+
|
|
1072
|
+
```text
|
|
1073
|
+
"Header:H1 Payload:H2-9 Sum:H10"
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
For non-standard checksums, add a CRC plugin (3 lines of Python):
|
|
1077
|
+
|
|
1078
|
+
```python
|
|
1079
|
+
NAME = "sum8"
|
|
1080
|
+
WIDTH = 1
|
|
1081
|
+
|
|
1082
|
+
def compute(data: bytes) -> int:
|
|
1083
|
+
return sum(data) & 0xFF
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
Drop into `builtins/crc/` or `termapy_cfg/<name>/crc/`.
|
|
1087
|
+
|
|
1088
|
+
</details>
|
|
1089
|
+
|
|
1090
|
+
<details>
|
|
1091
|
+
<summary>CRC support</summary>
|
|
1092
|
+
|
|
1093
|
+
62 built-in algorithms covering CRC-8, CRC-16, CRC-32 families (Modbus, XMODEM, CCITT, USB, and more).
|
|
1094
|
+
|
|
1095
|
+
In format specs, CRC columns verify data integrity automatically:
|
|
1096
|
+
|
|
1097
|
+
- `CRC:crc16-modbus_le` - little-endian Modbus CRC-16
|
|
1098
|
+
- `CRC:crc16-xmodem_be` - big-endian XMODEM CRC-16
|
|
1099
|
+
- `CRC:crc8-maxim` - 1-byte CRC (no endianness needed)
|
|
1100
|
+
|
|
1101
|
+
From the REPL:
|
|
1102
|
+
|
|
1103
|
+
- `/proto.crc.list` - show all 62 algorithms
|
|
1104
|
+
- `/proto.crc.help crc16-modbus` - show parameters
|
|
1105
|
+
- `/proto.crc.calc crc16-modbus 01 03 00 00 00 0A` - compute CRC
|
|
1106
|
+
|
|
1107
|
+
</details>
|
|
1108
|
+
|
|
1109
|
+
</details>
|
|
1110
|
+
|
|
1111
|
+
<details>
|
|
1112
|
+
<summary><strong>Portability</strong></summary>
|
|
1113
|
+
|
|
1114
|
+
Developed and tested on **Windows**. Basic usage verified on **macOS** (serial, ANSI rendering, screenshots). macOS support is **alpha** until further testing. Linux has not been tested.
|
|
1115
|
+
|
|
1116
|
+
</details>
|
|
1117
|
+
|
|
1118
|
+
<details>
|
|
1119
|
+
<summary><strong>Architecture</strong> — threading model</summary>
|
|
1120
|
+
|
|
1121
|
+
Textual runs on a single async event loop. Termapy uses `@work(thread=True)` for blocking operations, posting UI updates via `call_from_thread()`.
|
|
1122
|
+
|
|
1123
|
+
| Worker | Lifetime | Purpose |
|
|
1124
|
+
| ------------------- | ----------- | ------------------------------------------------------- |
|
|
1125
|
+
| `read_serial()` | Long-lived | Reads serial data in a loop, posts lines to the RichLog |
|
|
1126
|
+
| `_auto_reconnect()` | Short-lived | Retries serial connection every 2.5s until success |
|
|
1127
|
+
| `_run_lines()` | Short-lived | Sends multiple commands with inter-command delay |
|
|
1128
|
+
| `_run_script()` | Short-lived | Executes a `.run` script file line by line |
|
|
1129
|
+
| `_send_test()` | Short-lived | Runs a single protocol test case (send/receive/match) |
|
|
1130
|
+
| `_run_cmds()` | Short-lived | Sends setup/teardown commands for protocol tests |
|
|
1131
|
+
|
|
1132
|
+
Only `read_serial()` is long-lived. At most two workers run concurrently: the serial reader plus one command/script/test worker.
|
|
1133
|
+
|
|
1134
|
+
</details>
|
|
1135
|
+
|
|
1136
|
+
<details>
|
|
1137
|
+
<summary><strong>Test Coverage</strong> — 1191 tests, 68% overall</summary>
|
|
1138
|
+
|
|
1139
|
+
1191 tests across 27 test files. Run with `uv run pytest`.
|
|
1140
|
+
|
|
1141
|
+
**Core logic** (serial engine, capture, REPL, protocol, config):
|
|
1142
|
+
|
|
1143
|
+
| Module | Coverage | Test file |
|
|
1144
|
+
| ------------------ | -------- | ------------------------------------ |
|
|
1145
|
+
| `defaults.py` | 97% | `test_defaults.py` |
|
|
1146
|
+
| `scripting.py` | 97% | `test_scripting.py` |
|
|
1147
|
+
| `migration.py` | 98% | `test_migration.py` |
|
|
1148
|
+
| `plugins.py` | 94% | `test_plugins.py` |
|
|
1149
|
+
| `capture.py` | 92% | `test_capture.py` |
|
|
1150
|
+
| `serial_engine.py` | 93% | `test_serial_engine.py` |
|
|
1151
|
+
| `serial_port.py` | 92% | `test_serial_port.py` |
|
|
1152
|
+
| `protocol.py` | 89% | `test_protocol.py` |
|
|
1153
|
+
| `config.py` | 87% | `test_app_config.py` |
|
|
1154
|
+
| `port_control.py` | 80% | `test_port_control.py` |
|
|
1155
|
+
| `repl.py` | 72% | `test_engine.py`, `test_repl_cfg.py` |
|
|
1156
|
+
| `demo.py` | 73% | `test_demo.py` |
|
|
1157
|
+
| `cli.py` | 53% | `test_cli.py` |
|
|
1158
|
+
|
|
1159
|
+
**Built-in plugins** — 15 of 18 plugins tested via mock `PluginContext` in `test_builtins.py`.
|
|
1160
|
+
|
|
1161
|
+
**UI code** — `app.py` (~3400 lines), `proto_debug.py` (~1160 lines), `dialogs.py` (~1200 lines) are Textual UI and tested manually. The 66% overall figure reflects these large untested UI files. Core logic coverage is higher — the focus has been on extracting business logic into testable modules and keeping UI as thin delegation.
|
|
1162
|
+
|
|
1163
|
+
</details>
|
|
1164
|
+
|
|
1165
|
+
<details>
|
|
1166
|
+
<summary><strong>Continuous Integration</strong> — GitHub Actions</summary>
|
|
1167
|
+
|
|
1168
|
+
All tests run automatically on push to `main` and on pull requests via GitHub Actions.
|
|
1169
|
+
|
|
1170
|
+
| Job | What it does |
|
|
1171
|
+
| --- | --- |
|
|
1172
|
+
| **test** | Runs `pytest` across Python 3.11, 3.12, 3.13, and 3.14 |
|
|
1173
|
+
| **coverage** | Runs `pytest --cov` on Python 3.14 and uploads to [Codecov](https://codecov.io/gh/hucker/termapy) |
|
|
1174
|
+
| **audit** | Runs `pip-audit` to check for known vulnerabilities in dependencies |
|
|
1175
|
+
|
|
1176
|
+
The CI badge at the top of this README reflects the current status of the test workflow. See [`.github/workflows/tests.yml`](.github/workflows/tests.yml) for the full configuration.
|
|
1177
|
+
|
|
1178
|
+
</details>
|