Two beans that turn a stream of waveform into the file a protection engineer opens after a fault:
DisturbanceRecorder keeps the samples on both sides of an event, and ComtradeWriter writes
them out as an IEEE C37.111 (COMTRADE) configuration and data file pair.
They are source-agnostic, which is why they live here beside ComtradeParser rather than in a
driver. The waveform may come from an IEC 61850 Sampled Values subscription, from a COMTRADE file
being played back, or from anything else that produces records; the trigger may be a GOOSE
subscriber's trip signal, a detector's flag, or an operator's button. Neither bean knows which,
which is what lets the same diagram be tested from a file and deployed against a merging unit.
waveform -> TimeSeriesWindowBuffer -> DisturbanceRecorder -> ComtradeWriter -> a .cfg/.dat pair
^
trigger
The window comes first and that is the whole point. The half of a disturbance record that matters most is the half from before the trigger: what the current was doing in the cycles leading up to the trip is what says whether the relay was right. A recorder that started collecting when something happened would have missed it.
| Inport | What it takes |
|---|---|
window | the rolling window, an array of record chunks, from TimeSeriesWindowBuffer |
trigger | anything true and of good quality |
| Property | What it does |
|---|---|
PostTriggerSeconds | how much waveform to keep after the trigger |
RetriggerHoldOff | how long after a record ends before another may start |
MaxRows | a guard rail: a stuck trigger must not exhaust the heap |
How much is kept before the trigger is not a property here: it is however much the window upstream holds, which is where it belongs. One buffer can feed a recorder, a spectrum and a chart, and each of them wanting its own pre-fault period would mean three copies of the same samples.
A second trigger during a record extends it rather than starting another. A fault is rarely one event – a trip, a reclose, a second trip – and three records with the fault split across them is worse than one record of the whole sequence. The hold-off is what stops a chattering trigger filling a disk.
A trigger whose value is of bad quality does not record. The commonest way one arrives is a detector whose own input went away, and a recorder that wrote a file every time a link dropped is a recorder somebody turns off.
The emitted record carries TRIGGER_TIME, TRIGGER_SOURCE, TRIGGER_MICROS (the trigger's
offset into the record, which is where a chart puts its marker), PRE_TRIGGER_ROWS and
TRIGGER_COUNT.
Rows are ordered by the timestamp on each row's first cell, and anything not newer than the last row collected is dropped – a rolling window re-delivers what it still holds, so that is the normal case. It means a producer must date its rows with an instant rather than with milliseconds: a merging unit samples every 250 microseconds, and four rows in every five sharing a millisecond is four rows in every five discarded.
| Inport | What it takes |
|---|---|
record | a record, from the recorder or from anywhere else |
The outport carries the path of the .cfg written, with the .dat path and the sample count on
it as properties.
Almost nothing has to be configured, because a record describes most of itself: channel names come
from the column names, units and phases from the properties on row 0's cells – which is where
ComtradeParser puts them and where anything building a record for this writer should put them –
and times from each row's own timestamp. StationName, DeviceId and LineFrequency fill in
what a record from outside COMTRADE cannot carry, and the record's own values win where it has them.
BaseName left empty names the files after when the record was triggered, which is what a
recorder writing a file per fault wants.
Which columns are status channels rather than analogue ones is stated by the record where it says
(DN or AN on the description cell) and inferred where it does not: a column of nothing but
zeroes and ones is a contact. That is right almost always and wrong for a tap position that did not
move during the record; putting AN on the column is the cure.
Sixteen bits. COMTRADE carries samples as 16-bit integers with a scale and an offset per channel. Whole numbers inside that range are written exactly; anything else has the channel's range spread across the bits, which quantises it to a sixty-five-thousandth of that range. That is the format rather than the writer – a COMTRADE file of floating-point samples does not exist.
The revision written is 1999. The 2013 revision's configuration adds four lines describing the time source – the zone, whether it is local, the quality of the clock, whether a leap second was in progress – and a record assembled from a merging unit's stream or from a playback has nothing trustworthy to say in any of them. A 1999 file is read by every tool, including the ones that read 2013.
ComtradeWriterTest writes records and reads them back with ComtradeParser, which was written
first, against the standard and against records from real devices, and does not know the writer
exists. A test that read the files with code written beside the writer would prove only that the two
agreed with each other about a mistake.
DisturbanceRecorderTest drives the recorder the way a diagram does. FaultRecordingIT, in
Drivers/Iec61850, is the whole chain: a fault played out as Sampled Values, subscribed,
windowed, triggered, recorded, written, and parsed back to the same samples.