/// <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) { // Length is validated at a frame level well in advance so that low level parsing routines do not have // to re-validate that enough length is available to parse needed information as an optimization... IDataCellParsingState parsingState = State; IPhasorValue phasorValue; IAnalogValue analogValue; IDigitalValue digitalValue; int x, parsedLength, index = startIndex; StatusFlags = BigEndian.ToUInt16(buffer, startIndex); index += 2; // By the very nature of the major phasor protocols supporting the same order of phasors, frequency, df/dt, analog and digitals // we are able to "automatically" parse this data out in the data cell base class - BEAUTIFUL!!! // Parse out phasor values for (x = 0; x < parsingState.PhasorCount; x++) { phasorValue = parsingState.CreateNewPhasorValue(this, m_configurationCell.PhasorDefinitions[x], buffer, index, out parsedLength); m_phasorValues.Add(phasorValue); index += parsedLength; } // Parse out frequency and dF/dt values m_frequencyValue = parsingState.CreateNewFrequencyValue(this, m_configurationCell.FrequencyDefinition, buffer, index, out parsedLength); index += parsedLength; // Parse out analog values for (x = 0; x < parsingState.AnalogCount; x++) { analogValue = parsingState.CreateNewAnalogValue(this, m_configurationCell.AnalogDefinitions[x], buffer, index, out parsedLength); m_analogValues.Add(analogValue); index += parsedLength; } // Parse out digital values for (x = 0; x < parsingState.DigitalCount; x++) { digitalValue = parsingState.CreateNewDigitalValue(this, m_configurationCell.DigitalDefinitions[x], buffer, index, out parsedLength); m_digitalValues.Add(digitalValue); index += parsedLength; } // Return total parsed length return(index - startIndex); }
/// <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) { DataFrame parentFrame = Parent; DataFrameParsingState frameState = parentFrame.State; IDataCellParsingState state = State; RevisionNumber revision = parentFrame.ConfigurationFrame.RevisionNumber; int x, index = startIndex; byte analogs, digitals, phasors; // Read data buffer if using phasor data file format if (UsePhasorDataFileFormat && frameState.RemainingPdcBlockPmus == 0) { m_dataBuffer = BigEndian.ToUInt32(buffer, index); index += 4; } // Get data cell flags m_channelFlags = (ChannelFlags)buffer[index]; analogs = buffer[index + 1]; index += 2; // Parse PDCstream specific header image if (revision >= RevisionNumber.Revision2 && frameState.RemainingPdcBlockPmus == 0) { // Strip off reserved flags m_reservedFlags = (ReservedFlags)analogs & ~ReservedFlags.AnalogWordsMask; // Leave analog word count analogs &= (byte)ReservedFlags.AnalogWordsMask; } else { // Older revisions didn't allow analogs m_dataRate = analogs; analogs = 0; } if (frameState.RemainingPdcBlockPmus > 0) { // PDC Block PMU's contain exactly 2 phasors, 0 analogs and 1 digital phasors = 2; analogs = 0; digitals = 1; UsingPdcExchangeFormat = true; // Decrement remaining PDC block PMU's frameState.RemainingPdcBlockPmus--; } else { // Parse number of digitals and phasors for normal PMU cells digitals = buffer[index]; phasors = buffer[index + 1]; index += 2; if (revision >= RevisionNumber.Revision2) { // Strip off IEEE flags FormatFlags = (FormatFlags)digitals & ~FormatFlags.DigitalWordsMask; // Leave digital word count digitals &= (byte)FormatFlags.DigitalWordsMask; } // Check for PDC exchange format if (UsingPdcExchangeFormat) { // In cases where we are using PDC exchange the phasor count is the number of PMU's in the PDC block int pdcBlockPmus = phasors - 1; // <-- Current PMU counts as one frameState.RemainingPdcBlockPmus = pdcBlockPmus; frameState.CellCount += pdcBlockPmus; // PDC Block PMU's contain exactly 2 phasors, 0 analogs and 1 digital phasors = 2; analogs = 0; digitals = 1; // Get data cell flags for PDC block PMU m_channelFlags = (ChannelFlags)buffer[index]; UsingPdcExchangeFormat = true; index += 2; } else { // Parse PMU's sample number m_sampleNumber = BigEndian.ToUInt16(buffer, index); index += 2; } } // Algorithm Case: Determine best course of action when stream counts don't match counts defined in the // external INI based configuration file. Think about what *will* happen when new data appears in the // stream that's not in the config file - you could raise an event notifying consumer about the mismatch // instead of raising an exception - could even make a boolean property that would allow either case. // The important thing to consider is that to parse the cell images you have to have a defined // definition (see base class method "DataCellBase.ParseBodyImage"). If you have more items defined // in the stream than you do in the config file then you won't get the new value, too few items and you // don't have enough definitions to correctly interpret the data (that would be bad) - either way the // definitions won't line up with the appropriate data value and you won't know which one is missing or // added. I can't change the protocol so this is enough argument to just raise an error for config // file/stream mismatch. So for now we'll just throw an exception and deal with consequences :) // Note that this only applies to BPA PDCstream protocol because of external configuration. // Addendum: After running this with several protocol implementations I noticed that if a device wasn't // reporting, the phasor count dropped to zero even if there were phasors defined in the configuration // file, so the only time an exception is thrown is if there are more phasors defined in the the stream // than there are defined in the INI file... // At least this number of phasors should be already defined in BPA PDCstream configuration file if (phasors > ConfigurationCell.PhasorDefinitions.Count) { throw new InvalidOperationException( "Stream/Config File Mismatch: Phasor value count in stream (" + phasors + ") does not match defined count in configuration file (" + ConfigurationCell.PhasorDefinitions.Count + ") for " + ConfigurationCell.IDLabel); } // If analog values get a clear definition in INI file at some point, we can validate the number in the // stream to the number in the config file, in the mean time we dyanmically add analog definitions to // configuration cell as needed (they are only defined in data frame of BPA PDCstream) if (analogs > ConfigurationCell.AnalogDefinitions.Count) { for (x = ConfigurationCell.AnalogDefinitions.Count; x < analogs; x++) { ConfigurationCell.AnalogDefinitions.Add(new AnalogDefinition(ConfigurationCell, "Analog " + (x + 1), 1, 0.0D, AnalogType.SinglePointOnWave)); } } // If digital values get a clear definition in INI file at some point, we can validate the number in the // stream to the number in the config file, in the mean time we dyanmically add digital definitions to // configuration cell as needed (they are only defined in data frame of BPA PDCstream) if (digitals > ConfigurationCell.DigitalDefinitions.Count) { for (x = ConfigurationCell.DigitalDefinitions.Count; x < digitals; x++) { ConfigurationCell.DigitalDefinitions.Add(new DigitalDefinition(ConfigurationCell, "Digital Word " + (x + 1))); } } // Unlike most all other protocols the counts defined for phasors, analogs and digitals in the data frame // may not exactly match what's defined in the configuration frame as these values are defined in an external // INI file for BPA PDCstream. As a result, we manually assign the counts to the parsing state so that these // will be the counts used to parse values from data frame in the base class ParseBodyImage method state.PhasorCount = phasors; state.AnalogCount = analogs; state.DigitalCount = digitals; // Status flags and remaining data elements will parsed by base class in the ParseBodyImage method return(index - startIndex); }