/// <summary> /// Parses a common header instance that implements <see cref="ICommonHeader{TTypeIdentifier}"/> for the output type represented /// in the binary image. /// </summary> /// <param name="buffer">Buffer containing data to parse.</param> /// <param name="offset">Offset index into buffer that represents where to start parsing.</param> /// <param name="length">Maximum length of valid data from offset.</param> /// <returns>The <see cref="ICommonHeader{TTypeIdentifier}"/> which includes a type ID for the <see cref="Type"/> to be parsed.</returns> /// <remarks> /// <para> /// Derived classes need to provide a common header instance (i.e., class that implements <see cref="ICommonHeader{TTypeIdentifier}"/>) /// for the output types; this will primarily include an ID of the <see cref="Type"/> that the data image represents. This parsing is /// only for common header information, actual parsing will be handled by output type via its <see cref="ISupportBinaryImage.ParseBinaryImage"/> /// method. This header image should also be used to add needed complex state information about the output type being parsed if needed. /// </para> /// <para> /// If there is not enough buffer available to parse common header (as determined by <paramref name="length"/>), return null. Also, if /// the protocol allows frame length to be determined at the time common header is being parsed and there is not enough buffer to parse /// the entire frame, it will be optimal to prevent further parsing by returning null. /// </para> /// </remarks> protected override ICommonHeader <int> ParseCommonHeader(byte[] buffer, int offset, int length) { int scanLength; // Calculate a maximum reasonable scan size for the buffer if (length > Common.MaximumPracticalFrameSize) { scanLength = Common.MaximumPracticalFrameSize; } else { scanLength = length; } // See if there is enough data in the buffer to parse the common frame header by scanning for the F-NET termination byte if (scanLength > 0 && Array.IndexOf(buffer, Common.EndByte, offset, scanLength) >= 0) { // Pre-parse F-NET data row... CommonFrameHeader parsedFrameHeader = new CommonFrameHeader(buffer, offset, length); int parsedLength = parsedFrameHeader.ParsedLength; if (parsedLength > 0) { // Create configuration frame if it doesn't exist if (m_configurationFrame == null) { string[] data = parsedFrameHeader.DataElements; // Create virtual configuration frame m_configurationFrame = new ConfigurationFrame(ushort.Parse(data[Element.UnitID]), DateTime.UtcNow.Ticks, m_frameRate, m_nominalFrequency, m_timeOffset, m_stationName); // Notify clients of new configuration frame OnReceivedChannelFrame(m_configurationFrame); } if (m_configurationFrame != null) { // Assign common header and data frame parsing state parsedFrameHeader.State = new DataFrameParsingState(parsedLength, m_configurationFrame, DataCell.CreateNewCell, TrustHeaderLength, ValidateDataFrameCheckSum); // Expose the frame buffer image in case client needs this data for any reason OnReceivedFrameBufferImage(FundamentalFrameType.DataFrame, buffer, offset, parsedLength); return(parsedFrameHeader); } } } else if (scanLength == Common.MaximumPracticalFrameSize) { throw new InvalidOperationException(string.Format("Possible bad F-NET data stream, scanned {0} bytes without finding an expected termination byte 0x0", Common.MaximumPracticalFrameSize)); } return(null); }
/// <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> /// <remarks> /// The longitude, latitude and number of satellites arrive at the top of minute in F-NET data as the analog /// data in a single row, each on their own row, as sample 1, 2, and 3 respectively. /// </remarks> protected override int ParseBodyImage(byte[] buffer, int startIndex, int length) { DataFrame parent = Parent; CommonFrameHeader commonHeader = parent.CommonHeader; string[] data = commonHeader.DataElements; ConfigurationCell configurationCell = ConfigurationCell; uint sampleIndex; // Attempt to parse sample index if (uint.TryParse(data[Element.SampleIndex], out sampleIndex)) { parent.SampleIndex = sampleIndex; // Get timestamp of data record parent.Timestamp = configurationCell.TimeOffset + ParseTimestamp(data[Element.Date], data[Element.Time], parent.SampleIndex, configurationCell.FrameRate); // Parse out first analog value (can be long/lat at top of minute) m_analogValue = double.Parse(data[Element.Analog]); if (data[Element.Time].Length >= 7 && int.Parse(data[Element.Time].Substring(4, 2)) == 0) { switch (parent.SampleIndex) { case 1: configurationCell.Latitude = m_analogValue; break; case 2: configurationCell.Longitude = m_analogValue; break; case 3: configurationCell.NumberOfSatellites = (int)m_analogValue; break; } } // Update (or create) frequency value double frequency = double.Parse(data[Element.Frequency]); if (FrequencyValue != null) { FrequencyValue.Frequency = frequency; } else { FrequencyValue = new FrequencyValue(this, configurationCell.FrequencyDefinition as FrequencyDefinition, frequency, 0.0D); } // Update (or create) phasor value Angle angle = double.Parse(data[Element.Angle]); double magnitude = double.Parse(data[Element.Voltage]); PhasorValue phasor = null; if (PhasorValues.Count > 0) { phasor = PhasorValues[0] as PhasorValue; } if (phasor != null) { phasor.Angle = angle; phasor.Magnitude = magnitude; } else { phasor = new PhasorValue(this, configurationCell.PhasorDefinitions[0] as PhasorDefinition, angle, magnitude); PhasorValues.Add(phasor); } } return(commonHeader.ParsedLength); }