/// <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 ((object)configurationFrame != null) { m_configurationFrame = configurationFrame; } }
/// <summary> /// Casts the parsed <see cref="IChannelFrame"/> to its specific implementation (i.e., <see cref="DataFrame"/> or <see cref="ConfigurationFrame"/>). /// </summary> /// <param name="frame"><see cref="IChannelFrame"/> that was parsed by <see cref="FrameImageParserBase{TTypeIdentifier,TOutputType}"/> that implements protocol specific common frame header interface.</param> protected override void OnReceivedChannelFrame(IChannelFrame frame) { // Raise abstract channel frame events as a priority (i.e., IDataFrame, IConfigurationFrame, etc.) base.OnReceivedChannelFrame(frame); // Raise Macrodyne specific channel frame events, if any have been subscribed if (frame != null && (ReceivedDataFrame != null || ReceivedConfigurationFrame != null || ReceivedHeaderFrame != null)) { DataFrame dataFrame = frame as DataFrame; if (dataFrame != null) { if (ReceivedDataFrame != null) { ReceivedDataFrame(this, new EventArgs <DataFrame>(dataFrame)); } } else { HeaderFrame headerFrame = frame as HeaderFrame; if (headerFrame != null) { if (ReceivedHeaderFrame != null) { ReceivedHeaderFrame(this, new EventArgs <HeaderFrame>(headerFrame)); } } else { ConfigurationFrame configFrame = frame as ConfigurationFrame; if (configFrame != null) { if (ReceivedConfigurationFrame != null) { ReceivedConfigurationFrame(this, new EventArgs <ConfigurationFrame>(configFrame)); } } } } } }
/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from given <paramref name="buffer"/>. /// </summary> /// <param name="buffer">Buffer that contains data to parse.</param> /// <param name="startIndex">Start index into buffer where valid data begins.</param> /// <param name="protocolVersion">Macrodyne protocol to use for parsing.</param> /// <param name="configurationFrame">Configuration frame, if available.</param> public CommonFrameHeader(byte[] buffer, int startIndex, ProtocolVersion protocolVersion, ConfigurationFrame configurationFrame) { byte firstByte = buffer[startIndex]; // Cache protocol version m_protocolVersion = protocolVersion; // Validate Macrodyne data image if (firstByte != 0xAA && firstByte != 0xBB) { throw new InvalidOperationException($"Bad data stream, expected 0xAA or 0xBB as first byte in Macrodyne ON-LINE data frame or configuration frame request response, got 0x{buffer[startIndex].ToString("X").PadLeft(2, '0')}"); } // Determine frame type (it's either the sync byte of a data frame or the response by from a command request) if (firstByte == 0xAA) { TypeID = Macrodyne.FrameType.DataFrame; } else { TypeID = BigEndian.ToUInt16(buffer, startIndex) switch { (ushort)DeviceCommand.RequestOnlineDataFormat => Macrodyne.FrameType.ConfigurationFrame, (ushort)DeviceCommand.RequestUnitIDBufferValue => Macrodyne.FrameType.HeaderFrame, _ => throw new InvalidOperationException($"Bad data stream, expected 0xBB24 or 0xBB48 in response to Macrodyne device command, got 0xBB{buffer[startIndex + 1].ToString("X").PadLeft(2, '0')}"), }; }
/// <summary> /// Parses the binary body 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 ParseBodyImage(byte[] buffer, int startIndex, int length) { ConfigurationCell configCell = ConfigurationCell; ConfigurationFrame configFrame = configCell.Parent; ProtocolVersion protocolVersion = configFrame.CommonHeader.ProtocolVersion; IPhasorValue phasorValue; IDigitalValue digitalValue; int parsedLength, index = startIndex; if (protocolVersion == ProtocolVersion.M) { // Parse out optional STATUS2 flags if (configFrame.Status2Included) { m_status2Flags = buffer[index]; index++; } else { m_status2Flags = 0; } // We interpret status bytes together as one word (matches other protocols this way) base.StatusFlags = Word.MakeWord((byte)Status1Flags, m_status2Flags); } else { // Read sample number for G protocol m_sampleNumber = BigEndian.ToUInt16(buffer, index); index += 2; } // Parse out time tag if (configFrame.TimestampIncluded) { m_clockStatusFlags = (ClockStatusFlags)buffer[index]; index += 1; ushort day = BinaryCodedDecimal.Decode(BigEndian.ToUInt16(buffer, index)); byte hours = BinaryCodedDecimal.Decode(buffer[index + 2]); byte minutes = BinaryCodedDecimal.Decode(buffer[index + 3]); byte seconds = BinaryCodedDecimal.Decode(buffer[index + 4]); double timebase = 2880.0D; index += 5; // Read sample number for M protocol if (protocolVersion == ProtocolVersion.M) { m_sampleNumber = BigEndian.ToUInt16(buffer, index + 5); timebase = 719.0D; index += 2; } // TODO: Think about how to handle year change with floating clock... // Calculate timestamp Parent.Timestamp = new DateTime(DateTime.UtcNow.Year, 1, 1).AddDays(day - 1).AddHours(hours).AddMinutes(minutes).AddSeconds(seconds + m_sampleNumber / timebase); } else { Parent.Timestamp = DateTime.UtcNow.Ticks; SynchronizationIsValid = false; m_sampleNumber = BigEndian.ToUInt16(buffer, index); index += 2; } // Parse out first five phasor values (1 - 5) int phasorIndex = 0; // Phasor 1 (always present) phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor2Enabled) == OnlineDataFormatFlags.Phasor2Enabled) { // Phasor 2 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor3Enabled) == OnlineDataFormatFlags.Phasor3Enabled) { // Phasor 3 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor4Enabled) == OnlineDataFormatFlags.Phasor4Enabled) { // Phasor 4 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor5Enabled) == OnlineDataFormatFlags.Phasor5Enabled) { // Phasor 5 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } // For 1690M format the frequency, reference phasor, dF/dt and first digital follow phasors 1-5 if (protocolVersion == ProtocolVersion.M) { // Parse out frequency value FrequencyValue = Macrodyne.FrequencyValue.CreateNewValue(this, configCell.FrequencyDefinition, buffer, index, out parsedLength); index += parsedLength; // Parse reference phasor information if (configFrame.ReferenceIncluded) { m_referenceSampleNumber = BigEndian.ToUInt16(buffer, index); m_referencePhasor = PhasorValue.CreateNewValue(this, new PhasorDefinition(null, "Reference Phasor", PhasorType.Voltage, null), buffer, index, out parsedLength) as PhasorValue; index += 6; } // Parse first digital value if (configFrame.Digital1Included) { digitalValue = DigitalValue.CreateNewValue(this, configCell.DigitalDefinitions[0], buffer, index, out parsedLength); DigitalValues.Add(digitalValue); index += parsedLength; } } // Parse out next five phasor values (6 - 10) if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor6Enabled) == OnlineDataFormatFlags.Phasor6Enabled) { // Phasor 6 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor7Enabled) == OnlineDataFormatFlags.Phasor7Enabled) { // Phasor 7 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor8Enabled) == OnlineDataFormatFlags.Phasor8Enabled) { // Phasor 8 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor9Enabled) == OnlineDataFormatFlags.Phasor9Enabled) { // Phasor 9 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } if ((configFrame.OnlineDataFormatFlags & OnlineDataFormatFlags.Phasor10Enabled) == OnlineDataFormatFlags.Phasor10Enabled) { // Phasor 10 phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } // For 1690G format the channel phasors, reference phasor, frequency, dF/dt and digitals follow phasors 1-10 if (protocolVersion == ProtocolVersion.G) { // Technically 30 more possible channel phasors can be defined for (int i = phasorIndex; i < ConfigurationCell.PhasorDefinitions.Count; i++) { phasorValue = PhasorValue.CreateNewValue(this, configCell.PhasorDefinitions[phasorIndex++], buffer, index, out parsedLength); PhasorValues.Add(phasorValue); index += parsedLength; } // Parse reference phasor information if (configFrame.ReferenceIncluded) { m_referencePhasor = PhasorValue.CreateNewValue(this, new PhasorDefinition(null, "Reference Phasor", PhasorType.Voltage, null), buffer, index, out parsedLength) as PhasorValue; index += parsedLength; } // Parse out frequency value FrequencyValue = Macrodyne.FrequencyValue.CreateNewValue(this, configCell.FrequencyDefinition, buffer, index, out parsedLength); index += parsedLength; // Parse first digital value if (configFrame.Digital1Included) { digitalValue = DigitalValue.CreateNewValue(this, configCell.DigitalDefinitions[0], buffer, index, out parsedLength); DigitalValues.Add(digitalValue); index += parsedLength; } } // Parse second digital value if (configFrame.Digital2Included) { digitalValue = DigitalValue.CreateNewValue(this, configCell.DigitalDefinitions[configCell.DigitalDefinitions.Count - 1], buffer, index, out parsedLength); DigitalValues.Add(digitalValue); index += parsedLength; } // Return total parsed length return(index - startIndex); }
/// <summary> /// Creates a new <see cref="ConfigurationCell"/> from specified parameters. /// </summary> /// <param name="parent">The parent <see cref="ConfigurationFrame"/> reference to use.</param> /// <param name="deviceLabel">INI section device label to use.</param> public ConfigurationCell(ConfigurationFrame parent, string deviceLabel = null) : base(parent, 0, Common.MaximumPhasorValues, Common.MaximumAnalogValues, Common.MaximumDigitalValues) { // Assign station name that came in from header frame StationName = parent.StationName; if (!string.IsNullOrEmpty(deviceLabel)) { SectionEntry = deviceLabel; } // Add a single frequency definition FrequencyDefinition = new FrequencyDefinition(this) { Label = "Line frequency" }; OnlineDataFormatFlags flags = parent.OnlineDataFormatFlags; PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 1", PhasorType.Voltage, null)); if ((flags & OnlineDataFormatFlags.Phasor2Enabled) == OnlineDataFormatFlags.Phasor2Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 2", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Phasor3Enabled) == OnlineDataFormatFlags.Phasor3Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 3", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Phasor4Enabled) == OnlineDataFormatFlags.Phasor4Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 4", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Phasor5Enabled) == OnlineDataFormatFlags.Phasor5Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 5", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Phasor6Enabled) == OnlineDataFormatFlags.Phasor6Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 6", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Phasor7Enabled) == OnlineDataFormatFlags.Phasor7Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 7", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Phasor8Enabled) == OnlineDataFormatFlags.Phasor8Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 8", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Phasor9Enabled) == OnlineDataFormatFlags.Phasor9Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 9", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Phasor10Enabled) == OnlineDataFormatFlags.Phasor10Enabled) { PhasorDefinitions.Add(new PhasorDefinition(this, "Phasor 10", PhasorType.Voltage, null)); } if ((flags & OnlineDataFormatFlags.Digital1Enabled) == OnlineDataFormatFlags.Digital1Enabled) { DigitalDefinitions.Add(new DigitalDefinition(this, "Digital 1")); } if ((flags & OnlineDataFormatFlags.Digital2Enabled) == OnlineDataFormatFlags.Digital2Enabled) { DigitalDefinitions.Add(new DigitalDefinition(this, "Digital 2")); } }
/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from given <paramref name="buffer"/>. /// </summary> /// <param name="buffer">Buffer that contains data to parse.</param> /// <param name="startIndex">Start index into buffer where valid data begins.</param> /// <param name="protocolVersion">Macrodyne protocol to use for parsing.</param> /// <param name="configurationFrame">Configuration frame, if available.</param> public CommonFrameHeader(byte[] buffer, int startIndex, ProtocolVersion protocolVersion, ConfigurationFrame configurationFrame) { byte firstByte = buffer[startIndex]; // Cache protocol version m_protocolVersion = protocolVersion; // Validate Macrodyne data image if (firstByte != 0xAA && firstByte != 0xBB) { throw new InvalidOperationException($"Bad data stream, expected 0xAA or 0xBB as first byte in Macrodyne ON-LINE data frame or configuration frame request response, got 0x{buffer[startIndex].ToString("X").PadLeft(2, '0')}"); } // Determine frame type (it's either the sync byte of a data frame or the response by from a command request) if (firstByte == 0xAA) { TypeID = Macrodyne.FrameType.DataFrame; } else { switch (BigEndian.ToUInt16(buffer, startIndex)) { case (ushort)DeviceCommand.RequestOnlineDataFormat: TypeID = Macrodyne.FrameType.ConfigurationFrame; break; case (ushort)DeviceCommand.RequestUnitIDBufferValue: TypeID = Macrodyne.FrameType.HeaderFrame; break; default: throw new InvalidOperationException($"Bad data stream, expected 0xBB24 or 0xBB48 in response to Macrodyne device command, got 0xBB{buffer[startIndex + 1].ToString("X").PadLeft(2, '0')}"); } } // Cache config frame, if defined, for future use m_configurationFrame = configurationFrame; // Parse relevant common header values if (TypeID == Macrodyne.FrameType.DataFrame) { switch (m_protocolVersion) { case ProtocolVersion.M: m_statusFlags = (StatusFlags)buffer[startIndex + 1]; break; case ProtocolVersion.G: if (buffer[startIndex + 1] != 0x02) { throw new InvalidOperationException($"Bad data stream, expected 0xAA02 for Macrodyne 1690G version devices, got 0xAA{buffer[startIndex + 1].ToString("X").PadLeft(2, '0')}"); } break; } } }
/// <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 != 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) { Ratio = double.Parse(entry[1].Trim()); } else { Ratio = defaultPhasor.Ratio; } if (entry.Length > 2) { CalFactor = double.Parse(entry[2].Trim()); } else { ConversionFactor = defaultPhasor.ConversionFactor; } if (entry.Length > 3) { Offset = double.Parse(entry[3].Trim()); } else { Offset = defaultPhasor.Offset; } if (entry.Length > 4) { Shunt = double.Parse(entry[4].Trim()); } else { Shunt = defaultPhasor.Shunt; } if (entry.Length > 5) { VoltageReferenceIndex = (int)double.Parse(entry[5].Trim()); } else { VoltageReferenceIndex = defaultPhasor.VoltageReferenceIndex; } if (entry.Length > 6) { Label = entry[6].Trim(); } else { Label = defaultPhasor.Label; } this.Index = index; }