971 lines · 49.9 KB · C#
RawDownload
//******************************************************************************************************//  Writer.cs - Gbtc////  Copyright © 2010, Grid Protection Alliance.  All Rights Reserved.////  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See//  the NOTICE file distributed with this work for additional information regarding copyright ownership.//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may//  not use this file except in compliance with the License. You may obtain a copy of the License at:////      http://www.opensource.org/licenses/MIT////  Unless agreed to in writing, the subject software distributed under the License is distributed on an//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the//  License for the specific language governing permissions and limitations.////  Code Modification History://  ----------------------------------------------------------------------------------------------------//  06/19/2013 - J. Ritchie Carroll//       Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Globalization;using System.IO;using System.Linq;using System.Text;using GSF.Units.EE; namespace GSF.COMTRADE{    /// <summary>    /// COMTRADE data and config file writer.    /// </summary>    public static class Writer    {        /// <summary>        /// Defines the maximum file size for this COMTRADE implementation.        /// </summary>        /// <remarks>        /// Value represents 256TB, arbitrarily chosen extreme maximum file size.        /// </remarks>        public const long MaxFileSize = 281474976710656L;         private static readonly string MaxByteCountString = new string('0', $"{MaxFileSize}".Length);         /// <summary>        /// Defines the maximum COMTRADE end sample number.        /// </summary>        public const long MaxEndSample = 9999999999L;         /// <summary>        /// Defines a carriage return and line feed constant, i.e., <c>"\r\n"</c>.        /// </summary>        /// <remarks>        /// The standard .NET <see cref="Environment.NewLine"/> constant can just be a line feed, <c>"\n"</c>,        /// on some operating systems, e.g., Linux, however the COMTRADE standard requires that end of line        /// markers be both a carriage return and line feed, <c>"\r\n"</c>.        /// </remarks>        public const string CRLF = "\r\n";         /// <summary>        /// Creates a new COMTRADE configuration <see cref="Schema"/>.        /// </summary>        /// <param name="metadata">Schema <see cref="ChannelMetadata"/> records.</param>        /// <param name="stationName">Station name for the schema.</param>        /// <param name="deviceID">Device ID for the schema.</param>        /// <param name="dataStartTime">Data start time.</param>        /// <param name="sampleCount">Total data samples (i.e., total number of rows).</param>        /// <param name="version">Target schema version - defaults to 1999.</param>        /// <param name="fileType">Determines the data file type for the schema.</param>        /// <param name="timeFactor">Time factor to use in schema - defaults to 1000.</param>        /// <param name="samplingRate">Desired sampling rate - defaults to 33.3333Hz.</param>        /// <param name="nominalFrequency">Nominal frequency - defaults to 60Hz.</param>        /// <param name="includeFracSecDefinition">Determines if the FRACSEC word digital definitions should be included - defaults to <c>true</c>.</param>        /// <returns>New COMTRADE configuration <see cref="Schema"/>.</returns>        /// <remarks>        /// <para>        /// This function is primarily intended to create a configuration based on synchrophasor data        /// (see Annex H: Schema for Phasor Data 2150 Using the COMTRADE File Standard in IEEE C37.111-2010),        /// it may be necessary to manually create a schema object for other COMTRADE needs. You can call        /// the <see cref="Schema.FileImage"/> property to return a string that can be written to a file        /// that will be the contents of the configuration file.        /// </para>        /// <para>        /// Linear scaling factors for analog channels, i.e., adders and multipliers, will be set to reasonable        /// values based on the channel type. These should be adjusted as needed based on actual channel value        /// ranges. Note that for <see cref="FileType.Float32"/> the multiplier will be <c>1.0</c> and the adder        /// will be <c>0.0</c> for all analog values.        /// </para>        /// </remarks>        public static Schema CreateSchema(IEnumerable<ChannelMetadata> metadata, string stationName, string deviceID, Ticks dataStartTime, long sampleCount, int version = 1999, FileType fileType = FileType.Binary, double timeFactor = 1.0D, double samplingRate = 30.0D, double nominalFrequency = 60.0D, bool includeFracSecDefinition = true)        {            Schema schema = new Schema            {                StationName = stationName,                DeviceID = deviceID,                Version = version            };             SampleRate samplingFrequency = new SampleRate            {                Rate = samplingRate,                EndSample = sampleCount            };             schema.SampleRates = new[] { samplingFrequency };             Timestamp startTime;            startTime.Value = dataStartTime;            schema.StartTime = startTime;            schema.TriggerTime = startTime;             schema.FileType = fileType;            schema.TimeFactor = timeFactor;             List<AnalogChannel> analogChannels = new List<AnalogChannel>();            List<DigitalChannel> digitalChannels = new List<DigitalChannel>();            bool targetFloatingPoint = fileType == FileType.Ascii || fileType == FileType.Float32;             int analogIndex = 1;            int digitalIndex = 1;             if (includeFracSecDefinition)            {                // Add default time quality digitals for IEEE C37.118 FRACSEC word. Note that these flags, as                // defined in Annex H of the IEEE C37.111-2010 standard, assume full export was all from one                // source device. This a poor assumption since data can be exported from historical data for any                // number of points which could have come from any number of devices all with different FRACSEC                // values. Regardless there is only one FRACSEC definition defined and, if included, it must                // come as the first set of digitals in the COMTRADE configuration.                for (int i = 0; i < 4; i++)                {                    digitalChannels.Add(new DigitalChannel(schema.Version)                    {                        Index = digitalIndex,                        Name = "TQ_CNT" + i,                        PhaseID = "T" + digitalIndex++                    });                }                 digitalChannels.Add(new DigitalChannel(schema.Version)                {                    Index = digitalIndex,                    Name = "TQ_LSPND",                    PhaseID = "T" + digitalIndex++                });                  digitalChannels.Add(new DigitalChannel(schema.Version)                {                    Index = digitalIndex,                    Name = "TQ_LSOCC",                    PhaseID = "T" + digitalIndex++                });                 digitalChannels.Add(new DigitalChannel(schema.Version)                {                    Index = digitalIndex,                    Name = "TQ_LSDIR",                    PhaseID = "T" + digitalIndex++                });                 digitalChannels.Add(new DigitalChannel(schema.Version)                {                    Index = digitalIndex,                    Name = "RSV",                    PhaseID = "T" + digitalIndex++                });                 for (int i = 1; i < 9; i++)                {                    digitalChannels.Add(new DigitalChannel(schema.Version)                    {                        Index = digitalIndex,                        Name = "RESV" + i,                        PhaseID = "T" + digitalIndex++                    });                }            }             // Add meta data for selected points sorted by analogs followed by status flags then digitals            foreach (ChannelMetadata record in metadata.OrderBy(m => m, ChannelMetadataSorter.Default))            {                if (record.IsDigital)                {                    switch (record.SignalType)                    {                        case SignalType.FLAG: // Status flags                            // Add synchrophasor status flag specific digitals                            int statusIndex = 0;                             for (int i = 1; i < 5; i++)                            {                                digitalChannels.Add(new DigitalChannel(schema.Version)                                {                                    Index = digitalIndex++,                                    Name = record.Name + ":TRG" + i,                                    PhaseID = "S" + statusIndex++.ToString("X")                                });                            }                             for (int i = 1; i < 3; i++)                            {                                digitalChannels.Add(new DigitalChannel(schema.Version)                                {                                    Index = digitalIndex++,                                    Name = record.Name + ":UNLK" + i,                                    PhaseID = "S" + statusIndex++.ToString("X")                                });                            }                             for (int i = 1; i < 5; i++)                            {                                digitalChannels.Add(new DigitalChannel(schema.Version)                                {                                    Index = digitalIndex++,                                    Name = record.Name + ":SEC" + i,                                    PhaseID = "S" + statusIndex++.ToString("X")                                });                            }                             digitalChannels.Add(new DigitalChannel(schema.Version)                            {                                Index = digitalIndex++,                                Name = record.Name + ":CFGCH",                                PhaseID = "S" + statusIndex++.ToString("X")                            });                             digitalChannels.Add(new DigitalChannel(schema.Version)                            {                                Index = digitalIndex++,                                Name = record.Name + ":PMUTR",                                PhaseID = "S" + statusIndex++.ToString("X")                            });                             digitalChannels.Add(new DigitalChannel(schema.Version)                            {                                Index = digitalIndex++,                                Name = record.Name + ":SORT",                                PhaseID = "S" + statusIndex++.ToString("X")                            });                             digitalChannels.Add(new DigitalChannel(schema.Version)                            {                                Index = digitalIndex++,                                Name = record.Name + ":SYNC",                                PhaseID = "S" + statusIndex++.ToString("X")                            });                             digitalChannels.Add(new DigitalChannel(schema.Version)                            {                                Index = digitalIndex++,                                Name = record.Name + ":PMUERR",                                PhaseID = "S" + statusIndex++.ToString("X")                            });                             digitalChannels.Add(new DigitalChannel(schema.Version)                            {                                Index = digitalIndex++,                                Name = record.Name + ":DTVLD",                                PhaseID = "S" + statusIndex.ToString("X")                            });                            break;                        default:                            // Every synchrophasor digital is 16-bits                            for (int i = 0; i < 16; i++)                            {                                digitalChannels.Add(new DigitalChannel(schema.Version)                                {                                    Index = digitalIndex++,                                    Name = record.Name,                                    PhaseID = "B" + i.ToString("X"),                                    CircuitComponent = record.CircuitComponent                                });                            }                            break;                    }                }                else                {                    switch (record.SignalType)                    {                        case SignalType.IPHM: // Current Magnitude                            analogChannels.Add(new AnalogChannel(schema.Version, targetFloatingPoint)                            {                                Index = analogIndex++,                                Name = record.Name,                                Units = record.Units ?? "A",                                PhaseID = "Pm",                                CircuitComponent = record.CircuitComponent,                                Multiplier = targetFloatingPoint ? 1.0D : AnalogChannel.DefaultCurrentMagnitudeMultiplier                            });                            break;                        case SignalType.VPHM: // Voltage Magnitude                            analogChannels.Add(new AnalogChannel(schema.Version, targetFloatingPoint)                            {                                Index = analogIndex++,                                Name = record.Name,                                Units = record.Units ?? "V",                                PhaseID = "Pm",                                CircuitComponent = record.CircuitComponent,                                Multiplier = targetFloatingPoint ? 1.0D : AnalogChannel.DefaultVoltageMagnitudeMultiplier                            });                            break;                        case SignalType.IPHA: // Current Phase Angle                        case SignalType.VPHA: // Voltage Phase Angle                            analogChannels.Add(new AnalogChannel(schema.Version, targetFloatingPoint)                            {                                Index = analogIndex++,                                Name = record.Name,                                Units = record.Units ?? "Rads",                                PhaseID = "Pa",                                CircuitComponent = record.CircuitComponent,                                Multiplier = targetFloatingPoint ? 1.0D : AnalogChannel.DefaultPhaseAngleMultiplier                            });                            break;                        case SignalType.FREQ: // Frequency                            analogChannels.Add(new AnalogChannel(schema.Version, targetFloatingPoint)                            {                                Index = analogIndex++,                                Name = record.Name,                                Units = record.Units ?? "Hz",                                PhaseID = "F",                                CircuitComponent = record.CircuitComponent,                                Adder = targetFloatingPoint ? 0.0D : nominalFrequency,                                Multiplier = targetFloatingPoint ? 1.0D : AnalogChannel.DefaultFrequencyMultiplier                            });                            break;                        case SignalType.DFDT: // Frequency Delta (dF/dt)                            analogChannels.Add(new AnalogChannel(schema.Version, targetFloatingPoint)                            {                                Index = analogIndex++,                                Name = record.Name,                                Units = record.Units ?? "Hz/s",                                PhaseID = "dF",                                CircuitComponent = record.CircuitComponent,                                Multiplier = targetFloatingPoint ? 1.0D : AnalogChannel.DefaultDfDtMultiplier                            });                            break;                        default:     // All other signals assumed to be analog values                            analogChannels.Add(new AnalogChannel(schema.Version, targetFloatingPoint)                            {                                Index = analogIndex++,                                Name = record.Name,                                PhaseID = "",                                Units = record.Units,                                CircuitComponent = record.CircuitComponent,                                Multiplier = targetFloatingPoint ? 1.0D : AnalogChannel.DefaultAnalogMultipler                            });                            break;                    }                }            }             schema.AnalogChannels = analogChannels.ToArray();            schema.DigitalChannels = digitalChannels.ToArray();            schema.NominalFrequency = nominalFrequency;             return schema;        }         /// <summary>        /// Creates a new Combined File Format (.cff) COMTRADE file stream.        /// </summary>        /// <param name="fileName">Target file name. Must have ".cff" extension.</param>        /// <param name="schema">Schema of file stream.</param>        /// <param name="infLines">Lines of "INF" section to write to stream, if any.</param>        /// <param name="hdrLines">Lines of "HDR" section to write to stream, if any.</param>        /// <param name="encoding">Target encoding; <c>null</c> value will default to UTF-8 (no BOM).</param>        /// <returns>New file stream for Combined File Format (.cff) COMTRADE file, ready to write at data section.</returns>        public static FileStream CreateCFFStream(string fileName, Schema schema, string[] infLines = null, string[] hdrLines = null, Encoding encoding = null) =>             CreateCFFStream(fileName, schema, infLines, hdrLines, encoding, out _);         /// <summary>        /// Creates a new Combined File Format (.cff) COMTRADE file stream targeted for ASCII.        /// </summary>        /// <param name="fileName">Target file name. Must have ".cff" extension.</param>        /// <param name="schema">Schema of file stream.</param>        /// <param name="infLines">Lines of "INF" section to write to stream, if any.</param>        /// <param name="hdrLines">Lines of "HDR" section to write to stream, if any.</param>        /// <param name="encoding">Target encoding; <c>null</c> value will default to UTF-8 (no BOM).</param>        /// <returns>New stream writer for Combined File Format (.cff) COMTRADE file, ready to write at data section.</returns>        /// <remarks>        /// For COMTRADE versions greater than 2001, any use of the term ASCII also inherently implies Unicode UTF-8.        /// When then <paramref name="encoding"/> parameter is <c>null</c>, the default, UTF-8 encoding will be used        /// for text writes. If ASCII encoding needs to be enforced for backwards compatibility reasons, then the        /// <paramref name="encoding"/> parameter will need to be set to <see cref="Encoding.ASCII"/>.        /// </remarks>        public static StreamWriter CreateCFFStreamAscii(string fileName, Schema schema, string[] infLines = null, string[] hdrLines = null, Encoding encoding = null)        {            if (schema.FileType != FileType.Ascii)                throw new ArgumentException($"Cannot create ASCII CFF stream using schema targeted for {schema.FileType.ToString().ToUpperInvariant()}", nameof(schema));             CreateCFFStream(fileName, schema, infLines, hdrLines, encoding, out StreamWriter writer);            return writer;        }         private static FileStream CreateCFFStream(string fileName, Schema schema, string[] infLines, string[] hdrLines, Encoding encoding, out StreamWriter writer)        {            if (fileName is null)                throw new ArgumentNullException(nameof(fileName));             if (!Schema.HasCFFExtension(fileName))                throw new ArgumentException("Specified file name is not using standard Combined File Format COMTRADE file extension: \".cff\".", nameof(fileName));             FileStream stream = File.Create(fileName);             CreateCFFStream(stream, schema, infLines, hdrLines, encoding, out writer);             return stream;        }         /// <summary>        /// Creates a new Combined File Format (.cff) COMTRADE file stream.        /// </summary>        /// <param name="stream">Target stream.</param>        /// <param name="schema">Schema of file stream.</param>        /// <param name="infLines">Lines of "INF" section to write to stream, if any.</param>        /// <param name="hdrLines">Lines of "HDR" section to write to stream, if any.</param>        /// <param name="encoding">Target encoding; <c>null</c> value will default to UTF-8 (no BOM).</param>        public static void CreateCFFStream(Stream stream, Schema schema, string[] infLines = null, string[] hdrLines = null, Encoding encoding = null) =>            CreateCFFStream(stream, schema, infLines, hdrLines, encoding, out _);         /// <summary>        /// Creates a new Combined File Format (.cff) COMTRADE file stream targeted for ASCII.        /// </summary>        /// <param name="stream">Target stream.</param>        /// <param name="schema">Schema of file stream.</param>        /// <param name="infLines">Lines of "INF" section to write to stream, if any.</param>        /// <param name="hdrLines">Lines of "HDR" section to write to stream, if any.</param>        /// <param name="encoding">Target encoding; <c>null</c> value will default to UTF-8 (no BOM).</param>        /// <returns>New stream writer for Combined File Format (.cff) COMTRADE file, ready to write at data section.</returns>        /// <remarks>        /// For COMTRADE versions greater than 2001, any use of the term ASCII also inherently implies Unicode UTF-8.        /// When then <paramref name="encoding"/> parameter is <c>null</c>, the default, UTF-8 encoding will be used        /// for text writes. If ASCII encoding needs to be enforced for backwards compatibility reasons, then the        /// <paramref name="encoding"/> parameter will need to be set to <see cref="Encoding.ASCII"/>.        /// </remarks>        public static StreamWriter CreateCFFStreamAscii(Stream stream, Schema schema, string[] infLines = null, string[] hdrLines = null, Encoding encoding = null)        {            if (schema.FileType != FileType.Ascii)                throw new ArgumentException($"Cannot create ASCII CFF stream using schema targeted for {schema.FileType.ToString().ToUpperInvariant()}", nameof(schema));             CreateCFFStream(stream, schema, infLines, hdrLines, encoding, out StreamWriter writer);            return writer;        }         private static void CreateCFFStream(Stream stream, Schema schema, string[] infLines, string[] hdrLines, Encoding encoding, out StreamWriter writer)        {            if (stream is null)                throw new ArgumentNullException(nameof(stream));             if (schema is null)                throw new ArgumentNullException(nameof(schema));             if (schema.Version < 2013)                throw new ArgumentException("Minimum COMTRADE version for a Combined File Format (.cff) file is 2013", nameof(schema));             writer = new StreamWriter(stream, encoding ?? new UTF8Encoding(false)) { NewLine = CRLF };             writer.WriteLine("--- file type: CFG ---");            writer.WriteLine(schema.FileImage);             writer.WriteLine("--- file type: INF ---");            writer.WriteLine(string.Join(CRLF, infLines ?? Array.Empty<string>()));             writer.WriteLine("--- file type: HDR ---");            writer.WriteLine(string.Join(CRLF, hdrLines ?? Array.Empty<string>()));             // Reserve space for binary byte count            writer.WriteLine($"--- file type: DAT {(schema.FileType == FileType.Ascii ? "ASCII" : $"BINARY: {MaxByteCountString}")} ---");             // Do not dispose writer as this will dispose base stream            writer.Flush();        }         /// <summary>        /// Updates a Combined File Format (.cff) COMTRADE file stream with a final end sample number.        /// </summary>        /// <param name="stream">Destination stream.</param>        /// <param name="endSample">End sample value.</param>        /// <param name="rateIndex">Zero-based rate index to update.</param>        /// <param name="encoding">Target encoding; <c>null</c> value will default to UTF-8 (no BOM).</param>        public static void UpdateCFFEndSample(Stream stream, long endSample, int rateIndex = 0, Encoding encoding = null)        {            if (endSample > MaxEndSample)                throw new ArgumentOutOfRangeException(nameof(endSample), $"Max end sample for COMTRADE is {MaxEndSample:N0}");             if (rateIndex < 0)                throw new ArgumentOutOfRangeException(nameof(rateIndex), "Rate index cannot be a negative value");             stream.Position = 0;             // Do not dispose stream reader - this would dispose base stream            StreamReader fileReader = new StreamReader(stream);            Encoding utf8 = new UTF8Encoding(false);            string lastLine = null;

Lines 1–500 of 971.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t