//****************************************************************************************************** // ControlFile.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: // ---------------------------------------------------------------------------------------------------- // 08/01/2014 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace GSF.EMAX { /// /// Represents an EMAX control file (i.e., a .CTL file). /// public class ControlFile { #region [ Members ] // Fields public string FileName; public DataSize DataSize; public CTL_HEADER Header; public CTL_FILE_STRUCT[] FileStructures; public SYSTEM_PARAMETERS SystemParameters; public SYS_SETTINGS SystemSettings; public ANALOG_GROUP AnalogGroup; public EVENT_GROUP EventGroup; public A_E_RSLTS AnalogEventResults; public IDENTSTRING IdentityString; public Dictionary AnalogChannelSettings; public Dictionary EventChannelSettings; public Dictionary ScalingFactors; public EVENT_DISPLAY EventDisplay; public SENS_RSLTS SensorResults; public TPwrRcd PowerRecord; public BoardAnalogEventChannels BoardAnalogEventChannels; public A_SELECTION AnalogSelection; public E_GRP_SELECT EventGroupSelection; public PHASOR_GROUPS PhasorGroups; public LINE_CONSTANTS LineConstants; public LINE_NAMES LineNames; public FAULT_LOCATIONS FaultLocations; public SEQUENCE_CHANNELS SequenceChannels; public BREAKER_TRIP_TIMES BreakerTripTimes; private int[] m_frameLength; private int m_configuredAnalogChannels; private readonly List m_parsedSuccesses; private readonly List> m_parsedFailures; private StructureType m_currentType; #endregion #region [ Constructors ] /// /// Creates a new . /// public ControlFile() { m_parsedSuccesses = new List(); m_parsedFailures = new List>(); } /// /// Creates a new for the specified and attempts to parse. /// /// Control file name. public ControlFile(string fileName) : this() { FileName = fileName; Parse(); } #endregion #region [ Properties ] /// /// Gets configured analog channels. /// public int ConfiguredAnalogChannels { get { return m_configuredAnalogChannels; } } /// /// Gets the digital channel count for the . /// public int ConfiguredDigitalChannels { get { return EventGroupCount * 8; } } /// /// Gets the analog channel count for the . /// public int AnalogChannelCount { get { ushort samplesPerSecond = SystemParameters.samples_per_second; return m_configuredAnalogChannels * (samplesPerSecond > 5760 ? samplesPerSecond / 5760 : 1); } } /// /// Gets the event group count for the . /// public int EventGroupCount { get { if (SystemParameters.samples_per_second > 5760) return 4; return 4 * (m_configuredAnalogChannels > 32 ? 2 : 1); } } /// /// Gets the calculated frame length for the . /// public int[] FrameLength { get { // 64 data words + 8 event group words + 8 clock words const int largeFrame = sizeof(ushort) * 80; // 32 data words + 4 event group words + 4 clock words const int smallFrame = sizeof(ushort) * 40; int cardCount; int remainder; if ((object)m_frameLength != null) return m_frameLength; cardCount = AnalogChannelCount / 64; remainder = AnalogChannelCount % 64; if (remainder > 0) cardCount++; m_frameLength = Enumerable.Repeat(largeFrame, cardCount).ToArray(); if (0 < remainder && remainder <= 32) m_frameLength[cardCount - 1] = smallFrame; return m_frameLength; } } /// /// Gets instances that were parsed successfully. /// public IEnumerable ParsedSuccesses { get { return m_parsedSuccesses; } } /// /// Gets instances that failed to parse. /// public IEnumerable> ParsedFailures { get { return m_parsedFailures; } } #endregion #region [ Methods ] /// /// Parses the . /// /// "No EMAX control file name was specified. /// EMAX control file was not found. public void Parse() { if (string.IsNullOrEmpty(FileName)) throw new InvalidOperationException("No EMAX control file name was specified."); if (!File.Exists(FileName)) throw new FileNotFoundException(string.Format("EMAX control file {0} not found.", FileName)); m_parsedSuccesses.Clear(); m_parsedFailures.Clear(); byte byteValue; using (FileStream stream = File.OpenRead(FileName)) { // Read in header and file structure definitions using (BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, true)) { // Read control header Header = reader.ReadStructure(); // Read byte that defines number of analog channels m_configuredAnalogChannels = BinaryCodedDecimal.Decode(Header.id.LowByte()); // Read byte that defines data size (i.e., 12 or 16 bits) byteValue = Header.id.HighByte(); if (!Enum.IsDefined(typeof(DataSize), byteValue)) throw new InvalidOperationException("Invalid EMAX data size code encountered: 0x" + byteValue.ToString("X").PadLeft(2, '0')); DataSize = (DataSize)byteValue; // Create array of file structures List fileStructures = new List(); CTL_FILE_STRUCT fileStructure = new CTL_FILE_STRUCT(reader); // ReSharper disable once LoopVariableIsNeverChangedInsideLoop while (fileStructure.type != StructureType.EndOfStructures) { if (fileStructure.type != StructureType.Unknown) fileStructures.Add(fileStructure); fileStructure = new CTL_FILE_STRUCT(reader); } FileStructures = fileStructures.ToArray(); } // Read in actual file structures for (int index = 0; index < FileStructures.Length; index++) { CTL_FILE_STRUCT fileStructure = FileStructures[index]; if (fileStructure.type == StructureType.Unknown) continue; // Set current type m_currentType = fileStructure.type; // Locate structure in the file stream.Position = fileStructure.offset; // Parse the structure type using (BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, true)) { switch (m_currentType) { case StructureType.SYSTEM_PARAMETERS: AttemptParse(() => SystemParameters = reader.ReadStructure()); break; case StructureType.SYS_SETTINGS: AttemptParse(() => SystemSettings = reader.ReadStructure()); break; case StructureType.A_E_RSLTS: AttemptParse(() => AnalogEventResults = new A_E_RSLTS(reader, m_configuredAnalogChannels, SystemParameters.analog_groups)); break; case StructureType.ANALOG_GROUP: AttemptParse(() => AnalogGroup = new ANALOG_GROUP(reader, m_configuredAnalogChannels)); break; case StructureType.EVENT_GROUP: AttemptParse(() => EventGroup = reader.ReadStructure()); break; case StructureType.ANLG_CHNL_NEW: AttemptParse(() => { AnalogChannelSettings = new Dictionary(); ScalingFactors = new Dictionary(); ANLG_CHNL_NEW settings; uint nextOffset = (index + 1 < FileStructures.Length) ? FileStructures[index + 1].offset : (uint)stream.Length; uint length = nextOffset - fileStructure.offset; Func channelFactory; if (Marshal.SizeOf() * ConfiguredAnalogChannels <= length) channelFactory = () => reader.ReadStructure().ToAnlgChnlNew(); else channelFactory = () => reader.ReadStructure().ToAnlgChnlNew(); for (int i = 0; i < ConfiguredAnalogChannels; i++) { settings = channelFactory(); AnalogChannelSettings.Add(settings.ChannelNumber, settings); ScalingFactors.Add(settings.ChannelNumber, settings.ScalingFactor); } }); break; case StructureType.EVNT_CHNL_NEW: AttemptParse(() => { EventChannelSettings = new Dictionary(); EVNT_CHNL_NEW settings; uint nextOffset = (index + 1 < FileStructures.Length) ? FileStructures[index + 1].offset : (uint)stream.Length; uint length = nextOffset - fileStructure.offset; Func channelFactory; if (Marshal.SizeOf() * ConfiguredDigitalChannels <= length) channelFactory = () => reader.ReadStructure().ToEvntChnlNew(); else channelFactory = () => reader.ReadStructure().ToEvntChnlNew(); for (int i = 0; i < ConfiguredDigitalChannels; i++) { settings = channelFactory(); EventChannelSettings.Add(settings.EventNumber, settings); } }); break; case StructureType.ANLG_CHNLS: // TODO: Add decoder once structure definition is known... m_parsedFailures.Add(new Tuple(m_currentType, new NotImplementedException())); break; case StructureType.SHORT_HEADER: // TODO: Add decoder once structure definition is known... m_parsedFailures.Add(new Tuple(m_currentType, new NotImplementedException())); break; case StructureType.FAULT_HEADER: // TODO: Add decoder once structure definition is known... m_parsedFailures.Add(new Tuple(m_currentType, new NotImplementedException())); break; case StructureType.EVENT_DISPLAY: AttemptParse(() => EventDisplay = new EVENT_DISPLAY(reader, SystemParameters.event_groups)); break; case StructureType.IDENTSTRING: AttemptParse(() => { IdentityString = reader.ReadStructure(); IdentityString.value.TruncateRight(IdentityString.length); }); break; case StructureType.A_SELECTION: AttemptParse(() => AnalogSelection = new A_SELECTION(reader)); break; case StructureType.E_GROUP_SELECT: AttemptParse(() => EventGroupSelection = new E_GRP_SELECT(reader)); break; case StructureType.PHASOR_GROUP: AttemptParse(() => PhasorGroups = new PHASOR_GROUPS(reader)); break; case StructureType.LINE_CONSTANTS: AttemptParse(() => LineConstants = new LINE_CONSTANTS(reader)); break; case StructureType.LINE_NAMES: AttemptParse(() => LineNames = new LINE_NAMES(reader)); break; case StructureType.FAULT_LOCATION: AttemptParse(() => FaultLocations = new FAULT_LOCATIONS(reader)); break; case StructureType.SENS_RSLTS: AttemptParse(() => SensorResults = reader.ReadStructure()); break; case StructureType.SEQUENCE_CHANNELS: AttemptParse(() => SequenceChannels = new SEQUENCE_CHANNELS(reader)); break; case StructureType.TPwrRcd: AttemptParse(() => PowerRecord = reader.ReadStructure()); break; case StructureType.BoardAnalogEventChannels: AttemptParse(() => BoardAnalogEventChannels = reader.ReadStructure()); break; case StructureType.BREAKER_TRIP_TIMES: AttemptParse(() => BreakerTripTimes = new BREAKER_TRIP_TIMES(reader, m_configuredAnalogChannels, SystemParameters.analog_groups)); break; default: throw new ArgumentOutOfRangeException(); } } } } } private void AttemptParse(Action parseAction) { try { parseAction(); m_parsedSuccesses.Add(m_currentType); } catch (Exception ex) { m_parsedFailures.Add(new Tuple(m_currentType, ex)); } } #endregion #region [ Old Code ] //private readonly StructureType[] StructureTypeProcessingOrder = //{ // StructureType.SYSTEM_PARAMETERS, // StructureType.SYS_SETTINGS, // StructureType.A_E_RSLTS, // StructureType.ANALOG_GROUP, // StructureType.EVENT_GROUP, // StructureType.ANLG_CHNL_NEW, // StructureType.EVNT_CHNL_NEW, // StructureType.ANLG_CHNLS, // StructureType.SHORT_HEADER, // StructureType.FAULT_HEADER, // StructureType.EVENT_DISPLAY, // StructureType.IDENTSTRING, // StructureType.A_SELECTION, // StructureType.E_GROUP_SELECT, // StructureType.PHASOR_GROUP, // StructureType.LINE_CONSTANTS, // StructureType.LINE_NAMES, // StructureType.FAULT_LOCATION, // StructureType.SENS_RSLTS, // StructureType.SEQUENCE_CHANNELS, // StructureType.TPwrRcd, // StructureType.BoardAnalogEventChannels, // StructureType.Unknown, // StructureType.EndOfStructures //}; //Debug.Assert(StructureTypeProcessingOrder.Length == Enum.GetValues(typeof(StructureType)).Length, "StructureTypeProcessOrder is not the same length as StructureType enumeration!"); //// Sort file structures in desired processing order //Func getProcessingOrder = structureType => Array.IndexOf(StructureTypeProcessingOrder, structureType); //fileStructures.Sort((x, y) => //{ // int indexX = getProcessingOrder(x.type); // int indexY = getProcessingOrder(y.type); // return indexX < indexY ? -1 : indexX > indexY ? 1 : 0; //}); #endregion } }