/// <summary> /// Raises the <see cref="FrameParserBase{TypeIndentifier}.ReceivedConfigurationFrame"/> event. /// </summary> /// <param name="frame"><see cref="IConfigurationFrame"/> to send to <see cref="FrameParserBase{TypeIndentifier}.ReceivedConfigurationFrame"/> event.</param> protected override void OnReceivedConfigurationFrame(IConfigurationFrame frame) { // We override this method so we can cache configuration frame when it's received base.OnReceivedConfigurationFrame(frame); // Cache new configuration frame for parsing subsequent data frames... ConfigurationFrame configurationFrame = frame as ConfigurationFrame; if (configurationFrame != null) { m_configurationFrame = configurationFrame; } }
// Static Methods // Attempts to cast given frame into a BPA PDCstream configuration frame - theoretically this will // allow the same configuration frame to be used for any protocol implementation internal static ConfigurationFrame CastToDerivedConfigurationFrame(IConfigurationFrame sourceFrame, string configurationFileName) { // See if frame is already a BPA PDCstream configuration frame (if so, we don't need to do any work) ConfigurationFrame derivedFrame = sourceFrame as ConfigurationFrame; if (derivedFrame == null) { // Create a new BPA PDCstream configuration frame converted from equivalent configuration information ConfigurationCell derivedCell; IFrequencyDefinition sourceFrequency; derivedFrame = new ConfigurationFrame(sourceFrame.Timestamp, configurationFileName, 1, RevisionNumber.Revision2, StreamType.Compact); foreach (IConfigurationCell sourceCell in sourceFrame.Cells) { // Create new derived configuration cell derivedCell = new ConfigurationCell(derivedFrame, sourceCell.IDCode, sourceCell.NominalFrequency); // Create equivalent derived phasor definitions foreach (IPhasorDefinition sourcePhasor in sourceCell.PhasorDefinitions) { derivedCell.PhasorDefinitions.Add(new PhasorDefinition(derivedCell, sourcePhasor.Label, sourcePhasor.ScalingValue, sourcePhasor.Offset, sourcePhasor.PhasorType, null)); } // Create equivalent derived frequency definition sourceFrequency = sourceCell.FrequencyDefinition; if (sourceFrequency != null) { derivedCell.FrequencyDefinition = new FrequencyDefinition(derivedCell, sourceFrequency.Label); } // Create equivalent derived analog definitions (assuming analog type = SinglePointOnWave) foreach (IAnalogDefinition sourceAnalog in sourceCell.AnalogDefinitions) { derivedCell.AnalogDefinitions.Add(new AnalogDefinition(derivedCell, sourceAnalog.Label, sourceAnalog.ScalingValue, sourceAnalog.Offset, sourceAnalog.AnalogType)); } // Create equivalent derived digital definitions foreach (IDigitalDefinition sourceDigital in sourceCell.DigitalDefinitions) { derivedCell.DigitalDefinitions.Add(new DigitalDefinition(derivedCell, sourceDigital.Label)); } // Add cell to frame derivedFrame.Cells.Add(derivedCell); } } return(derivedFrame); }
/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from given <paramref name="buffer"/>. /// </summary> /// <param name="parseWordCountFromByte">Defines flag that interprets word count in packet header from a byte instead of a word.</param> /// <param name="usePhasorDataFileFormat">Defines flag that determines if source data is in the Phasor Data File Format (i.e., a DST file).</param> /// <param name="configFrame">Previously parsed configuration frame, if available.</param> /// <param name="buffer">Buffer that contains data to parse.</param> /// <param name="startIndex">Start index into buffer where valid data begins.</param> /// <param name="length">Maximum length of valid data from start index.</param> public CommonFrameHeader(bool parseWordCountFromByte, bool usePhasorDataFileFormat, ConfigurationFrame configFrame, byte[] buffer, int startIndex, int length) { uint secondOfCentury; // Determines if format is for DST file or streaming data m_usePhasorDataFileFormat = usePhasorDataFileFormat; if (m_usePhasorDataFileFormat) { // Handle phasor file format data protocol steps if (buffer[startIndex] == PhasorProtocols.Common.SyncByte && buffer[startIndex + 1] == Common.PhasorFileFormatFlag) { // Bail out and leave frame length zero if there's not enough buffer to parse complete fixed portion of header if (length >= DstHeaderFixedLength) { // Read full DST header m_packetNumber = (byte)BPAPDCstream.FrameType.ConfigurationFrame; m_fileType = (FileType)buffer[startIndex + 2]; m_fileVersion = (FileVersion)buffer[startIndex + 3]; m_sourceID = Encoding.ASCII.GetString(buffer, startIndex + 4, 4); uint headerLength = BigEndian.ToUInt32(buffer, startIndex + 8); secondOfCentury = BigEndian.ToUInt32(buffer, startIndex + 12); switch (m_fileType) { case FileType.PdcNtp: RoughTimestamp = new NtpTimeTag(secondOfCentury, 0).ToDateTime().Ticks; break; case FileType.PdcUnix: RoughTimestamp = new UnixTimeTag(secondOfCentury).ToDateTime().Ticks; break; default: RoughTimestamp = 0; break; } m_startSample = BigEndian.ToUInt32(buffer, startIndex + 16); m_sampleInterval = BigEndian.ToUInt16(buffer, startIndex + 20); m_sampleRate = BigEndian.ToUInt16(buffer, startIndex + 22); m_rowLength = BigEndian.ToUInt32(buffer, startIndex + 24); m_totalRows = BigEndian.ToUInt32(buffer, startIndex + 28); secondOfCentury = BigEndian.ToUInt32(buffer, startIndex + 32); switch (m_fileType) { case FileType.PdcNtp: m_triggerTime = new NtpTimeTag(secondOfCentury, 0).ToDateTime().Ticks; break; case FileType.PdcUnix: m_triggerTime = new UnixTimeTag(secondOfCentury).ToDateTime().Ticks; break; default: m_triggerTime = 0; break; } m_triggerSample = BigEndian.ToUInt32(buffer, startIndex + 36); m_preTriggerRows = BigEndian.ToUInt32(buffer, startIndex + 40); m_triggerPMU = BigEndian.ToUInt16(buffer, startIndex + 44); m_triggerType = BigEndian.ToUInt16(buffer, startIndex + 46); m_userInformation = Encoding.ASCII.GetString(buffer, startIndex + 48, 80).Trim(); m_pmuCount = BigEndian.ToUInt32(buffer, startIndex + 128); FrameLength = unchecked ((ushort)headerLength); } } else { // Must assume this is a data row if there are no sync bytes m_packetNumber = (byte)BPAPDCstream.FrameType.DataFrame; m_rowFlags = BigEndian.ToUInt32(buffer, startIndex); if (configFrame is null) { FrameLength = FixedLength; } else { uint sampleIndex = configFrame.SampleIndex; CommonFrameHeader configFrameHeader = configFrame.CommonHeader; if (configFrameHeader is null) { FrameLength = FixedLength; } else { // Assign row length to make sure parser knows how much data it needs FrameLength = unchecked ((ushort)configFrameHeader.RowLength); // Calculate timestamp as offset plus sample index * frame rate RoughTimestamp = configFrameHeader.RoughTimestamp + Ticks.FromSeconds(sampleIndex * (1.0D / configFrameHeader.FrameRate)); } // Increment sample index for next row configFrame.SampleIndex = sampleIndex + 1; } } } else { // Handle streaming data protocol steps if (buffer[startIndex] != PhasorProtocols.Common.SyncByte) { throw new InvalidOperationException($"Bad data stream, expected sync byte 0xAA as first byte in BPA PDCstream frame, got 0x{buffer[startIndex].ToString("X").PadLeft(2, '0')}"); } // Get packet number m_packetNumber = buffer[startIndex + 1]; // Some older streams have a bad word count (e.g., some data streams have a 0x01 as the third byte // in the stream - this should be a 0x00 to make the word count come out correctly). The following // compensates for this erratic behavior m_wordCount = parseWordCountFromByte ? buffer[startIndex + 3] : BigEndian.ToUInt16(buffer, startIndex + 2); // If this is a data frame get a rough timestamp down to the second (full parse will get accurate timestamp), this way // data frames that don't get fully parsed because configuration hasn't been received will still show a timestamp if (m_packetNumber > 0 && length > 8) { secondOfCentury = BigEndian.ToUInt32(buffer, startIndex + 4); // Until configuration is available, we make a guess at time tag type - this will just be // used for display purposes until a configuration frame arrives. If second of century // is greater than 3155673600 (SOC value for NTP timestamp 1/1/2007), then this is likely // an NTP time stamp (else this is a Unix time tag for the year 2069 - not likely). RoughTimestamp = secondOfCentury > 3155673600 ? new NtpTimeTag(secondOfCentury, 0).ToDateTime().Ticks : new UnixTimeTag(secondOfCentury).ToDateTime().Ticks; } } }
/// <summary> /// Creates a new <see cref="ConfigurationCell"/> from specified parameters. /// </summary> /// <param name="parent">The reference to parent <see cref="ConfigurationFrame"/> of this <see cref="ConfigurationCell"/>.</param> /// <param name="idCode">The numeric ID code for this <see cref="ConfigurationCell"/>.</param> /// <param name="nominalFrequency">The nominal <see cref="LineFrequency"/> of the <see cref="FrequencyDefinition"/> of this <see cref="ConfigurationCell"/>.</param> public ConfigurationCell(ConfigurationFrame parent, ushort idCode, LineFrequency nominalFrequency = LineFrequency.Hz60) : this(parent) { IDCode = idCode; NominalFrequency = nominalFrequency; }
/// <summary> /// Creates a new <see cref="DataFrame"/> from specified parameters. /// </summary> /// <param name="timestamp">The exact timestamp, in <see cref="Ticks"/>, of the data represented by this <see cref="DataFrame"/>.</param> /// <param name="configurationFrame">The <see cref="ConfigurationFrame"/> associated with this <see cref="DataFrame"/>.</param> /// <param name="packetNumber">Packet number for this <see cref="DataFrame"/>.</param> /// <param name="sampleNumber">Sample number for this <see cref="DataFrame"/>.</param> /// <remarks> /// This constructor is used by a consumer to generate a BPA PDCstream data frame. /// </remarks> public DataFrame(Ticks timestamp, ConfigurationFrame configurationFrame, byte packetNumber, ushort sampleNumber) : base(new DataCellCollection(), timestamp, configurationFrame) { PacketNumber = packetNumber; m_sampleNumber = sampleNumber; }
/// <summary> /// Creates a new <see cref="PhasorDefinition"/> from specified parameters. /// </summary> /// <param name="parent">The <see cref="ConfigurationCell"/> parent of this <see cref="PhasorDefinition"/>.</param> /// <param name="index">Index of phasor within INI based configuration file.</param> /// <param name="entryValue">The entry value from the INI based configuration file.</param> public PhasorDefinition(ConfigurationCell parent, int index, string entryValue) : base(parent) { string[] entry = entryValue.Split(','); string entryType = entry[0].Trim().Substring(0, 1).ToUpper(); PhasorDefinition defaultPhasor; double dValue; if (parent != null) { ConfigurationFrame configFile = this.Parent.Parent; if (entryType == "V") { PhasorType = PhasorType.Voltage; defaultPhasor = configFile.DefaultPhasorV; } else if (entryType == "I") { PhasorType = PhasorType.Current; defaultPhasor = configFile.DefaultPhasorI; } else { PhasorType = PhasorType.Voltage; defaultPhasor = configFile.DefaultPhasorV; } } else { defaultPhasor = new PhasorDefinition(null as ConfigurationCell); } if (entry.Length > 1 && double.TryParse(entry[1].Trim(), out dValue)) { Ratio = dValue; } else { Ratio = defaultPhasor.Ratio; } if (entry.Length > 2 && double.TryParse(entry[2].Trim(), out dValue)) { CalFactor = dValue; } else { CalFactor = defaultPhasor.CalFactor; } if (entry.Length > 3 && double.TryParse(entry[3].Trim(), out dValue)) { Offset = dValue; } else { Offset = defaultPhasor.Offset; } if (entry.Length > 4 && double.TryParse(entry[4].Trim(), out dValue)) { Shunt = dValue; } else { Shunt = defaultPhasor.Shunt; } if (entry.Length > 5 && double.TryParse(entry[5].Trim(), out dValue)) { VoltageReferenceIndex = (int)dValue; } else { VoltageReferenceIndex = defaultPhasor.VoltageReferenceIndex; } if (entry.Length > 6) { Label = entry[6].Trim(); } else { Label = defaultPhasor.Label; } this.Index = index; }
/// <summary> /// Creates a new <see cref="PhasorDefinition"/> from specified parameters. /// </summary> /// <param name="parent">The <see cref="ConfigurationCell"/> parent of this <see cref="PhasorDefinition"/>.</param> /// <param name="index">Index of phasor within INI based configuration file.</param> /// <param name="entryValue">The entry value from the INI based configuration file.</param> public PhasorDefinition(ConfigurationCell parent, int index, string entryValue) : base(parent) { string[] entry = entryValue.Split(','); string entryType = entry[0].Trim().Substring(0, 1).ToUpper(); PhasorDefinition defaultPhasor; if (parent is null) { defaultPhasor = new PhasorDefinition(null); } else { ConfigurationFrame configFile = Parent.Parent; switch (entryType) { case "V": PhasorType = PhasorType.Voltage; defaultPhasor = configFile.DefaultPhasorV; break; case "I": PhasorType = PhasorType.Current; defaultPhasor = configFile.DefaultPhasorI; break; default: PhasorType = PhasorType.Voltage; defaultPhasor = configFile.DefaultPhasorV; break; } } if (entry.Length > 1 && double.TryParse(entry[1].Trim(), out double dValue)) { Ratio = dValue; } else { Ratio = defaultPhasor.Ratio; } if (entry.Length > 2 && double.TryParse(entry[2].Trim(), out dValue)) { CalFactor = dValue; } else { CalFactor = defaultPhasor.CalFactor; } if (entry.Length > 3 && double.TryParse(entry[3].Trim(), out dValue)) { Offset = dValue; } else { Offset = defaultPhasor.Offset; } if (entry.Length > 4 && double.TryParse(entry[4].Trim(), out dValue)) { Shunt = dValue; } else { Shunt = defaultPhasor.Shunt; } if (entry.Length > 5 && double.TryParse(entry[5].Trim(), out dValue)) { VoltageReferenceIndex = (int)dValue; } else { VoltageReferenceIndex = defaultPhasor.VoltageReferenceIndex; } Label = entry.Length > 6 ? entry[6].Trim() : defaultPhasor.Label; Index = index; }
/// <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) { IDataFrameParsingState state = State; ConfigurationFrame configurationFrame = state.ConfigurationFrame as ConfigurationFrame; // Check for unlikely occurrence of unexpected configuration frame type if ((object)configurationFrame == null) { throw new InvalidOperationException("Unexpected configuration frame encountered - BPA PDCstream configuration frame expected, cannot parse data frame."); } if (m_usePhasorDataFileFormat) { // Because in cases where PDCxchng is being used the data cell count will be smaller than the // configuration cell count - we save this count to calculate the offsets later state.CellCount = unchecked ((int)configurationFrame.CommonHeader.PmuCount); if (state.CellCount > configurationFrame.Cells.Count) { throw new InvalidOperationException("Stream/Config File Mismatch: PMU count (" + state.CellCount + ") in stream does not match defined count in configuration file (" + configurationFrame.Cells.Count + ")"); } return(CommonFrameHeader.FixedLength); } // Only need to parse what wasn't already parsed in common frame header int index = startIndex + CommonFrameHeader.FixedLength; // Parse frame timestamp uint secondOfCentury = BigEndian.ToUInt32(buffer, index); m_sampleNumber = BigEndian.ToUInt16(buffer, index + 4); index += 6; if (configurationFrame.RevisionNumber == RevisionNumber.Revision0) { Timestamp = (new NtpTimeTag(secondOfCentury, 0)).ToDateTime().Ticks + (long)((m_sampleNumber - 1) * configurationFrame.TicksPerFrame); } else { Timestamp = (new UnixTimeTag(secondOfCentury)).ToDateTime().Ticks + (long)((m_sampleNumber - 1) * configurationFrame.TicksPerFrame); } // Because in cases where PDCxchng is being used the data cell count will be smaller than the // configuration cell count - we save this count to calculate the offsets later state.CellCount = BigEndian.ToUInt16(buffer, index); index += 2; if (state.CellCount > configurationFrame.Cells.Count) { throw new InvalidOperationException("Stream/Config File Mismatch: PMU count (" + state.CellCount + ") in stream does not match defined count in configuration file (" + configurationFrame.Cells.Count + ")"); } // We'll at least retrieve legacy labels if defined (might be useful for debugging dynamic changes in data-stream) if (configurationFrame.StreamType == StreamType.Legacy) { m_legacyLabels = new string[state.CellCount]; for (int x = 0; x < state.CellCount; x++) { m_legacyLabels[x] = Encoding.ASCII.GetString(buffer, index, 4); // We don't need offsets, so we skip them... index += 8; } } return(index - startIndex); }