← Articles
Networking/7 min read/ views

Subscribing to IEC 61850 GOOSE in SCADA Without Missing a State Change

How GOOSE stNum/sqNum counters, allowedToLive and ConfRev decide whether SCADA sees a breaker trip in milliseconds or never — and proving it is alive.

NetworkingSCADATroubleshootingCommissioning

The first GOOSE subscription I ever debugged looked perfectly healthy on the engineering laptop and was completely dead at the SCADA gateway. Same substation LAN, same publisher, same dataset. The IED's own tool decoded every message; our gateway showed the breaker position frozen at its power-on value. The difference turned out to be one VLAN tag the gateway's NIC was quietly stripping — but I spent half a day getting there because GOOSE gives you almost no feedback when it fails. It just goes quiet, and quiet looks exactly like "nothing has changed."

That's the thing to internalize before you subscribe to a single GOOSE message: unlike a poll or an MMS report, GOOSE has no request, no confirmation, no session. The publisher blasts a multicast Ethernet frame and forgets about it. If you don't receive it, nobody knows. So the entire job of a GOOSE subscriber is to distinguish "the value genuinely hasn't changed" from "I stopped hearing the publisher," and to do it fast enough that a missed trip signal actually matters.

The two counters do all the work

Every GOOSE frame carries two numbers that most people gloss over and then regret: stNum (state number) and sqNum (sequence number). They are the whole protocol, really.

sqNum increments on every retransmission while nothing changes. The publisher isn't sending one frame and going silent — it re-sends the same state on a stretching interval so a subscriber that just joined, or missed a frame, catches up. stNum increments only when a value in the dataset actually changes, and when it does, sqNum resets to 0. So the pattern on the wire for a stable breaker is:

stNum=41 sqNum=0   (breaker just opened)
stNum=41 sqNum=1
stNum=41 sqNum=2
stNum=41 sqNum=3   ... interval growing toward the max

and the instant it trips again:

stNum=42 sqNum=0   (new state — sent immediately, then bursts)
stNum=42 sqNum=1
stNum=42 sqNum=2

A subscriber that only watches the payload value is throwing away the most useful diagnostic in the frame. Watch stNum and you know a real state change happened. Watch sqNum and you know retransmission is running. If stNum jumps by more than one between two frames you received, you missed a state — the value you're holding may have gone open→closed→open and you only saw the endpoints. That's the kind of thing that never shows up in the tag value but absolutely shows up in an event-sequence review after an incident.

allowedToLive is your only liveness check

The other field that earns its keep is timeAllowedtoLive (TAL), carried in every frame. It's the publisher telling you: if you don't hear from me again within this many milliseconds, assume I'm gone. On a fresh state change the publisher sends TAL small — matching the fast burst — and grows it as sqNum climbs, until it settles at the steady-state value, typically twice the maximum retransmission interval.

Your subscriber must arm a timer for TAL on every received frame and, if it expires without a new frame, mark the subscription stale and drive the associated tags to bad quality. This is not optional. A GOOSE subscription with no TAL supervision is a status tag that silently lies the moment the cable is pulled. I've seen SCADA screens show a happily "closed" breaker for twenty minutes after the publishing IED was pulled for maintenance, because nobody wired up the timeout. The value was the last one heard, quality was good, and it was worthless.

The steady-state max interval is usually 1000–2000 ms, so TAL in the quiet state lands around 2000–4000 ms. Size your stale detection off the received TAL, not a hardcoded number — different publishers configure it differently, and a good subscriber respects whatever the frame says.

Where subscriptions actually die

When a GOOSE subscription doesn't work, it's rarely the payload. In rough order of how often I've hit them:

VLAN and priority tagging. GOOSE frames are almost always 802.1Q-tagged, carrying a VLAN ID and a priority (PCP), because the whole point is to jump the queue on a busy substation LAN — priority 4 is the IEC 61850 default for GOOSE/SV. If any switch port, NIC, or virtual-switch config in the path strips or filters that VLAN, the frame vanishes before your stack ever sees it. This was my breaker story. Capture at the gateway port with Wireshark and confirm the tag is present; if the engineering laptop sees tagged frames and the gateway doesn't, the problem is between them, not in the config.

APPID / MAC address mismatch. GOOSE is delivered to a multicast MAC in the 01-0C-CD-01-00-00 range and identified by a 16-bit APPID. Your subscriber filters on both. If the SCL (the SCD file) you imported doesn't match what the publisher was actually configured with — someone changed the APPID after export — you'll subscribe to a mailbox nobody posts to. Cross-check the APPID and destination MAC in the live capture against your subscription config, not against the SCD you were handed.

ConfRev mismatch. The GoCB carries a configuration revision number, confRev. Change the dataset — add a member, reorder, change a type — and confRev bumps. A careful subscriber verifies confRev matches what it was engineered against and refuses the data if it doesn't, because the positions in the dataset are all you have; GOOSE members are decoded by order, not by name. If someone inserts a new boolean at the front of the dataset and your subscriber doesn't check confRev, every subsequent member shifts and your breaker status is now reading what used to be an alarm bit. This is the failure mode that's dangerous rather than merely annoying — it produces confident wrong values.

The simulation/test bit. IEC 61850-8-1 Edition 2 added a simulation flag (the older Ed1 name was test) in the frame header. A test set injecting simulated GOOSE sets it true; a subscriber that honors simulation will ignore real traffic once it's been told to accept simulated, and vice versa. During commissioning this bites people constantly — the test injector's frames are marked simulated, the subscriber isn't in simulation mode, so nothing is accepted and it looks like the subscription is broken. Know which mode your subscriber is in.

Proving it's alive, not just quiet

Because a healthy idle subscription and a dead one both show a static value, build the liveness in and put it on a screen:

  • Expose stNum, sqNum, and a subscription-alive boolean (driven by the TAL timer) as tags. Operators don't need them, but engineers do the first time something looks wrong.
  • Alarm on the alive boolean going false, not on the payload. A GOOSE feed dropping is a comms event, and it should be handled like a dead poll — bad quality on the derived status, a low-priority alarm, and a clear indication on the screen that the value is stale rather than "closed."
  • During commissioning, force a state change (operate the breaker in test, toggle the source) and confirm stNum increments at both ends. If stNum moves at the publisher but your sqNum-reset-to-0 frame never arrives at the subscriber, you've lost the initial burst — usually loss or a switch that isn't prioritizing correctly under load.
  • Watch for sqNum gaps under load. Occasional single-frame loss is survivable because of retransmission, but if you're routinely missing the sqNum=0 burst frame you'll add latency to every real event, which defeats the entire reason you chose GOOSE over polling.

One more habit worth keeping: capture a few minutes of the GOOSE traffic during commissioning and archive the pcap with the project. When someone changes a dataset a year later and a status tag starts reading garbage, the fastest way to prove the confRev bumped is to diff the live frame against that baseline. GOOSE won't tell you it changed — you have to have written down what "correct" looked like.