private void ProcessTSSCMeasurements(IFrame frame) { lock (m_tsscSyncLock) { try { if (!Enabled) { return; } if ((object)m_tsscEncoder == null || m_resetTsscEncoder) { m_resetTsscEncoder = false; m_tsscEncoder = new TsscEncoder(); m_tsscWorkingBuffer = new byte[32 * 1024]; OnStatusMessage(MessageLevel.Info, $"TSSC algorithm reset before sequence number: {m_tsscSequenceNumber}", "TSSC"); m_tsscSequenceNumber = 0; m_tsscEncoder.SetBuffer(m_tsscWorkingBuffer, 0, m_tsscWorkingBuffer.Length); } else { m_tsscEncoder.SetBuffer(m_tsscWorkingBuffer, 0, m_tsscWorkingBuffer.Length); } int count = 0; foreach (IMeasurement measurement in frame.Measurements.Values) { ushort index = m_signalIndexCache.GetSignalIndex(measurement.Key); if (!m_tsscEncoder.TryAddMeasurement(index, measurement.Timestamp.Value, (uint)measurement.StateFlags, (float)measurement.AdjustedValue)) { SendTSSCPayload(frame.Timestamp, count); count = 0; m_tsscEncoder.SetBuffer(m_tsscWorkingBuffer, 0, m_tsscWorkingBuffer.Length); // This will always succeed m_tsscEncoder.TryAddMeasurement(index, measurement.Timestamp.Value, (uint)measurement.StateFlags, (float)measurement.AdjustedValue); } count++; } if (count > 0) { SendTSSCPayload(frame.Timestamp, count); } // Update latency statistics long publishTime = DateTime.UtcNow.Ticks; m_parent.UpdateLatencyStatistics(frame.Measurements.Values.Select(m => (long)(publishTime - m.Timestamp))); } catch (Exception ex) { string message = $"Error processing measurements: {ex.Message}"; OnProcessException(MessageLevel.Info, new InvalidOperationException(message, ex)); } } }
/// <summary> /// Generates binary image of the <see cref="CompactMeasurement"/> and copies it into the given buffer, for <see cref="ISupportBinaryImage.BinaryLength"/> bytes. /// </summary> /// <param name="buffer">Buffer used to hold generated binary image of the source object.</param> /// <param name="startIndex">0-based starting index in the <paramref name="buffer"/> to start writing.</param> /// <returns>The number of bytes written to the <paramref name="buffer"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="startIndex"/> or <see cref="ISupportBinaryImage.BinaryLength"/> is less than 0 -or- /// <paramref name="startIndex"/> and <see cref="ISupportBinaryImage.BinaryLength"/> will exceed <paramref name="buffer"/> length. /// </exception> /// <remarks> /// <para> /// Field: Bytes: <br/> /// -------- -------<br/> /// Flags 1 <br/> /// ID 2 <br/> /// Value 4 <br/> /// [Time] 2? <br/> /// </para> /// <para> /// Constant Length = 7<br/> /// Variable Length = 0, 2, 4 or 8 (i.e., total size is 7, 9, 11 or 15) /// </para> /// </remarks> public int GenerateBinaryImage(byte[] buffer, int startIndex) { // Call to binary length property caches result of m_usingBaseTimeOffset int length = BinaryLength; buffer.ValidateParameters(startIndex, length); // Encode compact state flags CompactMeasurementStateFlags flags = StateFlags.MapToCompactFlags(); if (m_timeIndex != 0) { flags |= CompactMeasurementStateFlags.TimeIndex; } if (m_usingBaseTimeOffset) { flags |= CompactMeasurementStateFlags.BaseTimeOffset; } // Added flags to beginning of buffer buffer[startIndex++] = (byte)flags; // Encode runtime ID EndianOrder.BigEndian.CopyBytes(m_signalIndexCache.GetSignalIndex(ID), buffer, startIndex); startIndex += 2; // Encode adjusted value (accounts for adder and multipler) EndianOrder.BigEndian.CopyBytes((float)AdjustedValue, buffer, startIndex); startIndex += 4; if (m_includeTime) { if (m_usingBaseTimeOffset) { if (m_useMillisecondResolution) { // Encode 2-byte millisecond offset timestamp EndianOrder.BigEndian.CopyBytes((ushort)(Timestamp - m_baseTimeOffsets[m_timeIndex]).ToMilliseconds(), buffer, startIndex); } else { // Encode 4-byte ticks offset timestamp EndianOrder.BigEndian.CopyBytes((uint)((long)Timestamp - m_baseTimeOffsets[m_timeIndex]), buffer, startIndex); } } else { // Encode 8-byte full fidelity timestamp EndianOrder.BigEndian.CopyBytes((long)Timestamp, buffer, startIndex); } } return(length); }
private void ProcessMeasurements(IEnumerable <IMeasurement> measurements) { // Includes data packet flags and measurement count const int PacketHeaderSize = DataPublisher.ClientResponseHeaderSize + 5; List <IBinaryMeasurement> packet; int packetSize; bool usePayloadCompression; bool useCompactMeasurementFormat; BufferBlockMeasurement bufferBlockMeasurement; byte[] bufferBlock; ushort bufferBlockSignalIndex; IBinaryMeasurement binaryMeasurement; int binaryLength; try { if (!Enabled) { return; } packet = new List <IBinaryMeasurement>(); packetSize = PacketHeaderSize; usePayloadCompression = m_usePayloadCompression; useCompactMeasurementFormat = m_useCompactMeasurementFormat || usePayloadCompression; foreach (IMeasurement measurement in measurements) { bufferBlockMeasurement = measurement as BufferBlockMeasurement; if ((object)bufferBlockMeasurement != null) { // Still sending buffer block measurements to client; we are expecting // confirmations which will indicate whether retransmission is necessary, // so we will restart the retransmission timer m_bufferBlockRetransmissionTimer.Stop(); // Handle buffer block measurements as a special case - this can be any kind of data, // measurement subscriber will need to know how to interpret buffer bufferBlock = new byte[6 + bufferBlockMeasurement.Length]; // Prepend sequence number BigEndian.CopyBytes(m_bufferBlockSequenceNumber, bufferBlock, 0); m_bufferBlockSequenceNumber++; // Copy signal index into buffer bufferBlockSignalIndex = m_signalIndexCache.GetSignalIndex(bufferBlockMeasurement.ID); BigEndian.CopyBytes(bufferBlockSignalIndex, bufferBlock, 4); // Append measurement data and send Buffer.BlockCopy(bufferBlockMeasurement.Buffer, 0, bufferBlock, 6, bufferBlockMeasurement.Length); m_parent.SendClientResponse(m_workingBuffer, m_clientID, ServerResponse.BufferBlock, ServerCommand.Subscribe, bufferBlock); lock (m_bufferBlockCacheLock) { // Cache buffer block for retransmission m_bufferBlockCache.Add(bufferBlock); } // Start the retransmission timer in case we never receive a confirmation m_bufferBlockRetransmissionTimer.Start(); } else { // Serialize the current measurement. if (useCompactMeasurementFormat) { binaryMeasurement = new CompactMeasurement(measurement, m_signalIndexCache, m_includeTime, m_baseTimeOffsets, m_timeIndex, m_useMillisecondResolution); } else { binaryMeasurement = new SerializableMeasurement(measurement, m_parent.GetClientEncoding(m_clientID)); } // Determine the size of the measurement in bytes. binaryLength = binaryMeasurement.BinaryLength; // If the current measurement will not fit in the packet based on the max // packet size, process the current packet and start a new packet. if (packetSize + binaryLength > DataPublisher.MaxPacketSize) { ProcessBinaryMeasurements(packet, useCompactMeasurementFormat, usePayloadCompression); packet.Clear(); packetSize = PacketHeaderSize; } // Add the current measurement to the packet. packet.Add(binaryMeasurement); packetSize += binaryLength; } } // Process the remaining measurements. if (packet.Count > 0) { ProcessBinaryMeasurements(packet, useCompactMeasurementFormat, usePayloadCompression); } IncrementProcessedMeasurements(measurements.Count()); // Update latency statistics m_parent.UpdateLatencyStatistics(measurements.Select(m => (long)(m_lastPublishTime - m.Timestamp))); } catch (Exception ex) { string message = string.Format("Error processing measurements: {0}", ex.Message); OnProcessException(new InvalidOperationException(message, ex)); } }