Skip to content

Commit a06119a

Browse files
author
Michal Tichák
committed
[peanut] peanut now supports direct, fmq batched and single step modes
in TUI and CLI modes written using Claude Opus 4.6
1 parent adbed10 commit a06119a

File tree

5 files changed

+1030
-159
lines changed

5 files changed

+1030
-159
lines changed

cmd/peanut/main.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,46 @@
2525
package main
2626

2727
import (
28+
"flag"
29+
"fmt"
2830
"os"
29-
"strings"
3031

3132
"github.com/AliceO2Group/Control/occ/peanut"
3233
)
3334

3435
func main() {
35-
cmdArg := os.Args[1:]
36-
cmdString := strings.Trim(strings.Join(cmdArg, " "), " ")
36+
fs := flag.NewFlagSet("peanut", flag.ExitOnError)
37+
addr := fs.String("addr", "", "OCC gRPC address (host:port); if empty, OCC_CONTROL_PORT env var is used in direct mode")
38+
mode := fs.String("mode", "direct", "control mode: direct (default), fmq, or fmq-step")
39+
fs.Usage = func() {
40+
fmt.Fprint(os.Stderr, `peanut — process execution and control utility for OCC / FairMQ processes
3741
38-
if err := peanut.Run(cmdString); err != nil {
39-
panic(err)
42+
TUI mode (interactive, launched when no command is given):
43+
OCC_CONTROL_PORT=<port> peanut
44+
peanut -addr host:port -mode fmq
45+
46+
CLI mode (non-interactive, launched when a command is given):
47+
peanut [flags] <command> [args]
48+
Run "peanut -addr x get-state" for full CLI usage.
49+
50+
Flags:
51+
`)
52+
fs.PrintDefaults()
53+
}
54+
_ = fs.Parse(os.Args[1:])
55+
56+
if fs.NArg() > 0 {
57+
// CLI mode — pass all original args so RunCLI can re-parse its own flags
58+
if err := peanut.RunCLI(os.Args[1:]); err != nil {
59+
fmt.Fprintf(os.Stderr, "peanut: %v\n", err)
60+
os.Exit(1)
61+
}
62+
return
63+
}
64+
65+
// TUI mode
66+
if err := peanut.Run(peanut.Options{Addr: *addr, Mode: *mode}); err != nil {
67+
fmt.Fprintf(os.Stderr, "peanut: %v\n", err)
68+
os.Exit(1)
4069
}
4170
}

occ/peanut/README.md

Lines changed: 208 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,227 @@
11
# Process control and execution utility overview
22

33
`peanut` is the **p**rocess **e**xecution **a**nd co**n**trol **ut**ility for OCClib-based O² processes. Its purpose
4-
is to be a debugging and development aid for non-FairMQ O² devices, where FairMQ's interactive
5-
controller is not available.
4+
is to be a debugging and development aid for OCC-based and FairMQ O² devices.
65

76
In aliBuild it is part of the `coconut` package.
87

9-
`peanut` can connect to a running OCClib-based process, query its status, drive its state machine
8+
`peanut` can connect to a running OCClib-based or FairMQ process, query its status, drive its state machine
109
and push runtime configuration data.
1110

12-
`peanut` is an interactive tool, the only information it picks up from its environment is the
13-
`OCC_CONTROL_PORT` variable, which is used to connect to a running OCClib-based process.
11+
`peanut` runs in two modes depending on whether a command is passed:
12+
13+
* **TUI mode** — interactive terminal UI (launched when no command is given)
14+
* **CLI mode** — non-interactive, scriptable (launched when a command is given)
15+
16+
---
17+
18+
## TUI mode
19+
20+
![Screenshot of peanut](peanut.png)
1421

1522
```bash
16-
$ OCC_CONTROL_PORT=<some port> peanut
23+
peanut [flags]
1724
```
1825

19-
![Screenshot of peanut](peanut.png)
26+
| Flag | Default | Description |
27+
|------|---------|-------------|
28+
| `-addr` | `""` | gRPC address `host:port`; if empty, falls back to `OCC_CONTROL_PORT` env var (direct mode only) |
29+
| `-mode` | `direct` | `direct`, `fmq`, or `fmq-step` (see below) |
30+
31+
### Modes
32+
33+
#### `direct` — OCC protobuf (default)
34+
35+
Connects to an OCClib-based process using the standard OCC protobuf codec.
36+
The state machine operates on OCC states: `STANDBY`, `CONFIGURED`, `RUNNING`, `ERROR`.
37+
38+
```bash
39+
OCC_CONTROL_PORT=47100 peanut
40+
# or
41+
peanut -addr localhost:47100 -mode direct
42+
```
43+
44+
Control buttons: **CONFIGURE**, **RESET**, **START**, **STOP**, **RECOVER**, **EXIT**
45+
46+
#### `fmq` — FairMQ JSON codec with automatic multi-step sequencing
47+
48+
Connects to a FairMQ device using the JSON codec. Each OCC-level button press
49+
automatically drives the full underlying FairMQ state machine sequence.
50+
The state is displayed as an OCC-mapped state (`STANDBY`, `CONFIGURED`, `RUNNING`…).
51+
52+
```bash
53+
peanut -addr localhost:47100 -mode fmq
54+
```
55+
56+
Control buttons: **CONFIGURE**, **RESET**, **START**, **STOP**, **RECOVER**, **EXIT**
57+
58+
Sequences driven automatically:
2059

21-
`peanut` commands are documented inline. Each transition is applied immediately and
22-
the state is updated in real time.
60+
| Button | FairMQ steps |
61+
|--------|-------------|
62+
| CONFIGURE | INIT DEVICE → COMPLETE INIT → BIND → CONNECT → INIT TASK |
63+
| RESET | RESET TASK → RESET DEVICE |
64+
| START | RUN |
65+
| STOP | STOP |
66+
| RECOVER | RESET DEVICE (from ERROR) |
67+
| EXIT | RESET (if needed) → END |
2368

24-
Compared to the raw gRPC API, the following limitations apply:
69+
#### `fmq-step` — FairMQ JSON codec with granular per-step control
2570

26-
* It is not possible to perform a `GO_ERROR` transition, as this transition is only triggered from
27-
user code.
71+
Connects to a FairMQ device using the JSON codec. Exposes each individual FairMQ
72+
state machine step as a separate button. The state is displayed as the raw FairMQ state.
2873

29-
* The `CONFIGURE` transition may be triggered both with and without runtime configuration data, which
30-
may or may not be suitable depending on user code. All other transitions send no payload.
74+
```bash
75+
peanut -addr localhost:47100 -mode fmq-step
76+
```
77+
78+
| Key | Button | Transition |
79+
|-----|--------|-----------|
80+
| `1` | INIT DEVICE | IDLE → INITIALIZING DEVICE |
81+
| `2` | COMPLETE INIT | INITIALIZING DEVICE → INITIALIZED |
82+
| `3` | BIND | INITIALIZED → BOUND |
83+
| `4` | CONNECT | BOUND → DEVICE READY |
84+
| `5` | INIT TASK | DEVICE READY → READY |
85+
| `6` | RUN | READY → RUNNING |
86+
| `7` | STOP | RUNNING → READY |
87+
| `8` | RESET TASK | READY → DEVICE READY |
88+
| `9` | RESET DEVICE | → IDLE |
89+
| `0` | END | IDLE → EXITING |
90+
91+
### Common TUI controls (all modes)
92+
93+
| Key | Action |
94+
|-----|--------|
95+
| `n` | **Reconnect** — re-establish the gRPC connection to the controlled process. Use this when the process has been restarted after a crash or deliberate termination. |
96+
| `l` | **Load configuration** — open a file dialog to read a YAML or JSON configuration file. The path field supports tab-completion. Once loaded, the right panel shows `NOT PUSHED` until the next CONFIGURE transition, then `PUSHED`. |
97+
| `q` | **Quit** — disconnect and exit without sending any transitions. |
98+
99+
### Connection monitoring
100+
101+
While connected, peanut passively monitors the gRPC connection in a background goroutine and detects process termination without any button press. The strategy depends on what the controlled process supports:
102+
103+
1. **StateStream** (OCClib processes, `direct` mode) — subscribes to the state stream; any disconnect immediately triggers `UNREACHABLE` and an error modal. State updates from the stream are also reflected in the display in real time.
104+
2. **EventStream** (FairMQ processes, `fmq`/`fmq-step` modes) — subscribes to the event stream; disconnect is detected immediately when the stream breaks.
105+
3. **Polling** (fallback) — if neither stream is available, `GetState` is polled every 2 seconds.
106+
107+
When the process dies, the state display shows `UNREACHABLE` and an error modal appears. After restarting the controlled process, press `n` to reconnect.
108+
109+
### Runtime configuration files
110+
111+
Configuration files are YAML or JSON, with arbitrarily nested structure.
112+
`peanut` flattens them to dot-notation key=value pairs before pushing.
113+
Integer map keys and integer values are both handled correctly.
114+
115+
Example (channel configuration):
116+
117+
```yaml
118+
chans:
119+
data:
120+
numSockets: 1
121+
0:
122+
address: ipc://@o2ipc-example
123+
method: bind
124+
type: push
125+
transport: shmem
126+
sndBufSize: 1000
127+
rcvBufSize: 1000
128+
sndKernelSize: 0
129+
rcvKernelSize: 0
130+
rateLogging: 0
131+
```
132+
133+
This flattens to entries like `chans.data.0.address=ipc://@o2ipc-example`.
134+
135+
---
136+
137+
## CLI mode
138+
139+
```bash
140+
peanut [flags] <command> [args]
141+
```
142+
143+
| Flag | Default | Description |
144+
|------|---------|-------------|
145+
| `-addr` | `localhost:47100` | gRPC address `host:port` |
146+
| `-mode` | `fmq` | `fmq` (JSON codec) or `direct` (protobuf) |
147+
| `-timeout` | `30s` | timeout for unary gRPC calls |
148+
| `-config` | `""` | path to YAML/JSON file; flattened key=value pairs are sent as arguments. Inline `key=val` arguments take precedence. |
149+
150+
### Commands
151+
152+
#### `get-state`
153+
154+
Print the current FSM state.
155+
156+
```bash
157+
peanut -addr localhost:47100 get-state
158+
```
159+
160+
#### `transition <fromState> <toState> [key=val ...]`
161+
162+
High-level state transition. In `fmq` mode drives the full multi-step FairMQ sequence automatically.
163+
164+
```bash
165+
# FairMQ: drive full configure sequence
166+
peanut -addr localhost:47100 -mode fmq transition STANDBY CONFIGURED \
167+
chans.data.0.address=ipc://@o2ipc-example
168+
169+
# FairMQ: with config file
170+
peanut -addr localhost:47100 -mode fmq -config stfsender-configure-args.yaml \
171+
transition STANDBY CONFIGURED
172+
173+
# Direct OCC
174+
peanut -addr localhost:47100 -mode direct transition STANDBY CONFIGURED
175+
```
176+
177+
FairMQ sequences driven automatically:
178+
179+
| From → To | Steps |
180+
|-----------|-------|
181+
| `STANDBY → CONFIGURED` | INIT DEVICE, COMPLETE INIT, BIND, CONNECT, INIT TASK |
182+
| `CONFIGURED → RUNNING` | RUN |
183+
| `RUNNING → CONFIGURED` | STOP |
184+
| `CONFIGURED → STANDBY` | RESET TASK, RESET DEVICE |
185+
186+
#### `direct-step <srcState> <event> [key=val ...]`
187+
188+
Low-level: send a single raw OCC gRPC Transition call (protobuf codec).
189+
190+
```bash
191+
peanut -addr localhost:47100 -mode direct direct-step STANDBY CONFIGURE key=val
192+
```
193+
194+
Events: `CONFIGURE`, `RESET`, `START`, `STOP`, `RECOVER`, `EXIT`
195+
196+
#### `fmq-step <srcFMQState> <fmqEvent> [key=val ...]`
197+
198+
Low-level: send a single raw FairMQ gRPC Transition call (JSON codec).
199+
State/event names that contain spaces must be quoted.
200+
201+
```bash
202+
peanut -addr localhost:47100 fmq-step IDLE "INIT DEVICE" chans.x.0.address=ipc://@foo
203+
peanut -addr localhost:47100 fmq-step READY RUN
204+
```
205+
206+
#### `state-stream`
207+
208+
Subscribe to `StateStream` and print state updates until interrupted (Ctrl-C).
209+
210+
```bash
211+
peanut -addr localhost:47100 state-stream
212+
```
213+
214+
#### `event-stream`
215+
216+
Subscribe to `EventStream` and print events until interrupted (Ctrl-C).
217+
218+
```bash
219+
peanut -addr localhost:47100 event-stream
220+
```
31221

32-
The last two commands are **not** transitions:
222+
---
33223

34-
* `Load configuration` allows the user to read in a JSON or YAML file containing sample
35-
configuration data that is then available to be pushed to the controlled process during a future
36-
`CONFIGURE` transition. On startup, there is no file loaded, so a `CONFIGURE` transition will push
37-
an empty payload. Once a runtime configuration file is loaded, its title bar reports `NOT PUSHED`
38-
until the next `CONFIGURE` transition, at which point it becomes `PUSHED`.
224+
## Limitations
39225

40-
* `Quit` disconnects from the controlled process and quits `peanut`, but it performs no transitions
41-
or other data exchange with the controlled process. A future instance of `peanut` may reattach itself
42-
to the same process and continue from there.
226+
* The `GO_ERROR` transition cannot be triggered from `peanut`, as it is only triggered from user code inside the controlled process.
227+
* `Quit` / `q` disconnects without sending any transition. A future instance of `peanut` can reattach to the same process and continue.

0 commit comments

Comments
 (0)