Files
SequencerIO/README.md
2026-03-02 17:48:55 -05:00

163 lines
5.7 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 systems.
Reads all digital inputs at 20 Hz, exposes a REST API for signal state,
and executes timed output sequences.
## Layout
```
server.py entrypoint — wires everything together
config.yaml edit this to describe your hardware
config_with_outputs.yaml example with input + output modules
runs.log JSON-lines sequence run history (created at runtime)
arnold/
config.py YAML loader, dataclasses, config validation
terminator_io.py Terminator I/O driver: Modbus TCP, signal cache, poll thread
sequencer.py Sequence execution engine
api.py FastAPI REST application
```
## Quick start
```bash
pip3 install pymodbus fastapi uvicorn pyyaml --break-system-packages
python3 server.py # uses config.yaml, port 8000
python3 server.py --config config_with_outputs.yaml --log-level debug
```
Interactive API docs: `http://<pi-ip>:8000/docs`
## API
| Method | Path | Description |
|--------|------|-------------|
| GET | `/status` | Device comms health, poll rates, active sequence |
| GET | `/io` | All signal states (name → value/stale/updated_at) |
| GET | `/io/{signal}` | Single signal with device/slot/point/modbus_address |
| GET | `/sequences` | List sequences from config |
| GET | `/sequences/{name}` | Sequence detail with step list |
| POST | `/sequences/{name}/run` | Start a sequence → `{run_id}` (409 if one is running) |
| GET | `/runs/{run_id}` | Run result: pending/running/success/failed/error |
| GET | `/runs` | Recent run history (most recent first, `?limit=N`) |
## Config file
```yaml
devices:
- id: ebc100_main
host: 192.168.3.202
port: 502 # default Modbus TCP port
unit_id: 1 # EBC100 responds to any unit ID over TCP; use 1
poll_interval_ms: 50
modules:
- slot: 1
type: T1H-08TDS # 8-pt 24VDC sinking input
points: 8
- slot: 3
type: T1K-16TD2-1 # 16-pt sourcing output
points: 16
logical_io:
- name: sensor_a
device: ebc100_main
slot: 1
point: 1
direction: input
- name: valve_1
device: ebc100_main
slot: 3
point: 1
direction: output
sequences:
- name: actuate
description: "Open valve, verify sensor, close valve"
steps:
- { t_ms: 0, action: set_output, signal: valve_1, state: true }
- { t_ms: 500, action: check_input, signal: sensor_a, expected: true }
- { t_ms: 1000, action: set_output, signal: valve_1, state: false }
```
**Timing:** `t_ms` is absolute from sequence T=0 (not relative delays).
Steps are sorted by `t_ms` at load time; order in the file doesn't matter.
Multiple steps with the same `t_ms` execute in file order.
**Failure:** a failed `check_input` aborts the sequence immediately.
Remaining steps — including output resets — are skipped.
Add an explicit reset sequence (`all_outputs_off`) and call it after a failure.
## Supported module types
| Type | Direction | Points |
|------|-----------|--------|
| T1H-08TDS, T1K-08TDS | input | 8 |
| T1H-08ND3, T1K-08ND3 | input | 8 |
| T1H-16ND3, T1K-16ND3 | input | 16 |
| T1H-08NA, T1K-08NA | input | 8 |
| T1H-08TD1, T1K-08TD1 | output | 8 |
| T1H-08TD2, T1K-08TD2 | output | 8 |
| T1H-16TD1, T1K-16TD1 | output | 16 |
| T1H-16TD2, T1K-16TD2, T1K-16TD2-1 | output | 16 |
| T1H-08TA, T1K-08TA | output | 8 |
| T1H-08TRS, T1K-08TRS | output | 8 |
## T1H-EBC100 hardware quirks
### Unified coil address space
The EBC100 maps **all modules — inputs and outputs — into a single flat
address space** ordered by physical slot number. There is no separate
"input base address" and "output base address".
Example: slot 1 = 8-pt input, slot 2 = 8-pt input, slot 3 = 16-pt output:
| Slot | Module | Points | Coil addresses |
|------|--------|--------|----------------|
| 1 | T1H-08TDS (input) | 8 | 07 |
| 2 | T1H-08TDS (input) | 8 | 815 |
| 3 | T1K-16TD2-1 (output) | 16 | **1631** |
FC05/FC15 output writes must use these unified addresses.
The config loader computes `modbus_address` for every module and signal
automatically — you never write raw addresses in YAML.
### FC02 input reads start at address 0
FC02 (read discrete inputs) returns only input bits, starting at bit index 0,
regardless of where inputs sit in the unified space. The poll thread reads
`total_input_points` bits from FC02 address 0. Because `modbus_address` for
input signals equals their FC02 bit index (inputs occupy the lowest slots in
practice), no remapping is needed.
### No exception on out-of-range addresses
The EBC100 returns zeros for any FC02 read address beyond the installed
modules — it never raises Modbus exception code 2 (illegal data address).
Module presence **cannot** be auto-detected from protocol errors.
The `modules` list in the config is authoritative.
### FC05 write echo
`write_coil` (FC05) echoes back `True` for any address, even unmapped ones.
There is no error feedback for writes to non-existent output points.
Config validation at startup prevents invalid addresses from being used.
### Unit ID is ignored
The EBC100 accepts and echoes back any Modbus unit/slave ID over TCP.
Set `unit_id: 1` in the config (standard default).
### No unsolicited push
Modbus TCP is a strictly polled protocol; the EBC100 has no push capability.
The server polls at `poll_interval_ms` (default 50 ms = 20 Hz).
At 24 input points a single FC02 read takes ~1 ms on a local network.
### Web interface
The EBC100 hosts a minimal HTTP server on port 80 (firmware by Host Engineering).
It exposes IP/subnet/gateway config and serial port mode only — no I/O data.
Port 443, 503, and 8080 are closed. UDP port 502 is not active.