open61850
IEC 61850 for Python, under the Apache 2.0 licence: an MMS client (reports, report control blocks, controls), GOOSE and Sampled Values codecs, Sampled Values publication with a real-time engine, an SCL reader and a Linux capture for the process bus. The library is pure Python (standard library only, Python 3.10 or later); the optional real-time engine is in Rust.
It was written for a test and diagnostic platform of a digital substation process bus, and checked there against real IEDs (a Schneider VMC7 and an ABB SSC600). The other open-source IEC 61850 stack, libiec61850, is GPL; open61850 is an alternative for projects that cannot take a GPL dependency.
Status: alpha. The MMS client side and SV publication are what exist and what has been tested; there is no MMS server yet. Until 1.0, minor versions may change the API.
Installation
pip install open61850
pip install "open61850[rt]" # adds the real-time SV engine (Linux wheels, x86_64 and aarch64)
What is in it
| Module | Content |
|---|---|
open61850.mms |
MMS client over TCP: association (Session, Presentation, ACSE, Initiate) with the negotiated limits honoured, reads and writes, GetNameList, type and data set descriptions, several requests in flight matched by invokeID, reports delivered to callbacks, typed errors |
open61850.mms.report |
IEC 61850 report decoding driven by the report's own OptFlds and inclusion bit string (segmentation, reason codes, data references) |
open61850.mms.rcb |
Report control blocks: status, free instance, reservation (edition 1 and 2 BRCBs, URCBs), enabling with checked writes, release |
open61850.mms.control |
Controls: direct and select-before-operate, normal and enhanced security (waits for the CommandTermination, reports the LastApplError AddCause) |
open61850.goose |
GOOSE PDUs and frames (IEC 61850-8-1) |
open61850.sv |
Sampled Values PDUs and frames (IEC 61850-9-2, IEC 61869-9), INT32 + quality samples |
open61850.sv_publisher |
SV publication: streams, waveforms aligned on the UNIX epoch, periodic faults, 6I3U / 4I4U data sets, frame templates, Publisher (native real-time engine or a Python thread) |
open61850.ethernet |
Ethernet II and 802.1Q framing with the APPID header |
open61850.data |
MMS Data values and their BER encoding |
open61850.quality |
Quality and TimeQuality in readable form |
open61850.scl |
SCL files: IEDs, addresses, logical devices, report control blocks and data sets |
open61850.capture |
GOOSE and SV capture on Linux from an AF_PACKET TPACKET_V3 ring, kernel timestamps, 802.1Q tags restored, no libpcap |
open61850.ber |
ASN.1 BER primitives |
Only open61850.mms, open61850.capture and the Publisher of open61850.sv_publisher do I/O. The public names of each module are those of its __all__.
Examples
Read a value and list the logical devices:
from open61850.mms import MmsClient, ObjectName, OBJECT_CLASS_DOMAIN
with MmsClient.connect("192.0.2.10") as client:
print(client.association.max_outstanding_calling) # requests the IED accepts at once
print(client.get_name_list(OBJECT_CLASS_DOMAIN))
print(client.read(ObjectName("LLN0$ST$Mod$stVal", "IED01_LD0")))
Subscribe to a buffered report control block:
import queue
from open61850.mms import MmsClient, ObjectName, decode_report, is_report, rcb
reports = queue.Queue()
with MmsClient.connect("192.0.2.10", on_information_report=reports.put) as client:
instances = [ObjectName(f"LLN0$BR$CB_MEAS{i:02d}", "IED01_LD0") for i in range(1, 4)]
free = rcb.find_free(client, instances)
rcb.enable(client, free.rcb) # default RcbSettings: dchg, qchg, integrity, GI
try:
while True:
message = reports.get()
if is_report(message):
report = decode_report(message)
print(report.rpt_id, report.seq_num, [(e.index, e.value) for e in report.entries])
finally:
rcb.disable(client, free.rcb) # also releases the reservation
Operate a breaker (the control model is read from the IED):
from open61850.mms import MmsClient, ObjectName, operate
with MmsClient.connect("192.0.2.10") as client:
result = operate(client, ObjectName("CBCSWI1$CO$Pos", "IED01_BayLD"), False) # False = open
print(result) # control model, ctlNum, CommandTermination, duration
A refusal raises ControlError with the AddCause of the IED.
Decode GOOSE and SV frames captured on the process bus (Linux, root):
from open61850 import goose, sv
from open61850.capture import PacketCapture
with PacketCapture("eth1") as cap:
while True:
frame = cap.recv()
if frame is None:
continue
if (decoded := goose.decode_goose_frame(frame.data)) is not None:
eth, pdu = decoded
print(frame.timestamp, pdu.gocb_ref, pdu.st_num, pdu.sq_num)
elif (decoded := sv.decode_sv_frame(frame.data)) is not None:
eth, pdu = decoded
print(frame.timestamp, [(a.sv_id, a.smp_cnt) for a in pdu.asdus])
Publish Sampled Values, as a merging unit or a simulator would (Linux, root):
from open61850.sv import Publisher, SvStream, Fault, three_phase
stream = SvStream(
sv_id="MU01_SV1", app_id=0x4000, dst_mac="01:0c:cd:04:00:01", src_mac="02:00:00:00:00:01",
waves=three_phase(i_peak=10, v_peak=100, i_lag_deg=30), # 6I3U: Ia Ib Ic Ires In Ih Va Vb Vc
fault=Fault(three_phase(i_peak=10, v_peak=100, ia_peak=50, va_peak=20), cycle_s=4), # every 4 s, for 2 s
conf_rev=1, smp_synch=2, vlan_id=100, vlan_priority=4,
)
with Publisher("eth1", rate=4800, asdus_per_frame=2, rt_priority=50) as pub:
pub.add(stream)
pub.start() # at the next second: smpCnt 0 goes out on the second
...
Waveforms are functions of UNIX time, so several streams, processes or machines on the same clock stay in phase. With open61850[rt] the frames are sent by the Rust engine: absolute CLOCK_REALTIME deadlines, one sendmmsg per period for all streams, optional SCHED_FIFO priority and CPU pinning. Without it, a Python thread sends the same frames, with only the precision of time.sleep (tests, low rates). Measured on loopback on a Xeon server, 3 streams at 4800 samples/s, SCHED_FIFO 50: every sample sent, delay after the nominal sample time median 6 µs, 99th percentile 14 µs, maximum 46 µs over 10 s.
Command line
open61850-mms 192.0.2.10 association
open61850-mms 192.0.2.10 domains
open61850-mms 192.0.2.10 rcbs --status
open61850-mms 192.0.2.10 read 'IED01_LD0/LLN0$DC$NamPlt'
open61850-mms 192.0.2.10 dataset 'IED01_LD0/LLN0$DS_MEAS'
open61850-mms 192.0.2.10 subscribe 'IED01_LD0/LLN0$BR$CB_MEAS'
open61850-mms 192.0.2.10 operate 'IED01_BayLD/CBCSWI1$CO$Pos' open
subscribe takes a block or a group name without its instance number, picks a free instance, prints the decoded reports and releases the block on Ctrl-C.
Scope and limits
- No MMS server; GOOSE is encoded and decoded, but publishing it (retransmission scheme) is up to the application.
- SV publication sends sinusoids and periodic faults; arbitrary sample sources (replay, live measurements) are to come.
- Not implemented: file services, log control blocks and journals, setting groups, IEC 62351 security.
- The association proposes fixed calling/called AP titles and selectors by default (
AssociationParameterschanges them). - Tested against two IED families so far; reports of other IEDs are welcome.
Development
python -m pip install pytest
python -m pytest
The tests need no network. The AF_PACKET capture tests run on Linux as root (sudo python -m pytest tests/test_capture.py); on another OS, docker run --rm --privileged -v "$PWD":/src -w /src python:3.13-slim sh -c "pip install pytest && python -m pytest".
Maintainer notes (what the IED captures taught, design decisions) are in AGENTS.md.
License
Copyright 2026 Florent Carli
Licensed under the Apache License, Version 2.0. See LICENSE for the full text.
Release files for open61850 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| open61850-0.2.0.tar.gz | 81.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| open61850-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 150.7 kB
Release files / open61850-0.2.0.tar.gz
| Download URL | open61850-0.2.0.tar.gz |
|---|---|
| Size | 81.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3be67db78dc481d7c86b2a3884a93702204d751c6658dfd1ec4205d33fb231a0
|
|
BLAKE2b-256 checksum How to use checksums |
7384d6ea9915df8ddd4946fbce0897f5460c46b75cc39eda36c899da0becaf41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / open61850-0.2.0-py3-none-any.whl
| Download URL | open61850-0.2.0-py3-none-any.whl |
|---|---|
| Size | 69.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ab03521ae0c476b28d64d79163e43000dbf2df3ad6f39d195180299baab2157b
|
|
BLAKE2b-256 checksum How to use checksums |
3d9b3c6e258e94040fabf2615e06af91b9719572ca9260ae4d2069213d12f1e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log