740 lines · 36.9 KB · C#
RawDownload
//******************************************************************************************************//  ConfigurationFrame.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://  ----------------------------------------------------------------------------------------------------//  11/12/2004 - J. Ritchie Carroll//       Generated original version of source code.//  09/15/2009 - Stephen C. Wills//       Added new header and license agreement.//  05/19/2011 - Ritchie//       Added DST file support.//  12/17/2012 - Starlynn Danyelle Gilliam//       Modified Header.////****************************************************************************************************** using System;using System.Collections.Generic;using System.ComponentModel;using System.IO;using System.Runtime.Serialization;using System.Text;using GSF.Interop;using GSF.IO.Checksums;using GSF.Parsing;using GSF.Reflection; // ReSharper disable PossibleInvalidCastExceptionInForeachLoop// ReSharper disable VirtualMemberCallInConstructornamespace GSF.PhasorProtocols.BPAPDCstream{    /// <summary>    /// Represents the BPA PDCstream implementation of a <see cref="IConfigurationFrame"/> that can be sent or received.    /// </summary>    [Serializable]    public class ConfigurationFrame : ConfigurationFrameBase, ISupportSourceIdentifiableFrameImage<SourceChannel, FrameType>    {        #region [ Members ]         // Constants        private const int FixedHeaderLength = CommonFrameHeader.FixedLength + 12;         /// <summary>        /// Default voltage phasor INI based configuration entry.        /// </summary>        public const string DefaultVoltagePhasorEntry = "V,4500.0,0.0060573,0,0,500,Default 500kV";         /// <summary>        /// Default current phasor INI based configuration entry.        /// </summary>        public const string DefaultCurrentPhasorEntry = "I,600.00,0.000040382,0,1,1,Default Current";         /// <summary>        /// Default frequency INI based configuration entry.        /// </summary>        public const string DefaultFrequencyEntry = "F,1000,60,1000,0,0,Frequency";         // Events         /// <summary>        /// Occurs when the BPA PDCstream INI based configuration file has been reloaded.        /// </summary>        public event EventHandler ConfigurationFileReloaded;         // Fields        private CommonFrameHeader m_frameHeader;        private IniFile m_iniFile;        private uint m_rowLength;        private ushort m_packetsPerSample;        private StreamType m_streamType;        private RevisionNumber m_revisionNumber;        private uint m_sampleIndex;     #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new <see cref="ConfigurationFrame"/>.        /// </summary>        /// <remarks>        /// This constructor is used by <see cref="FrameImageParserBase{TTypeIdentifier,TOutputType}"/> to parse a BPA PDCstream configuration frame.        /// </remarks>        [EditorBrowsable(EditorBrowsableState.Never)]        public ConfigurationFrame()            : base(0, new ConfigurationCellCollection(), 0, 0)        {            // We just assume current timestamp for configuration frames since they don't provide one            Timestamp = DateTime.UtcNow.Ticks;        }         /// <summary>        /// Creates a new <see cref="ConfigurationFrame"/> from specified parameters.        /// </summary>        /// <param name="timestamp">The exact timestamp, in <see cref="Ticks"/>, of the data represented by this <see cref="ConfigurationFrame"/>.</param>        /// <param name="configurationFileName">The required external BPA PDCstream INI based configuration file.</param>        /// <param name="revisionNumber">Defines the <see cref="RevisionNumber"/> for this BPA PDCstream configuration frame.</param>        /// <param name="streamType">Defines the <see cref="StreamType"/> for this BPA PDCstream configuration frame.</param>        /// <param name="packetsPerSample">Number of packets per sample.</param>        /// <remarks>        /// <para>        /// This constructor is used by a consumer to generate a BPA PDCstream configuration frame.        /// </para>        /// <para>        /// If you are going to create multiple data packets set <paramref name="packetsPerSample"/> to a number        /// greater than one. This will only start becoming necessary if you start hitting data size limits imposed        /// by the nature of the transport protocol.        /// </para>        /// </remarks>        public ConfigurationFrame(Ticks timestamp, string configurationFileName, ushort packetsPerSample, RevisionNumber revisionNumber, StreamType streamType)            : base(0, new ConfigurationCellCollection(), timestamp, 30)        {            m_iniFile = new IniFile(configurationFileName);            m_packetsPerSample = packetsPerSample;            m_revisionNumber = revisionNumber;            m_streamType = streamType;            Refresh(false);        }         /// <summary>        /// Creates a new <see cref="ConfigurationFrame"/> from serialization parameters.        /// </summary>        /// <param name="info">The <see cref="SerializationInfo"/> with populated with data.</param>        /// <param name="context">The source <see cref="StreamingContext"/> for this deserialization.</param>        protected ConfigurationFrame(SerializationInfo info, StreamingContext context)            : base(info, context)        {            // Deserialize configuration frame            m_frameHeader = (CommonFrameHeader)info.GetValue("frameHeader", typeof(CommonFrameHeader));            m_packetsPerSample = info.GetUInt16("packetsPerSample");            m_streamType = (StreamType)info.GetValue("streamType", typeof(StreamType));            m_revisionNumber = (RevisionNumber)info.GetValue("revisionNumber", typeof(RevisionNumber));            m_iniFile = new IniFile(info.GetString("configurationFileName"));             // The usePhasorDataFileFormat flag and other new elements did not exist in prior versions so we protect against possible deserialization failures            try            {                UsePhasorDataFileFormat = info.GetBoolean("usePhasorDataFileFormat");                m_rowLength = info.GetUInt32("rowLength");            }            catch (SerializationException)            {                UsePhasorDataFileFormat = false;                m_rowLength = 0;            }             Refresh(false);        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets reference to the <see cref="ConfigurationCellCollection"/> for this <see cref="ConfigurationFrame"/>.        /// </summary>        public new ConfigurationCellCollection Cells => base.Cells as ConfigurationCellCollection;         /// <summary>        /// Gets reference to the <see cref="ConfigurationCellCollection"/> loaded from the configuration file of this <see cref="ConfigurationFrame"/>.        /// </summary>        public ConfigurationCellCollection ConfigurationFileCells { get; private set; }         /// <summary>        /// Gets the <see cref="FrameType"/> of this <see cref="ConfigurationFrame"/>.        /// </summary>        public FrameType TypeID => BPAPDCstream.FrameType.ConfigurationFrame;         /// <summary>        /// Gets or sets current <see cref="CommonFrameHeader"/>.        /// </summary>        public CommonFrameHeader CommonHeader        {            // Make sure frame header exists            get => m_frameHeader ??= new CommonFrameHeader(Common.DescriptorPacketFlag);            set            {                m_frameHeader = value;                 if (m_frameHeader is null)                    return;                 UsePhasorDataFileFormat = m_frameHeader.UsePhasorDataFileFormat;                m_sampleIndex = m_frameHeader.StartSample;                Timestamp = m_frameHeader.RoughTimestamp;                m_rowLength = m_frameHeader.RowLength;                 if (m_frameHeader.State is ConfigurationFrameParsingState parsingState)                {                    State = parsingState;                    m_iniFile = new IniFile(parsingState.ConfigurationFileName);                }            }        }         // This interface implementation satisfies ISupportFrameImage<FrameType>.CommonHeader        ICommonHeader<FrameType> ISupportFrameImage<FrameType>.CommonHeader        {            get => CommonHeader;            set => CommonHeader = value as CommonFrameHeader;        }         /// <summary>        /// Gets or sets the BPA PDCstream protocol <see cref="BPAPDCstream.StreamType"/>, i.e., legacy or compact, of this <see cref="ConfigurationFrame"/>.        /// </summary>        public StreamType StreamType        {            get => m_streamType;            set => m_streamType = value;        }         /// <summary>        /// Gets or sets the <see cref="BPAPDCstream.RevisionNumber"/> of this <see cref="ConfigurationFrame"/>.        /// </summary>        public RevisionNumber RevisionNumber        {            get => m_revisionNumber;            set => m_revisionNumber = value;        }         /// <summary>        /// Gets or sets the number of packets per sample of this <see cref="ConfigurationFrame"/>.        /// </summary>        public ushort PacketsPerSample        {            get => m_packetsPerSample;            set => m_packetsPerSample = value;        }         /// <summary>        /// Gets or sets current sample index used to calculate row time stamp when source data is in the Phasor Data File Format (i.e., a DST file).        /// </summary>        public uint SampleIndex        {            get => m_sampleIndex;            set => m_sampleIndex = value;        }         /// <summary>        /// Gets the INI based configuration file name of this <see cref="ConfigurationFrame"/>.        /// </summary>        public string ConfigurationFileName => m_iniFile.FileName;         /// <summary>        /// Gets the default voltage phasor definition.        /// </summary>        public PhasorDefinition DefaultPhasorV { get; private set; }         /// <summary>        /// Gets the default current phasor definition.        /// </summary>        public PhasorDefinition DefaultPhasorI { get; private set; }         /// <summary>        /// Gets the default frequency definition.        /// </summary>        public FrequencyDefinition DefaultFrequency { get; private set; }         /// <summary>        /// Gets or sets flag that determines if source data is in the Phasor Data File Format (i.e., a DST file).        /// </summary>        public bool UsePhasorDataFileFormat { get; private set; }         /// <summary>        /// Gets the length of the <see cref="HeaderImage"/>.        /// </summary>        protected override int HeaderLength => FixedHeaderLength;         /// <summary>        /// Gets the binary header image of the <see cref="ConfigurationFrame"/> object.        /// </summary>        protected override byte[] HeaderImage        {            get            {                if (UsePhasorDataFileFormat)                    throw new NotSupportedException("Creation of the phasor file format (i.e., DST files) is not currently supported.");                 // Make sure to provide proper frame length for use in the common header image                CommonHeader.FrameLength = unchecked((ushort)BinaryLength);                 byte[] buffer = new byte[FixedHeaderLength];                int index = 0;                 CommonHeader.BinaryImage.CopyImage(buffer, ref index, CommonFrameHeader.FixedLength);                buffer[4] = (byte)StreamType;                buffer[5] = (byte)RevisionNumber;                BigEndian.CopyBytes(FrameRate, buffer, 6);                BigEndian.CopyBytes(RowLength(true), buffer, 8); // <-- Important: This step calculates all PMU row offsets!                BigEndian.CopyBytes(PacketsPerSample, buffer, 12);                BigEndian.CopyBytes((ushort)Cells.Count, buffer, 14);                 return buffer;            }        }         /// <summary>        /// <see cref="Dictionary{TKey,TValue}"/> of string based property names and values for the <see cref="ConfigurationFrame"/> object.        /// </summary>        public override Dictionary<string, string> Attributes        {            get            {                Dictionary<string, string> baseAttributes = base.Attributes;                 CommonHeader.AppendHeaderAttributes(baseAttributes);                 if (m_iniFile is not null)                    baseAttributes.Add("Configuration File Name", m_iniFile.FileName);                 baseAttributes.Add("Stream Type", $"{(int)m_streamType}: {m_streamType}");                baseAttributes.Add("Revision Number", $"{(int)m_revisionNumber}: {m_revisionNumber}");                baseAttributes.Add("Packets Per Sample", m_packetsPerSample.ToString());                 return baseAttributes;            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Parses the binary header image.        /// </summary>        /// <param name="buffer">Binary image to parse.</param>        /// <param name="startIndex">Start index into <paramref name="buffer"/> to begin parsing.</param>        /// <param name="length">Length of valid data within <paramref name="buffer"/>.</param>        /// <returns>The length of the data that was parsed.</returns>        protected override int ParseHeaderImage(byte[] buffer, int startIndex, int length)        {            if (UsePhasorDataFileFormat)            {                // Common frame header will have parsed all phasor data file header information at this point...                State.CellCount = unchecked((int)CommonHeader.PmuCount);                return CommonFrameHeader.DstHeaderFixedLength;            }             // Skip past header that was already parsed...            startIndex += CommonFrameHeader.FixedLength;             // Only need to parse what wasn't already parsed in common frame header            m_streamType = (StreamType)buffer[startIndex];            m_revisionNumber = (RevisionNumber)buffer[startIndex + 1];            FrameRate = BigEndian.ToUInt16(buffer, startIndex + 2);            m_rowLength = BigEndian.ToUInt32(buffer, startIndex + 4);            m_packetsPerSample = BigEndian.ToUInt16(buffer, startIndex + 8);            State.CellCount = BigEndian.ToUInt16(buffer, startIndex + 10);             // The data that's in the data stream will take precedence over what's in the            // in the configuration file.  The configuration file may define more PMU's than            // are in the stream - in my opinion that's OK - it's when you have PMU's in the            // stream that aren't defined in the INI file that you'll have trouble...             return FixedHeaderLength;        }         /// <summary>        /// Parses the binary image.        /// </summary>        /// <param name="buffer">Binary image to parse.</param>        /// <param name="startIndex">Start index into <paramref name="buffer"/> to begin parsing.</param>        /// <param name="length">Length of valid data within <paramref name="buffer"/>.</param>        /// <returns>The length of the data that was parsed.</returns>        /// <remarks>        /// This method is overridden so INI file can be loaded after binary image has been parsed.        /// </remarks>        public override int ParseBinaryImage(byte[] buffer, int startIndex, int length)        {            int parsedLength = base.ParseBinaryImage(buffer, startIndex, length);             // Load INI file image and associate parsed cells to cells in configuration file...            Refresh(true);             // Move past 4 EOH bytes, minus 2 since DST files do not contain a CRC at the end of each frame            if (UsePhasorDataFileFormat)                parsedLength += 2;             return parsedLength;        }         /// <summary>        /// Reload BPA PDcstream INI based configuration file.        /// </summary>        public void Refresh()        {            Refresh(false);        }         /// <summary>        /// Reload BPA PDcstream INI based configuration file specifying if the refresh was caused by a frame parse.        /// </summary>        /// <param name="refreshCausedByFrameParse">Flag that specifies if the refresh was caused by a frame parse.</param>        [EditorBrowsable(EditorBrowsableState.Advanced)]        public void Refresh(bool refreshCausedByFrameParse)        {            // The only time we need an access lock is when we reload the config file...            lock (m_iniFile)            {                if (File.Exists(m_iniFile.FileName))                {                    DefaultPhasorV = new PhasorDefinition(null, 0, m_iniFile["DEFAULT", "PhasorV", DefaultVoltagePhasorEntry]);                    DefaultPhasorI = new PhasorDefinition(null, 0, m_iniFile["DEFAULT", "PhasorI", DefaultCurrentPhasorEntry]);                    DefaultFrequency = new FrequencyDefinition(null, m_iniFile["DEFAULT", "Frequency", DefaultFrequencyEntry]);                    FrameRate = ushort.Parse(m_iniFile["CONFIG", "SampleRate", "30"]);                     // We read all cells in the config file into their own configuration cell collection - cells parsed                    // from the configuration frame will be mapped to their associated config file cell by ID label                    // when the configuration cell is parsed from the configuration frame                    if (ConfigurationFileCells is null)                        ConfigurationFileCells = new ConfigurationCellCollection();                     ConfigurationFileCells.Clear();                     // Load phasor data for each section in config file...                    foreach (string section in m_iniFile.GetSectionNames())                    {                        // Make sure this is not a special section                        if (section.Length == 0 || string.Equals(section, "DEFAULT", StringComparison.OrdinalIgnoreCase) || string.Equals(section, "CONFIG", StringComparison.OrdinalIgnoreCase))                            continue;                         // Create new PMU entry structure from config file settings...                        int phasorCount = int.Parse(m_iniFile[section, "NumberPhasors", "0"]);                         // Check for PDC code                        int pdcID = int.Parse(m_iniFile[section, "PDC", "-1"]);                         if (pdcID == -1)                        {                            // No PDC entry exists, assume this is a PMU                            ConfigurationCell pmuCell = new(this, 0)                            {                                IDCode = ushort.Parse(m_iniFile[section, "PMU", Cells.Count.ToString()]),                                SectionEntry = section,                                StationName = m_iniFile[section, "Name", section]                            };                             // This will automatically assign ID label as first 4 digits of section                            for (int x = 0; x < phasorCount; x++)                                pmuCell.PhasorDefinitions.Add(new PhasorDefinition(pmuCell, x + 1, m_iniFile[section, $"Phasor{(x + 1)}", DefaultVoltagePhasorEntry]));                             pmuCell.FrequencyDefinition = new FrequencyDefinition(pmuCell, m_iniFile[section, "Frequency", DefaultFrequencyEntry]);                            ConfigurationFileCells.Add(pmuCell);                        }                        else                        {                            // This is a PDC, need to define one virtual entry for each PMU                            int pmuCount = int.Parse(m_iniFile[section, "NumberPMUs", "0"]);                             for (int x = 0; x < pmuCount; x++)                            {                                // Create a new PMU cell for each PDC entry that exists                                // For BPA INI files, PMUs traditionally have an ID number indexed starting at zero or one - so we multiply                                // ID by 1000 and add index to attempt to create a fairly unique ID to help optimize downstream parsing                                ConfigurationCell pmuCell = new(this, 0)                                {                                    IDCode = unchecked((ushort)(pdcID * 1000 + x)),                                    SectionEntry = $"{section}pmu{x}",                                    StationName = $"{m_iniFile[section, "Name", section]} - Device {x + 1}"                                };                                 // This will automatically assign ID label as first 4 digits of section                                for (int y = 0; y < 2; y++)                                    pmuCell.PhasorDefinitions.Add(new PhasorDefinition(pmuCell, y + 1, m_iniFile[section, $"Phasor{(x * 2 + y + 1)}", DefaultVoltagePhasorEntry]));                                 pmuCell.FrequencyDefinition = new FrequencyDefinition(pmuCell, m_iniFile[section, "Frequency", DefaultFrequencyEntry]);                                ConfigurationFileCells.Add(pmuCell);                            }                        }                    }                     // Associate parsed cells with cells defined in INI file                    if (ConfigurationFileCells.Count > 0 && Cells is not null)                    {                        if (refreshCausedByFrameParse)                        {                            // Create a new configuration cell collection that will account for PDC block cells                            ConfigurationCellCollection cellCollection = new();                             // For freshly parsed configuration frames we'll have no PMU's in configuration                            // frame for PDCxchng blocks - so we'll need to dynamically create them                            for (int x = 0; x < Cells.Count; x++)                            {                                // Get current configuration cell                                ConfigurationCell cell = Cells[x];                                 // Lookup INI file configuration cell by ID label

Lines 1–500 of 740.

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