650 lines · 23.5 KB · C#
RawDownload
//******************************************************************************************************//  Parser.cs - Gbtc////  Copyright © 2012, 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/17/2012 - J. Ritchie Carroll//       Generated original version of source code.//  12/13/2012 - Starlynn Danyelle Gilliam//       Modified Header.////****************************************************************************************************** using System;using System.IO;using System.Linq;using System.Text;using System.Text.RegularExpressions;using GSF.IO;using GSF.Units;using GSF.Units.EE; namespace GSF.COMTRADE{    /// <summary>    /// COMTRADE data file(s) parser.    /// </summary>    public class Parser : IDisposable    {        #region [ Members ]         // Fields        private Schema m_schema;        private string m_fileName;        private FileStream[] m_fileStreams;        private StreamReader[] m_fileReaders;        private int m_streamIndex;        private double[] m_primaryValues;        private double[] m_secondaryValues;        private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Releases the unmanaged resources before the <see cref="Parser"/> object is reclaimed by <see cref="GC"/>.        /// </summary>        ~Parser() => Dispose(false);         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets associated COMTRADE schema for this <see cref="Parser"/>.        /// </summary>        /// <remarks>        /// Upon assignment, any <see cref="COMTRADE.Schema.FileName"/> value will be used to        /// initialize the parser's initial <see cref="FileName"/> property when an associated        /// data file that ends with ".cff", ".dat" or ".d00" is found to exist.        /// </remarks>        public Schema Schema        {            get => m_schema;            set            {                m_schema = value;                 if (m_schema is null)                {                    Values = null;                }                else                {                    if (m_schema.TotalChannels > 0)                        Values = new double[m_schema.TotalChannels];                    else                        throw new InvalidOperationException("Invalid schema: total channels defined in schema is zero.");                     if (m_schema.TotalSampleRates == 0)                        InferTimeFromSampleRates = false;                     // If no data file name is already defined, assume same file name for ".cff" files, otherwise,                    // attempt to find initial data file with same root file name as schema configuration file but                    // with a ".dat" or ".d00" extension:                    if (string.IsNullOrWhiteSpace(m_fileName) && !string.IsNullOrWhiteSpace(m_schema.FileName))                    {                        IsCombinedFileFormat = m_schema.IsCombinedFileFormat;                         if (IsCombinedFileFormat)                        {                            m_fileName = m_schema.FileName;                        }                        else                        {                            string directory = FilePath.GetDirectoryName(m_schema.FileName);                            string rootFileName = FilePath.GetFileNameWithoutExtension(m_schema.FileName);                            string dataFile1 = Path.Combine(directory, $"{rootFileName}.dat");                            string dataFile2 = Path.Combine(directory, $"{rootFileName}.d00");                             if (File.Exists(dataFile1))                                m_fileName = dataFile1;                            else if (File.Exists(dataFile2))                                m_fileName = dataFile2;                        }                    }                }            }        }         /// <summary>        /// Gets or sets COMTRADE data filename. If there are more than one data files in a set, this should be set to first file name in the set, e.g., DATA123.D00.        /// </summary>        public string FileName        {            get => m_fileName;            set            {                m_fileName = value;                IsCombinedFileFormat = Schema.HasCFFExtension(m_fileName);            }        }         /// <summary>        /// Gets flag that determines if <see cref="FileName"/> is a Combined File Format (.cff) file.        /// </summary>        public bool IsCombinedFileFormat { get; private set; }         /// <summary>        /// Gets the byte count as parsed from a Combined File Format (.cff) file targeted with binary format.        /// </summary>        public long BinaryByteCount { get; private set; }         /// <summary>        /// Gets or sets flag that determines if time should be inferred from sample rates.        /// </summary>        public bool InferTimeFromSampleRates { get; set; } = true;         /// <summary>        /// Gets timestamp of current record.        /// </summary>        public DateTime Timestamp { get; private set; }         /// <summary>        /// Gets values of current record.        /// </summary>        public double[] Values { get; private set; }         /// <summary>        /// Gets values of current record with secondary analog channels scaled to primary values.        /// </summary>        public double[] PrimaryValues        {            get            {                if (m_primaryValues is null)                {                    m_primaryValues = new double[Values.Length];                     for (int i = 0; i < m_primaryValues.Length; i++)                    {                        double value = Values[i];                         if (i < m_schema.AnalogChannels.Length)                        {                            if (char.ToUpper(m_schema.AnalogChannels[i].ScalingIdentifier) == 'S')                                value *= m_schema.AnalogChannels[i].PrimaryRatio / m_schema.AnalogChannels[i].SecondaryRatio;                        }                         m_primaryValues[i] = value;                    }                }                 return m_primaryValues;            }        }         /// <summary>        /// Gets values of current record with primary analog channels scaled to secondary values.        /// </summary>        public double[] SecondaryValues        {            get            {                if (m_secondaryValues is null)                {                    m_secondaryValues = new double[Values.Length];                     for (int i = 0; i < m_secondaryValues.Length; i++)                    {                        double value = Values[i];                         if (i < m_schema.AnalogChannels.Length)                        {                            if (char.ToUpper(m_schema.AnalogChannels[i].ScalingIdentifier) == 'P')                                value *= m_schema.AnalogChannels[i].SecondaryRatio / m_schema.AnalogChannels[i].PrimaryRatio;                        }                         m_secondaryValues[i] = value;                    }                }                 return m_secondaryValues;            }        }         /// <summary>        /// Gets or sets flag that determines if schemas that include a time code for a UTC offset        /// should apply the adjustment so that timestamps are parsed as UTC.        /// </summary>        public bool AdjustToUTC { get; set; } = true;         /// <summary>        /// Gets or sets target angle unit for any analog channels that are angles.        /// </summary>        /// <remarks>        /// When assigned, any encountered angles will be converted to the specified unit while parsing.        /// </remarks>        public AngleUnit? TargetAngleUnit { get; set; }         #endregion         #region [ Methods ]         /// <summary>        /// Releases all the resources used by the <see cref="Parser"/> object.        /// </summary>        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }         /// <summary>        /// Releases the unmanaged resources used by the <see cref="Parser"/> object and optionally releases the managed resources.        /// </summary>        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>        protected virtual void Dispose(bool disposing)        {            if (m_disposed)                return;             try            {                if (disposing)                    CloseFiles();            }            finally            {                m_disposed = true; // Prevent duplicate dispose.            }        }         /// <summary>        /// Opens all COMTRADE data file streams.        /// </summary>        public void OpenFiles()        {            if (string.IsNullOrWhiteSpace(FileName))                throw new InvalidOperationException("First COMTRADE data file name was not specified, cannot open files.");             if (!File.Exists(FileName))                throw new FileNotFoundException($"Specified COMTRADE data file \"{FileName}\" was not found, cannot open files.");             string[] fileNames;             if (IsCombinedFileFormat)            {                fileNames = new[] { FileName };            }            else            {                // Get all data files in the collection                const string FileRegex = @"(?:\.dat|\.d\d\d)$";                string directory = FilePath.GetDirectoryName(FileName);                string fileNamePattern = $"{FilePath.GetFileNameWithoutExtension(FileName)}.d*";                 fileNames = FilePath.GetFileList(Path.Combine(directory, fileNamePattern))                    .Where(fileName => Regex.IsMatch(fileName, FileRegex, RegexOptions.IgnoreCase))                    .OrderBy(fileName => fileName, StringComparer.OrdinalIgnoreCase)                    .ToArray();            }             // Create a new file stream for each file            m_fileStreams = new FileStream[fileNames.Length];             for (int i = 0; i < fileNames.Length; i++)                m_fileStreams[i] = new FileStream(fileNames[i], FileMode.Open, FileAccess.Read, FileShare.Read);             m_streamIndex = 0;             if (IsCombinedFileFormat)            {                // Scan ahead to data section (do not dispose stream reader - this would dispose base stream)                StreamReader fileReader = new StreamReader(m_fileStreams[0]);                Encoding utf8 = new UTF8Encoding(false);                long position = 0;                 do                {                    string line = fileReader.ReadLine();                     if (line is null)                        break;                     position += utf8.GetBytes(line).Length + 2;                     if (Schema.IsFileSectionSeparator(line, out string sectionType, out long byteCount) && sectionType.StartsWith("DAT"))                    {                        BinaryByteCount = byteCount;                        m_fileStreams[0].Position = position;                        break;                    }                }                while (true);            }        }         /// <summary>        /// Closes all COMTRADE data file streams.        /// </summary>        public void CloseFiles()        {            if (!(m_fileStreams is null))            {                foreach (FileStream fileStream in m_fileStreams)                    fileStream?.Dispose();            }             m_fileStreams = null;        }         /// <summary>        /// Reads next COMTRADE record.        /// </summary>        /// <returns><c>true</c> if read succeeded; otherwise, <c>false</c> if end of data set was reached.</returns>        public bool ReadNext()        {            if (m_fileStreams is null)                throw new InvalidOperationException("COMTRADE data files are not open, cannot read next record.");             if (m_schema is null)                throw new InvalidOperationException("No COMTRADE schema has been defined, cannot read records.");             if (m_streamIndex > m_fileStreams.Length)                throw new EndOfStreamException("All COMTRADE data has been read, cannot read more records.");             m_primaryValues = null;            m_secondaryValues = null;             switch (m_schema.FileType)            {                case FileType.Ascii:                    return ReadNextAscii();                case FileType.Binary:                    return ReadNextBinary();                case FileType.Binary32:                    return ReadNextBinary32();                case FileType.Float32:                    return ReadNextFloat32();                default:                    return false;            }        }         // Handle ASCII file read        private bool ReadNextAscii()        {            // For ASCII files, we wrap file streams with file readers            if (m_fileReaders is null)            {                m_fileReaders = new StreamReader[m_fileStreams.Length];                 for (int i = 0; i < m_fileStreams.Length; i++)                    m_fileReaders[i] = new StreamReader(m_fileStreams[i]);            }             // Read next line of record values            StreamReader reader = m_fileReaders[m_streamIndex];            string line = reader.ReadLine();            string[] elems = line?.Split(',');             // See if we have reached the end of this file            if (elems is null || elems.Length != Values.Length + 2)            {                if (reader.EndOfStream)                    return ReadNextFile();                 throw new InvalidOperationException("COMTRADE schema does not match number of elements found in ASCII data file.");            }             // Parse row of data            uint sample = uint.Parse(elems[0]);             // Get timestamp of this record            Timestamp = DateTime.MinValue;             // If sample rates are defined, this is the preferred method for timestamp resolution            if (InferTimeFromSampleRates && m_schema.SampleRates.Length > 0)            {                // Find rate for given sample                SampleRate sampleRate = m_schema.SampleRates.LastOrDefault(sr => sample <= sr.EndSample);                 if (sampleRate.Rate > 0.0D)                    Timestamp = new DateTime(Ticks.FromSeconds(1.0D / sampleRate.Rate * sample) + m_schema.StartTime.Value);            }             // Fall back on specified microsecond time            if (Timestamp == DateTime.MinValue)                Timestamp = new DateTime(Ticks.FromMicroseconds(double.Parse(elems[1]) * m_schema.TimeFactor) + m_schema.StartTime.Value);             // Apply timestamp offset to restore UTC timezone            if (AdjustToUTC)            {                TimeOffset offset = Schema.TimeCode ?? new TimeOffset();                Timestamp = new DateTime(Timestamp.Ticks + offset.TickOffset, DateTimeKind.Utc);            }             // Parse all record values            for (int i = 0; i < Values.Length; i++)            {                Values[i] = double.Parse(elems[i + 2]);                 if (i < m_schema.AnalogChannels.Length)                    Values[i] = AdjustValue(Values[i], i);            }             return true;        }         // Handle binary file read        private bool ReadNextBinary()        {            FileStream currentFile = m_fileStreams[m_streamIndex];            int recordLength = m_schema.BinaryRecordLength;            byte[] buffer = new byte[recordLength];             // Read next record from file            int bytesRead = currentFile.Read(buffer, 0, recordLength);             // See if we have reached the end of this file            if (bytesRead == 0)                return ReadNextFile();             if (bytesRead == recordLength)            {                int index = ReadTimestamp(buffer);                 index = ReadAnalogValues(buffer, index, ReadInt16, 2);                 ReadDigitalValues(buffer, index);            }            else            {                if (buffer[bytesRead - 1] == 0x1A)                    return ReadNextFile();                 throw new InvalidOperationException($"Failed to read enough bytes from COMTRADE BINARY file for a record as defined by schema - possible schema/data file mismatch or file corruption. Failed position = {currentFile.Position:N0}");            }             return true;        }          // Handle binary32 file read        private bool ReadNextBinary32()        {            FileStream currentFile = m_fileStreams[m_streamIndex];            int recordLength = m_schema.Binary32RecordLength;            byte[] buffer = new byte[recordLength];             // Read next record from file            int bytesRead = currentFile.Read(buffer, 0, recordLength);             // See if we have reached the end of this file            if (bytesRead == 0)                return ReadNextFile();             if (bytesRead == recordLength)            {                int index = ReadTimestamp(buffer);                 index = ReadAnalogValues(buffer, index, ReadInt32, 4);                 ReadDigitalValues(buffer, index);            }            else

Lines 1–500 of 650.

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