====== Disturbance recording ====== ===== Overview ===== 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. ===== The chain ===== 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. ===== DisturbanceRecorder ===== ^ 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. ===== ComtradeWriter ===== ^ 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. ===== What is tested ===== ''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.