Skip to content

Commit 9f57498

Browse files
committed
feat!: serial expect/send automation with monitor mode
Rework the serial module from a single read-only expect into a non-interactive automation engine driven entirely by up-front arguments, suitable for scripts and machines. Modes: - Monitor (no steps): stream the serial output to the client until the session is cancelled or -t elapses (a success). - Sequence (one or more steps): run expect / send / send-raw steps in order; the run fails on the first expect that times out. Grammar: [-t <dur>] [-eol cr|lf|crlf|none] [-keep-escapes] [--] <step>... where a step is expect <regex> / send <data> / send-raw <data>. send payloads support the escapes \r \n \t \\ and \xNN (e.g. \x03 for Ctrl-C). Per-step progress is reported to the client as "--- [n/total] matched/sent ... ---" markers. If the last step is a send, output is shown briefly afterwards so the DUT's reply is visible. A 'delay' module option (with: delay, default 50ms; "0s" disables) paces input by pausing before each send, for consoles that present a prompt before the tty is ready to read. By default terminal escape sequences (cursor moves, colour, queries) are stripped from the output before it is shown or matched, so they don't break expect patterns; pass -keep-escapes to forward the raw stream (e.g. binary data). Internals: a port interface plus a per-run engine with a 64 KiB rolling match buffer (matches across reads, never newline-reset) and a tee that forwards serial output to the client. Structured so an interactive session.Console mode can be added later. Also migrate the serial backend from the unmaintained tarm/serial to go.bug.st/serial, and update the module README and example config. BREAKING CHANGE: The serial module's invocation changed. It no longer accepts a single optional expect regex; callers must pass an explicit step sequence. For example, `serial "(?i)hello"` becomes `serial -- expect "(?i)hello"`. Existing configs/scripts using the old form must be updated. Signed-off-by: Jens Topp <jens.topp@blindspot.software>
1 parent 282989f commit 9f57498

11 files changed

Lines changed: 1692 additions & 133 deletions

File tree

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ linters:
152152
- DeviceServiceClient
153153
- Formatter
154154
- gpio
155+
- port
155156

156157
lll:
157158
line-length: 140

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/go-playground/validator/v10 v10.30.2
99
github.com/google/go-cmp v0.7.0
1010
github.com/stianeikeland/go-rpio/v4 v4.6.0
11-
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
11+
go.bug.st/serial v1.7.1
1212
golang.org/x/crypto v0.51.0
1313
google.golang.org/protobuf v1.36.11
1414
gopkg.in/yaml.v3 v3.0.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ github.com/stianeikeland/go-rpio/v4 v4.6.0 h1:eAJgtw3jTtvn/CqwbC82ntcS+dtzUTgo5q
5353
github.com/stianeikeland/go-rpio/v4 v4.6.0/go.mod h1:A3GvHxC1Om5zaId+HqB3HKqx4K/AqeckxB7qRjxMK7o=
5454
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
5555
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
56-
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
57-
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
56+
go.bug.st/serial v1.7.1 h1:5aP8wYL0UjEYOVs3oPAGscjaSfRQLHtCvBFXNN/rwtc=
57+
go.bug.st/serial v1.7.1/go.mod h1:d0MmS16Qt9b1m06yoYRNUXhRRTJV5Qg2S5EKqQtnayQ=
5858
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
5959
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
6060
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=

pkg/module/serial/README.md

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,109 @@ The serial package provides a single module:
22

33
# Serial
44

5-
This module establishes a serial connection to the DUT from the _dutagent_ and forwards the output.
5+
This module connects to the DUT's serial port from the _dutagent_, forwards the
6+
serial output it reads to the _dutctl_ client, and (optionally) drives a
7+
scripted `send`/`expect` sequence against the port.
8+
9+
All input is supplied up front as arguments, which makes the module suitable for
10+
scripts and automated callers. With **no step arguments** it runs in **monitor**
11+
mode: it streams the serial output until the session is cancelled (or `-t`
12+
elapses). It does not use an interactive console (`session.Console`) yet.
613

714
```
815
ARGUMENTS:
9-
[-t <duration>] [<expect>]
10-
11-
If a regex is provided, the module will wait for the regex to match on the serial output,
12-
then exit with success.
13-
If no expect string is provided, the module will read from the serial port until it is terminated by a signal (e.g. Ctrl-C).
14-
The expect string supports regular expressions. The optional -t flag specifies the maximum time to wait for the regex to match.
15-
The regex is passed to the shell as a single argument. The regex must not contain any newlines.
16-
Quote the regex if it contains spaces or special characters. E.g.: "(?i)hello\s+world!? dutctl"
16+
[-t <duration>] [-eol cr|lf|crlf|none] [-keep-escapes] (monitor: stream output)
17+
[-t <duration>] [-eol cr|lf|crlf|none] [-keep-escapes] [--] <step>... (run a step sequence)
18+
19+
step := expect <regex> | send <data> | send-raw <data>
1720
```
1821

19-
The syntax of the regular expressions accepted is the syntax accepted by RE2 and described at https://golang.org/s/re2syntax.
22+
## Steps
2023

21-
The module is read-only at that time and does not support sending bytes over the serial connection.
24+
Steps run in order, top to bottom. The run exits with success once all steps
25+
complete, and with failure on the first `expect` that times out (or on a serial
26+
error).
2227

23-
See [serial-example-cfg.yml](./serial-example-cfg.yml) for examples.
28+
| Step | Behaviour |
29+
|------|-----------|
30+
| `expect <re2>` | Wait until the serial output matches the [RE2] regular expression. A plain string is a valid regex that matches itself; escape regex metacharacters (`. $ * + ? ( ) [ ] { } ^ \| \`) to match them literally, e.g. `expect '192\.168\.0\.1'`. |
31+
| `send <data>` | Write `<data>` followed by the configured line ending (see `-eol`) to the port. |
32+
| `send-raw <data>` | Write `<data>` verbatim, with no line ending appended. |
2433

25-
## Configuration Options
34+
`send`/`send-raw` data supports the escapes `\r` `\n` `\t` `\\` and `\xNN`
35+
(e.g. `\x03` for Ctrl-C). Each step value is a single argument, so quote values
36+
that contain spaces.
37+
38+
If the last step is a `send`, the module keeps showing output for a moment
39+
afterwards so the DUT's reply to that final input is visible.
40+
41+
## Monitor mode
42+
43+
Invoking `serial` with **no step arguments** streams the serial output to the
44+
client until the session is cancelled (Ctrl-C / disconnect), or until `-t`
45+
elapses — reaching the `-t` deadline in monitor mode is a success. No matching
46+
is done.
47+
48+
## Flags
49+
50+
| Flag | Description |
51+
|------|-------------|
52+
| `-t <duration>` | Global timeout for the whole run (e.g. `30s`, `3m`). `0` (default) means no timeout. |
53+
| `-eol cr\|lf\|crlf\|none` | Line ending appended by `send`. Default `cr` (`\r`), which is what serial consoles expect on Enter. |
54+
| `-keep-escapes` | Keep terminal escape sequences in the output instead of stripping them (e.g. for binary data or exact-byte capture). |
55+
56+
The `--` separator before the steps is optional; it is only needed if a step
57+
value would otherwise look like a flag.
58+
59+
## Output and matching notes
60+
61+
- Serial output is forwarded to the client whenever the module is reading the
62+
port: in monitor mode, while an `expect` is waiting, and (if the last step is
63+
a `send`) for a moment afterwards so its reply is visible. Between sends with
64+
no following `expect` nothing is read, so nothing is forwarded. Step progress
65+
is reported with `--- [n/total] matched/sent … ---` markers.
66+
- Terminal escape sequences (cursor moves, colour, queries) are stripped from
67+
the output before it is shown or matched, so they don't interfere with
68+
patterns. Pass `-keep-escapes` to keep them (for binary data or exact-byte
69+
capture).
70+
- Expect matching uses a rolling window of the most recent **64 KiB** of
71+
output, so a single pattern cannot span more than that.
72+
- Match on distinctive markers/prompts rather than `^`/`$` anchors — the rolling
73+
buffer has no stable line-start or end-of-text.
74+
- The DUT may echo what you send, so an `expect` immediately after a `send` can
75+
match your own input rather than the device's reply.
2676

27-
| Option | Value | Description |
28-
|--------|--------|--------------------------------------------------------|
29-
| port | string | Hostname or IP address of the DUT |
30-
| baud | int | Port number of the SSH server on the DUT (default: 22) |
77+
The syntax of the regular expressions accepted is the syntax accepted by RE2 and
78+
described at https://golang.org/s/re2syntax.
79+
80+
## Examples
81+
82+
```
83+
# Monitor the console (stream until cancelled).
84+
serial
85+
86+
# Wait for a boot marker, then succeed.
87+
serial -- expect 'Welcome to'
88+
89+
# Log in and run a command.
90+
serial -t 60s -- expect 'login:' send root expect 'Password:' send secret expect '# ' send reboot
91+
92+
# Send Ctrl-C, then expect prompt.
93+
serial -- send-raw '\x03' expect '=>'
94+
95+
# Reboot, then see the output that follows the final send.
96+
serial -- expect '# ' send reboot
97+
```
98+
99+
See [serial-example-cfg.yml](./serial-example-cfg.yml) for a configuration
100+
example.
101+
102+
## Configuration Options
31103

104+
| Option | Value | Description |
105+
|--------|--------|---------------------------------------------------------------------|
106+
| port | string | Path to the serial device on the dutagent (e.g. `/dev/ttyUSB0`) |
107+
| baud | int | Baud rate of the serial connection (default: 115200) |
108+
| delay | string | Pause before each send, e.g. `200ms` (default: 50ms; `0s` disables) |
32109

110+
[RE2]: https://golang.org/s/re2syntax

0 commit comments

Comments
 (0)