Files
SequencerIO/README.md

180 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Arnold — Terminator I/O Server
Fast-poll Modbus TCP server for AutomationDirect Terminator I/O (T1H-EBC100).
Digital and analog I/O, REST API, timed output sequences, web UI, TUI debugger.
## Quick start
```bash
pip3 install pymodbus fastapi uvicorn pyyaml textual --break-system-packages
# REST API + web UI
python3 server.py --config config_with_outputs.yaml
# TUI debugger (standalone, no API server)
python3 tui.py config_with_outputs.yaml
```
Web UI: `http://<host>:8000`
API docs: `http://<host>:8000/docs`
## Web UI
Three-panel layout (mirrors the TUI). Polls I/O at ~20 Hz.
- **Inputs** — live digital (dot indicators) and analog (raw integer) values
- **Outputs** — click to toggle digital; +/- and Write for analog (065535)
- **Sequences** — run button, live step list with progress bar
Responsive: 3-column on desktop, stacked on tablet/phone.
## TUI
Terminal debugger with the same three panels.
| Key | Action |
|-----|--------|
| Tab | Cycle focus: Outputs / Sequences |
| Space/Enter | Toggle digital output / Run sequence |
| +/- | Adjust analog output (step 100) |
| Enter | Commit analog value |
| 0 / 1 | All digital outputs OFF / ON |
| r | Reconnect all devices |
| q | Quit |
## API
| Method | Path | Description |
|--------|------|-------------|
| GET | `/status` | Device health, poll rates, active run |
| GET | `/io` | All signal states |
| GET | `/io/{signal}` | Single signal + metadata (category, value_type, modbus_space) |
| POST | `/io/{signal}/write` | Write output: `{"value": true}` or `{"value": 4000}` |
| GET | `/config/signals` | Full signal config list (for UI bootstrap) |
| GET | `/sequences` | List sequences |
| GET | `/sequences/{name}` | Sequence detail with steps |
| POST | `/sequences/{name}/run` | Start sequence (202), 409 if busy |
| GET | `/runs/{run_id}` | Run result |
| GET | `/runs` | Recent run history |
## Config
```yaml
devices:
- id: ebc100_main
host: 192.168.3.202
port: 502
unit_id: 1
poll_interval_ms: 50
modules:
- { slot: 1, type: T1H-08TDS } # 8-pt digital input
- { slot: 2, type: T1H-08AD-1 } # 8-ch analog input (12-bit)
- { slot: 3, type: T1K-16TD2-1 } # 16-pt digital output
- { slot: 4, type: T1H-04DA-1 } # 4-ch analog output (12-bit)
logical_io:
- { name: sensor_a, device: ebc100_main, slot: 1, point: 1, direction: input }
- { name: pressure, device: ebc100_main, slot: 2, point: 1, direction: input }
- { name: valve_1, device: ebc100_main, slot: 3, point: 1, direction: output, default_state: false }
- { name: dac_ch1, device: ebc100_main, slot: 4, point: 1, direction: output, default_value: 2048 }
sequences:
- name: actuate
description: "Open valve, set DAC, verify sensor"
steps:
- { t_ms: 0, action: set_output, signal: valve_1, state: true }
- { t_ms: 100, action: set_output, signal: dac_ch1, value: 4000 }
- { t_ms: 500, action: check_input, signal: sensor_a, expected: true }
- { t_ms: 600, action: check_input, signal: pressure, expected_value: 2000, tolerance: 100 }
- { t_ms: 1000, action: set_output, signal: valve_1, state: false }
```
`t_ms` is absolute from T=0 (not relative). Steps auto-sort by `t_ms`.
Failed `check_input` aborts immediately — remaining steps are skipped.
`wait_input` blocks until condition met or `timeout_ms` expires.
## Supported modules (44 types)
| Category | Examples | Address space |
|----------|----------|---------------|
| Digital input | T1H-08TDS, T1H-16ND3, T1K-08ND3, ... | coil (FC02) |
| Digital output | T1H-08TD1, T1K-16TD2-1, T1H-08TA, ... | coil (FC05/FC15) |
| Relay output | T1H-08TRS, T1H-16TRS2, T1K-08TRS | coil (FC05/FC15) |
| Analog input | T1H-08AD-1 (12-bit), T1H-16AD-2 (15-bit), ... | register (FC04) |
| Analog output | T1H-04DA-1 (12-bit), T1H-08DA-2 (15-bit), ... | register (FC06/FC16) |
| Temperature input | T1H-08THM, T1H-04RTD, T1K-08THM, T1K-04RTD | register (FC04) |
Full registry in `arnold/module_types.py`.
---
## Architecture
```
server.py Entrypoint: config load, poll start, default writes, uvicorn
tui.py Textual TUI debugger (standalone, no API server)
probe_terminator.py Standalone Modbus probe script (not part of package)
config.yaml Input-only hardware config
config_with_outputs.yaml Input + output hardware config
runs.log JSON-lines run history (created at runtime)
arnold/
__init__.py
module_types.py ModuleType frozen dataclass + 44-module registry
config.py YAML loader, validation, dual address space computation
terminator_io.py Modbus TCP driver (dual-connection), signal cache, poll thread
sequencer.py Sequence engine: timing, digital+analog set/check/wait
api.py FastAPI app: REST endpoints, static file mount for web UI
web/
index.html Single-page app shell
style.css Dark theme, responsive (desktop/tablet/phone)
app.js Vanilla JS: polling, output control, sequence runner
```
### Data flow
```
YAML config ──► config.py ──► module_types.py (resolve part numbers)
terminator_io.py
┌─ TerminatorIO (two TCP connections per device)
│ ┌─ _reader conn ── FC02 read discrete inputs (digital)
│ │ FC04 read input registers (analog)
│ └─ _writer conn ── FC05/FC06 write single coil/register
│ FC15/FC16 write multiple coils/registers
└─ _PollThread (daemon, reads via _reader each cycle)
└─ IORegistry (multi-device coordinator, signal cache)
┌────────┼────────┐
▼ ▼ ▼
api.py sequencer tui.py / web UI
```
### Dual address spaces
The EBC100 has two independent flat address spaces:
- **Coil space** (1-bit) — digital modules. FC01/FC02/FC05/FC15. Sequential by slot.
- **Register space** (16-bit) — analog/temperature modules. FC03/FC04/FC06/FC16. Sequential by slot.
A digital module advances only `coil_offset`. An analog module advances only `register_offset`. They do not interfere. `config.py` computes all addresses at load time.
### Dual-connection architecture
Each `TerminatorIO` opens two independent TCP connections to the EBC100:
a **reader** (used exclusively by the poll thread for FC02/FC04) and a
**writer** (used by sequencer/API/TUI for FC05/FC06/FC15/FC16). Each has
its own lock and reconnect state. Writes never block behind poll reads,
reducing typical output actuation jitter from 535 ms to 519 ms.
### EBC100 quirks
- Returns zeros for out-of-range reads (no Modbus exception code 2)
- FC05 echoes True for any address, even unmapped — no write error feedback
- Responds to any unit ID (echoed, not routed). Use `unit_id: 1`
- No unsolicited push — polling mandatory
- UDP 502 inactive; ports 443, 503, 8080 closed