A command-line tool for IEC 61850 — MMS client and server, GOOSE, Sampled Values and SCL — built in pure Go on otfabric/go-iec61850 and otfabric/go-mms.
iec61850ctl gives operators, commissioning engineers and developers terminal access to an IEC 61850 substation:
- MMS client (IEC 61850-8-1) — browse the data model, read and write objects, operate controls, subscribe to reports (including BRCB purge/resume), transfer files and query journals.
- Station bus (IEC 61850-8-1 GOOSE, IEC 61850-9-2 / 9-2LE Sampled Values) — sniff, run supervised subscriptions, publish with guard rails, and read or write the control blocks over MMS.
- Engineering (IEC 61850-6 SCL) — parse, flatten and convert SCL files offline.
- Simulation and integration — run an MMS server from SCL, or a read-only HTTP/JSON API in front of one device.
For scripting, every command has stable exit codes, and the browse, read, control, report, GOOSE and SV commands have json / jsonl output (docs/AUTOMATION.md).
No CGO. Binaries are static Go builds — no libiec61850 native library at runtime.
Black-box e2e runs the built CLI against the four independent stacks of iec61850-interop: libiec61850, IEC61850bean, dscsystems/go-iec61850 and iec61850-rust. It covers their IED servers, their controller/reporter/client adapters against server start, and — for the three stacks with GOOSE and SV support (all but IEC61850bean) — GOOSE and Sampled Values in both directions. See docs/INTEROP.md.
- Features
- Installation
- Platform Support
- Quick Start
- Command Reference
- Usage Examples
- Global Flags and Environment
- Exit Codes
- Output Formats
- Safety
- Development
- Built With
- Documentation
- License
MMS client
- Discover — TCP scan of a subnet/range for MMS (port 102) responders
- Browse — list LDs, LNs, DOs, DAs, datasets, reports, journals, and files
- Read — objects (with FC), datasets, report configuration, journal entries, file download
- Tree — hierarchical or flat model walk;
--serializeJSON for value seeding - Find — path search by LN pattern + DO/DA path; bulk mapping file support
- Subscribe — report control blocks (BR/RP) with GI, optional dataset sync, BRCB purge/resume EntryID, clean disable on exit
- Control — atomic
control inspect/control operate(auto/direct/SBO/SBOw on one association) - Write —
set objectfor scalar MMS attributes (rejectsFC=CO; use control for CO)
Station bus (raw Ethernet)
- GOOSE — list GoCBs from SCL, sniff, supervised subscribe, guarded publish (test frames by default), GoCB read/write over MMS
- Sampled Values — list SVCBs from SCL, sniff, supervised subscribe with live loss/rate statistics, guarded publish (simulation frames by default) with 9-2LE sine waveform injection, MSVCB read/write over MMS
Engineering, simulation and integration
- SCL — offline parse/flatten and CSV convert (no device connection)
- Server — run an MMS server from ICD/CID/SCD (
--scl); optional value seed from serialized JSON - Export — generate libiec61850
.cfgfrom serialized JSON (export-only, not used by this tool’s runtime) - HTTP — read-only JSON API for browse/read/find (no control or write over HTTP; no authentication — see docs/HTTP.md)
See RELEASE.md for the latest release notes. Protocol model primer: docs/PROTOCOL.md.
GitHub Releases has archives for linux (amd64, arm64, armv7), darwin (amd64, arm64) and windows (amd64, arm64), plus a SHA-256 checksum file:
VERSION=0.4.0
curl -LO https://github.com/otfabric/iec61850ctl/releases/download/v$VERSION/iec61850ctl-$VERSION-linux-amd64.tar.gz
curl -LO https://github.com/otfabric/iec61850ctl/releases/download/v$VERSION/iec61850ctl-$VERSION-checksums.txt
sha256sum --ignore-missing -c iec61850ctl-$VERSION-checksums.txt
tar xzf iec61850ctl-$VERSION-linux-amd64.tar.gzRequires Go 1.25+.
git clone https://github.com/otfabric/iec61850ctl.git
cd iec61850ctl
make build-nocheck
# binary: ./bin/iec61850ctlOr:
go install github.com/otfabric/iec61850ctl@latestmake build-allProduces linux/darwin/windows binaries under release/ for amd64/arm64 (and linux arm/v7). See Makefile.
| Feature | Linux | macOS / *BSD | Windows |
|---|---|---|---|
| MMS client, server, SCL, HTTP | yes | yes | yes |
| GOOSE / SV on raw Ethernet | yes (AF_PACKET, needs CAP_NET_RAW) |
yes (/dev/bpf*, e.g. the access_bpf group on macOS) |
no — exit code 8 |
| GoCB / MSVCB access over MMS | yes | yes | yes |
On Linux, grant the capability once instead of running as root:
sudo setcap cap_net_raw+ep "$(command -v iec61850ctl)"server start binds port 102 by default, which needs root or CAP_NET_BIND_SERVICE on Linux; use --port above 1024 otherwise.
# List logical devices
iec61850ctl list lds --host 192.0.2.10
# List LNs in a device
iec61850ctl list lns --host 192.0.2.10 --ld MNSREF615LD0
# Read a measurement (FC MX)
iec61850ctl get object --host 192.0.2.10 \
--object MNSREF615LD0/FMMXU1.Hz.mag.f --fc MX
# Walk the model (flat)
iec61850ctl tree --host 192.0.2.10 --flatten --path MNSREF615LD0/FMMXU1
# Subscribe to a buffered report (Ctrl+C to stop)
iec61850ctl subscribe report --host 192.0.2.10 \
--ld MNSREF615LD0 --ln LLN0 --report rcbMeasFlt01 --type BR \
--show-values --interrogation
# Parse SCL offline
iec61850ctl scl parse --input device.cid --flatten
# Run a local MMS server from SCL
iec61850ctl server start --scl device.cid --host 0.0.0.0 --port 102
# Watch GOOSE and Sampled Values on the station bus
iec61850ctl goose sniff --iface eth0 --count 5
iec61850ctl sv subscribe --iface eth0 --scl station.scd --svcb MSVCB01 --duration 10sDefaults: --port 102. Host/port/IED name can also come from IEC61850_HOST / IEC61850_PORT / IEC61850_IED_NAME (flags win).
iec61850ctl
├── discover / scan Scan subnet (at most /16) or IP range for MMS devices
├── list
│ ├── lds List logical devices (alias: domains)
│ ├── lns List logical nodes (--ld)
│ ├── dos List data objects (--ld --ln)
│ ├── das List data attributes (--ld --ln --do)
│ ├── dss List data sets (--ld --ln)
│ ├── reports List report control blocks
│ ├── journals List journals (--ld or --domain)
│ └── files List remote files
├── get
│ ├── object Read an object reference (--object --fc)
│ ├── ds Get data set details / values
│ ├── report Get RCB configuration
│ ├── journal Read journal entries
│ └── file Download a file
├── tree Full or scoped model tree (--flatten / --serialize)
├── find
│ ├── path Find LN/DO paths by pattern
│ └── bulk Bulk path resolve from a mapping file (--mapping --output)
├── subscribe
│ └── report Subscribe to URCB/BRCB (--type RP|BR; BRCB --purge-buf/--entry-id; --keepalive)
├── control
│ ├── inspect Read ctlModel / controllability
│ └── operate Atomic select/operate journey (one association)
├── set
│ └── object Write a scalar attribute (--fc; rejects CO)
├── scl
│ ├── parse Parse SCL to tree or flat paths
│ └── convert Convert SCL to CSV
├── server
│ ├── start Start MMS server from --scl (--values, --files-dir, --port 0)
│ └── generate-config Export libiec61850 .cfg (export-only)
├── goose
│ ├── list GOOSE control blocks from SCL (--scl)
│ ├── sniff Decode GOOSE frames on an interface (--iface)
│ ├── subscribe Validated, supervised subscription to one GoCB (--iface --scl --gocb)
│ ├── publish Publish a GoCB (--iface --yes; test frames unless --test=false --live)
│ └── gocb get|set Read/write a GoCB over MMS (--ld --gocb)
├── sv
│ ├── list SV control blocks from SCL (--scl)
│ ├── sniff Decode SV ASDUs on an interface (--iface; --layout or --scl/--svcb to decode)
│ ├── subscribe Validated, supervised subscription with statistics (--iface --scl --svcb)
│ ├── publish Publish an SVCB (--iface --yes; simulation frames unless --simulation=false --live; --waveform)
│ └── svcb get|set Read/write an MSVCB over MMS (--ld --svcb)
├── http Read-only HTTP/JSON API fronting one IEC 61850 device
├── version Print version / build metadata
└── completion Shell completion (bash|zsh|fish|powershell)
export IEC61850_HOST=192.0.2.10
iec61850ctl list lds --detailed --format table
iec61850ctl list lns --ld MNSREF615LD0 --detailed
iec61850ctl list dos --ld MNSREF615LD0 --ln FMMXU1
iec61850ctl list das --ld MNSREF615LD0 --ln FMMXU1 --do Hz --detailed
iec61850ctl get object --object MNSREF615LD0/FMMXU1.Hz.mag.f --fc MX --detailed
iec61850ctl get ds --ld MNSREF615LD0 --ln LLN0 --name MeasFlt --detailediec61850ctl list reports --ld MNSREF615LD0 --ln LLN0
iec61850ctl list reports --all --detailed
iec61850ctl get report --ld MNSREF615LD0 --ln LLN0 --report rcbMeasFlt01
iec61850ctl subscribe report \
--ld MNSREF615LD0 --ln LLN0 --report rcbMeasFlt01 --type BR \
--show-values --interrogation --sync --duration 30s
# BRCB: purge buffer or resume from EntryID (hex) before enable
iec61850ctl subscribe report \
--ld MNSREF615LD0 --ln LLN0 --report rcbMeasFlt01 --type BR \
--purge-buf --interrogation --max-reports 1 --format jsonl--interrogation triggers GI after enable. --keepalive (default 5s, 0 disables) re-reads the RCB periodically so a dropped association ends the subscription with exit code 4 instead of waiting silently for reports. --sync reads the RCB dataset once for a baseline snapshot. Use --type RP for unbuffered (URCB). --purge-buf / --entry-id / --resv-tms apply only to --type BR.
# Inspect ctlModel
iec61850ctl control inspect --object InteropLD/GGIO1.SPCSO2 --format json
# Atomic operate (select when required; one association; optional confirm-ref)
iec61850ctl control operate \
--object InteropLD/GGIO1.SPCSO2 \
--value true --type bool --mode auto \
--confirm-ref 'InteropLD/GGIO1.SPCSO2.stVal[ST]' \
--format json
# Scalar write (not a control bypass; FC=CO rejected)
iec61850ctl set object \
--object InteropLD/GGIO1.SetInt1.setVal --fc SP \
--value 5 --type int --verify --format jsonSelection cannot survive across CLI processes. Prefer --mode auto. Confirmation is off unless --confirm-ref is set.
iec61850ctl list files --detailed
iec61850ctl get file --name conf.xml.gz --output ./conf.xml.gz --detailed
iec61850ctl list journals --ld MNSREF615LD0
iec61850ctl get journal --domain MNSREF615LD0 --journal LLN0\$EventLog \
--from 2024-10-30T12:00:00Z --to 2024-10-30T13:00:00Z--from / --to accept RFC3339, space-separated UTC, or Unix milliseconds. Omit --to to read after --from.
iec61850ctl find path --ln MMXU --path Hz
iec61850ctl find path --ln MMXU --path A.phsA --include-das --detailed--ln is a regex (e.g. MMXU matches FMMXU1). --path is an exact DO or DO.DA match.
# Resolve many semantic properties in one pass (example: docs/examples/mapping.json)
iec61850ctl find bulk --mapping docs/examples/mapping.json --output results.jsoniec61850ctl tree --serialize --include all --output device.json
iec61850ctl server start --scl device.cid --values device.json --port 102Details: docs/SERVER.md.
iec61850ctl goose list --scl station.scd
iec61850ctl goose sniff --iface eth0 --appid 0x1001 --count 10 --format jsonl
iec61850ctl goose subscribe --iface eth0 --scl station.scd --gocb IED1/LD0/gcb1 --duration 30s
# Publishing injects traffic: explicit --iface and --yes; test=true unless --test=false --live
iec61850ctl goose publish --iface eth0 --scl device.cid --gocb gcb1 --value 0=true --changes 3 --interval 1s --yes
iec61850ctl goose gocb get --host 192.0.2.10 --ld LD0 --gocb gcb1
iec61850ctl goose gocb set --host 192.0.2.10 --ld LD0 --gocb gcb1 --disableRaw sockets need CAP_NET_RAW on Linux or access to /dev/bpf* on macOS/BSD. Details and example sessions: docs/GOOSE.md.
iec61850ctl sv list --scl merging-unit.cid
iec61850ctl sv sniff --iface eth0 --appid 0x4000 --layout 9-2LE --sample-every 4000
iec61850ctl sv subscribe --iface eth0 --scl merging-unit.cid --svcb MSVCB01 --sample-every 4000 --duration 30s
# Publishing injects traffic: explicit --iface and --yes; simulation bit set unless --simulation=false --live
iec61850ctl sv publish --iface eth0 --scl merging-unit.cid --svcb MSVCB01 --yes --duration 10s \
--waveform IA=100 --waveform IB=100,50,-120 --waveform IC=100,50,120 --waveform VA=325
iec61850ctl sv svcb get --host 192.0.2.10 --ld LD0 --svcb MSVCB01
iec61850ctl sv svcb set --host 192.0.2.10 --ld LD0 --svcb MSVCB01 --disableDetails and example sessions: docs/SV.md.
iec61850ctl http --iec-host 192.0.2.10 --iec-port 102 --listen :8080
# GET /api/logical-devices
# GET /api/logical-nodes?ld=MNSREF615LD0
# GET /api/reports/allErrors are JSON {"error": "...", "code": "..."} with a status derived from the error class: 400 invalid input, 403 rejected/denied, 404 not found, 501 unsupported, 502 device connection failed, 503 shutting down, 504 timeout, 500 otherwise. The global --timeout, --connect-timeout and --retry flags apply to the device connection.
The API has no authentication or TLS and --listen defaults to :8080 (all interfaces): bind it to loopback or put a reverse proxy in front. Endpoint reference: docs/HTTP.md.
| Flag | Env | Description |
|---|---|---|
--host |
IEC61850_HOST |
IED address (required for most client commands) |
--port |
IEC61850_PORT |
MMS port (default 102) |
--ied-name |
IEC61850_IED_NAME |
IED name for dial (domain prefix) and server start SCL selection |
--connect-timeout |
— | Bound on each connect attempt: TCP + COTP + MMS association (default 10s) |
--timeout |
— | Bound on each MMS request (default 10s) |
--retry |
— | Connect retries with exponential backoff and jitter (default 0) |
--retry-max-delay |
— | Cap on the delay between connect retries (default 5s) |
--log-level |
— | Structured logs on stderr: debug, info, warn (default), error, off |
--log-format |
— | text (default) or json |
--debug |
— | Shorthand for --log-level debug (ignored when --log-level is set) |
Flag values override environment variables. discover / scan uses --host as a CIDR (at most /16) or IP range (e.g. 192.0.2.0/24 or 192.0.2.10-20), not a single client target. Optional --resolve-mac needs root to read the ARP cache.
Retries apply only to establishing the connection; invalid input, authentication failures and interrupts are never retried. Logs never go to stdout.
| Code | Meaning |
|---|---|
0 |
Success |
2 |
Usage error: invalid flag, argument or value, including a refused goose/sv publish without --yes, --iface or --live |
3 |
Invalid reference, configuration or SCL |
4 |
Connection failed or lost (including a dropped report subscription) |
5 |
Timeout (connect or request), or --count not reached before --duration |
6 |
Rejected by the server (including failed select/operate, an MMS access-denied result and a refused GoCB/MSVCB write) |
8 |
Service not supported by the server or platform (e.g. raw Ethernet on Windows) |
9 |
Permission: authentication failed, a service the server refuses as not permitted, or a raw-socket permission error |
10 |
Failure (unclassified) |
11 |
Not found (object, file, dataset, IED, RCB, journal, control block, network interface), including a server answer of MMS object-non-existent |
12 |
Write verification (--verify), control confirmation (--confirm-ref), or GoCB/MSVCB enable read-back mismatch |
130 |
Interrupted (SIGINT / SIGTERM) |
The full contract per command is in docs/AUTOMATION.md.
| Commands | --format |
|---|---|
list lds, list lns, list dos, list das |
text, json, csv, table, yaml |
list dss, list reports, get object, get ds, get report |
text, json |
control inspect, control operate, set object |
text, json |
subscribe report, goose sniff, goose subscribe, goose publish |
text, jsonl |
sv sniff, sv subscribe, sv publish |
text, jsonl |
goose list, goose gocb get, goose gocb set |
text, json |
sv list, sv svcb get, sv svcb set |
text, json |
Default is text. Automation contracts: docs/AUTOMATION.md. JSON shapes and JSONL events: docs/json-output.md.
Several commands change the state of real equipment or inject traffic on a live station bus:
| Command | Effect | Guard rail |
|---|---|---|
control operate |
operates a controllable object (possibly switchgear) | explicit --object and --value; --confirm-ref to verify |
set object |
writes a data attribute | FC=CO rejected; --verify reads back |
subscribe report |
writes RCB settings; --purge-buf clears a BRCB's buffer |
the RCB is disabled again on exit |
goose publish |
sends GOOSE frames | explicit --iface, --yes; test flag set unless --test=false --live |
sv publish |
sends SV frames at the configured rate | explicit --iface, --yes; simulation bit set unless --simulation=false --live |
goose gocb set, sv svcb set |
enables, disables or reconfigures a control block | changes only what you pass |
Run these against test equipment first, and only on networks you are authorised to work on. See SECURITY.md for the threat model and how to report a vulnerability.
make check # fmt, staticcheck, golangci-lint, vet, race tests, coverage gate
make build-nocheck # fast binary → bin/iec61850ctl
make test # unit tests with -race
make coverage-pkg-check # every package >= COVER_PKG_MIN (90%), cgo-free
make fuzz # every fuzz target for FUZZTIME (default 10s)
make vet
make e2e # black-box e2e (default: the four client stacks + self)
make e2e-self # self-server smoke only
IEC61850CTL_E2E_STACKS=all make e2e # all CI directions (adds reverse control/report/reader, GOOSE and SV)
make e2e-verify-fixtures # check e2e/testdata hashes vs interop.lock.json
make e2e-update-fixtures # refresh fixtures from IEC61850_INTEROP_DIR
make e2e-docs # regenerate docs/interop-examples.mdCGO_ENABLED=0 for builds and the coverage gate; only make test enables CGO, because Go's race detector needs it on Linux (the code itself is cgo-free). The cmd/ tests drive the CLI in-process against an in-repo MMS server and compare output with golden files under cmd/testdata/golden (refresh with go test ./cmd -update); cmd/chaos_test.go puts a fault-injecting proxy (stalls, resets, slow peers) between CLI and server. External/reverse e2e needs Docker. See Makefile and docs/INTEROP.md.
| Library | Role |
|---|---|
| go-iec61850 | IEC 61850 client/server, SCL model, reports, GOOSE, Sampled Values |
| go-mms | MMS / ISO-on-TCP transport |
| cobra | CLI |
| Doc | Contents |
|---|---|
| docs/PROTOCOL.md | IEC 61850 hierarchy, object refs, FCs |
| docs/SERVER.md | SCL server, readiness JSON, value seeding, .cfg export |
| docs/HTTP.md | HTTP API endpoints, errors, deployment |
| docs/GOOSE.md | GOOSE list/sniff/subscribe/publish, GoCB access, safety rules, example sessions |
| docs/SV.md | Sampled Values list/sniff/subscribe/publish, 9-2LE waveforms, MSVCB access, safety rules, example sessions |
| docs/AUTOMATION.md | Machine-facing stdout/stderr, exit code and flag contract |
| docs/json-output.md | JSON and JSONL output shapes |
| docs/INTEROP.md | Client/reverse matrices, fixtures, CI stacks |
| docs/interop-examples.md | Generated operator examples against the interop fixture |
| RELEASE.md | Release notes |
| SECURITY.md | Security considerations and vulnerability reporting |
This project is licensed under the MIT License. See LICENSE.