IEC 61850 MMS client in C: the five steps, and where people get stuck
Writing an MMS client looks like a large task until you cut it into the sequence it actually is: get the model, connect, find the variables you care about, subscribe, then sit in a loop handling what arrives. The difficulty is never the happy path — it is the four things that only happen on site: the peer restarts, the report buffer overflows, the control is rejected, and the network disappears halfway through a dataset.
Step 1 — get the model, not the addresses
Everything downstream depends on knowing the model: which logical devices, which logical nodes, which data objects and which functional constraints you may read. Configure it from the SCL file at build time and read it from the device at connect time; a client that hard-codes object references works exactly until the customer sends a new CID.
The one rule that saves the most time: address variables by LD/LN.DO.DA[FC] with the functional constraint attached, and keep that string as your internal key. Every mismatch between the file and the device shows up as a failed read with a clear reference instead of a mystery.
Step 2 — associate, with a timeout that matches reality
Association is where a client first meets the network. Set an explicit connect timeout, an explicit request timeout, and a keep-alive. The default values that come with a lab setup are almost always too aggressive for a substation LAN with an ageing switch.
Step 3 — browse and build your point list
Two approaches, and both are legitimate: discover at runtime (GetNameList over logical devices, then data objects), or configure from SCL and verify at connect. In production do the second and use the first as a diagnostic tool, because runtime discovery of a 200-IED system is slow and produces ordering that varies between runs.
Step 4 — enable reporting
This is the step that separates a demo from a product. Configure or create the dataset, configure or create the report control block, set the trigger options and the optional fields, enable it, and send a general interrogation so you start with a consistent snapshot rather than whatever happens next.
Our own published claim is that enabling reporting takes no more than five lines of code in our client SDK — that is a statement about API shape, and it is only useful if you can still reach the underlying control when the customer wants something the helper does not do.
Step 5 — the indication loop
An illustrative shape, deliberately API-agnostic:
- Never block inside the indication handler. A handler that reads a file synchronously stops report processing, and the peer's buffer starts filling up.
- Handle unknown indications by logging the service tag and continuing. A client that aborts the association on an unexpected message will fail on every third vendor.
for (;;) {
r = mms_poll(&conn, 100 /* ms */);
if (r == MMS_TIMEOUT) { heartbeat(&conn); continue; }
if (r != MMS_OK) { reconnect(&conn); continue; }
while (mms_next_indication(&conn, &ind)) {
switch (ind.kind) {
case IND_REPORT: apply_report(&ind); break;
case IND_WRITE: apply_peer_write(&ind); break;
case IND_COMMAND: handle_command(&ind); break;
case IND_FILE: on_file_chunk(&ind); break;
}
}
}
The four things that only happen on site
Build these paths before you need them, because debugging them from a customer site is expensive:
- Peer restart. The association drops, and when it comes back the report control block may be disabled or re-owned. Re-enable reporting and re-issue GI on every reconnect — not on first connect only.
- Buffer overflow. A buffered report control block that overflows loses entries. Detect it via the reported counters and the discontinuity flag, and re-synchronise with GI instead of pretending the stream was continuous.
- Rejected control. Every control can be refused: wrong state, interlock, select timeout, mode. Map the negative response into something an operator can read, and never report success because the request was sent.
- Half-dead link. TCP stays up, the peer stops answering. Keep-alive at the MMS layer, not only at TCP, and treat two consecutive missed keep-alives as a dead association.
Questions that come up
Should the client read the whole model at startup?
Only what it needs. Reading everything costs seconds per device and produces a point list that changes when the vendor ships new firmware. Read the branches you subscribe to, and keep a full browse as a maintenance action.
How do we handle a device that does not support report control block creation?
Use the configured ones from its SCL file. Check the Services block before designing: if the device declares zero creatable datasets, creation is not an option and the design must be configuration-driven from the start.
Is polling ever acceptable instead of reporting?
For a handful of slow-changing points, yes. As a general strategy it does not scale — polling 10,000 attributes is a bandwidth and CPU problem that reporting was designed to solve.
Keep reading
Related products
StationCore
Bring up to 200 IEDs into your master station or gateway with a few lines per device.
Substation Checker
Simulate one IED or two hundred, then see whether your master station keeps up.
Ask about your specific case
Describe the device, the platform and the acceptance criteria. You get an answer from an engineer, not a form letter.