243 lines
8.7 KiB
C#
243 lines
8.7 KiB
C#
///
|
|
/// Copyright (c) 2021 Sensus Slovensko a.s.
|
|
///
|
|
using System;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace TBF.Rig.RegisterReaders.S640Stream
|
|
{
|
|
public class DatastreamFrame
|
|
{
|
|
public const int FrameLength = 49; /// Including CR,LF
|
|
public static double VolumeScaleFactor = 1000.0; /// Raw volume is in ml
|
|
public static double TimeScaleFactor = 1000.0; /// Raw time is in ms
|
|
|
|
///
|
|
/// Stored values
|
|
///
|
|
public FrameFlags Flags;
|
|
public DateTime DateTime; /// From PC
|
|
public int Counter;
|
|
public float RefFlow; /// [m3/h]
|
|
|
|
char[] data = new char[FrameLength - 2];
|
|
|
|
public Int32 VolumeRaw;
|
|
public Int64 VolumeRawExt;
|
|
|
|
public Int64 Timestamp;
|
|
public Int64 TimestampExt;
|
|
|
|
///
|
|
/// Calculated values
|
|
///
|
|
public double Volume() { return (double)VolumeRawExt / VolumeScaleFactor; }
|
|
public double TimestampDbl() { return (double)TimestampExt / TimeScaleFactor; }
|
|
|
|
public string Label()
|
|
{
|
|
if (Flags == FrameFlags.OK_FullRound) return " #### end of round ####";
|
|
else if (Flags == FrameFlags.OK_TestStart) return " #### start test ####";
|
|
else if (Flags == FrameFlags.OK_TestEnd) return " #### end of test ####";
|
|
else return string.Empty;
|
|
}
|
|
|
|
|
|
public DatastreamFrame() { }
|
|
|
|
/// <summary>
|
|
/// Parses optical telegram and returns OptoTelegramRaw object
|
|
/// </summary>
|
|
/// <description>
|
|
/// Create a configuration structure from a complete byte array
|
|
///
|
|
/// Telegram description:
|
|
///
|
|
/// AAAAAAAAAAAA[tab]BBBBBBBBBB[tab]CCCCCCCCC[tab]DDDDDDDDDD[tab]EE[cr][lf] (49 bytes)
|
|
///
|
|
/// Data Comment Type Calculate to decimal
|
|
/// ----------------------------------------------------------------
|
|
/// AAAAAAAAAAAA PCB number Decimal
|
|
/// BBBBBBBBBB Radio address Decimal
|
|
/// CCCCCCCCC Meter reading Decimal Volume in ml
|
|
/// DDDDDDDDDD Timestamp Decimal Time in ms
|
|
/// EE Checksum Hexadecimal
|
|
/// ----------------------------------------------------------------
|
|
///
|
|
/// Example:
|
|
/// 540210730770 4291524429 000250215 0433411626 4F
|
|
/// 540210730770 4291524429 000250215 0433411876 56
|
|
/// ...
|
|
/// </description>
|
|
/// <param name="data">A complete byte array data</param>
|
|
/// <returns>true = telegram OK, false = telegram NOK</returns>
|
|
public bool UpdateFromString(string frame, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast, int counter)
|
|
{
|
|
DateTime = DateTime.Now;
|
|
Counter = counter;
|
|
RefFlow = (float)Sequences.ProcessData.RefFlow.Val;
|
|
|
|
if (frame == null || frame.Length < FrameLength ||
|
|
frame[FrameLength - 2] != '\r' || frame[FrameLength - 1] != '\n' ||
|
|
frame[12] != '\t' || frame[23] != '\t' || frame[33] != '\t' || frame[44] != '\t')
|
|
{
|
|
Flags = FrameFlags.InvalidFrame;
|
|
return false;
|
|
}
|
|
|
|
/// Copy data into the fixed size buffer
|
|
int sum = 0;
|
|
for (int i = 0; i < FrameLength - 4; i++)
|
|
{
|
|
data[i] = frame[i];
|
|
sum += (int)frame[i];
|
|
}
|
|
data[FrameLength - 4] = frame[FrameLength - 4];
|
|
data[FrameLength - 3] = frame[FrameLength - 3];
|
|
|
|
byte checkSum;
|
|
bool chksumOk = byte.TryParse(frame.Substring(FrameLength - 4, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out checkSum)
|
|
&& checkSum == (sum & 0xFF);
|
|
|
|
bool volumeOk = Int32.TryParse(frame.Substring(24, 9), out VolumeRaw);
|
|
volumeRawExtLast = VolumeRawExt = VolumeRaw;
|
|
|
|
bool timeOk = Int64.TryParse(frame.Substring(34, 10), out Timestamp);
|
|
timestampExtLast = TimestampExt = Timestamp;
|
|
|
|
bool allOk = volumeOk && timeOk && chksumOk;
|
|
|
|
Flags = allOk ? FrameFlags.OK : FrameFlags.InvalidFrame;
|
|
return allOk;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alternative to UpdateFromString(...) when data are flushed
|
|
/// </summary>
|
|
public bool UpdateFromStringDummy(string frame, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast,
|
|
out Int64 pcbNumber, out Int64 radioAddress)
|
|
{
|
|
pcbNumber = 0;
|
|
radioAddress = 0;
|
|
|
|
if (frame == null || frame.Length < FrameLength ||
|
|
frame[FrameLength - 2] != '\r' || frame[FrameLength - 1] != '\n' ||
|
|
frame[12] != '\t' || frame[23] != '\t' || frame[33] != '\t' || frame[44] != '\t')
|
|
{
|
|
Flags = FrameFlags.InvalidFrame;
|
|
return false;
|
|
}
|
|
|
|
/// Copy data into the fixed size buffer
|
|
int sum = 0;
|
|
for (int i = 0; i < FrameLength - 4; i++) sum += (int)frame[i];
|
|
byte checkSum;
|
|
bool chksumOk = byte.TryParse(frame.Substring(FrameLength - 4, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out checkSum)
|
|
&& checkSum == (sum & 0xFF);
|
|
|
|
bool pcbNumberOk = Int64.TryParse(frame.Substring(0, 12), out pcbNumber);
|
|
|
|
bool radioAddressOk = Int64.TryParse(frame.Substring(13, 10), out radioAddress);
|
|
|
|
bool volumeOk = Int32.TryParse(frame.Substring(24, 9), out VolumeRaw);
|
|
volumeRawExtLast = VolumeRawExt = VolumeRaw;
|
|
|
|
bool timeOk = Int64.TryParse(frame.Substring(34, 10), out Timestamp);
|
|
timestampExtLast = TimestampExt = Timestamp;
|
|
|
|
return chksumOk && pcbNumberOk && radioAddressOk && volumeOk && timeOk;
|
|
}
|
|
|
|
|
|
public void SetFlags(FrameFlags flags)
|
|
{
|
|
this.Flags = flags;
|
|
}
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
if (Flags == FrameFlags.SyncError)
|
|
{
|
|
return "Sychronization error";
|
|
}
|
|
else if (Flags == FrameFlags.InvalidFrame)
|
|
{
|
|
return "Invalid telegram";
|
|
}
|
|
else /// if (flags == OptoTelegramFlags.OK || OptoTelegramFlags.OK_TestStart || OptoTelegramFlags.OK_TestEnd)
|
|
{
|
|
string cntStr = Counter.ToString();
|
|
string indent = (cntStr.Length == 1) ? " " : (cntStr.Length == 2) ? " " : (cntStr.Length == 3) ? " " : "";
|
|
|
|
StringBuilder sb = new StringBuilder(FrameLength - 2);
|
|
for (int i = 0; i < FrameLength - 2; i++) sb.Append(data[i]);
|
|
|
|
return string.Format("{0}:{1}:{2}.{3}\t{4}{5} :\t{6}\tTime={7} Volume={8}{9}",
|
|
DateTime.Hour.ToString("D2"),
|
|
DateTime.Minute.ToString("D2"),
|
|
DateTime.Second.ToString("D2"),
|
|
DateTime.Millisecond.ToString("D3"),
|
|
indent,
|
|
cntStr,
|
|
sb.ToString(),
|
|
TimestampDbl(),
|
|
Volume(),
|
|
Label());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Filter RefFlow data in an array of OptoTelegramRaw objects by a FIR filter:
|
|
///
|
|
/// kSize = 5, kSize2 = 2
|
|
///
|
|
/// i k
|
|
/// ---------------------------------------------------------------------------
|
|
/// 0 -5 filtered[0] = data[0]
|
|
/// 1 -4 filtered[1] = data[1]
|
|
/// 2 -3 filtered[2] = data[0]*k[0] + ... + data[4]*k[4]
|
|
/// 3 -2 filtered[3] = data[1]*k[0] + ... + data[5]*k[4]
|
|
/// 4 -1 filtered[4] = data[2]*k[0] + ... + data[6]*k[4]
|
|
/// 5 0 data[0] = filtered[0], filtered[0] = data[3]*k[0] + ... + data[7]*k[4]
|
|
/// 6 1 data[1] = filtered[1], filtered[1] = data[4]*k[0] + ... + data[8]*k[4]
|
|
/// 7 ...
|
|
/// </summary>
|
|
/// <param name="optoData">array of OptoTelegramRaw objects</param>
|
|
/// <param name="optoDataCount">number of objects to process</param>
|
|
public static void FIRFilterFlow(DatastreamFrame[] optoData, int optoDataCount)
|
|
{
|
|
float[] kernel = new float[] { 0.1f, 0.2f, 0.4f, 0.2f, 0.1f };
|
|
int kSize = kernel.Length;
|
|
int kSize2 = kernel.Length / 2;
|
|
|
|
float[] filtered = new float[kSize];
|
|
|
|
for (int i = 0; i < optoDataCount; i++)
|
|
{
|
|
int k = i - kSize;
|
|
if (k >= 0) optoData[k].RefFlow = filtered[i % kSize];
|
|
|
|
if (i < kSize2 || i >= optoDataCount - kSize2)
|
|
{
|
|
filtered[i % kSize] = optoData[i].RefFlow;
|
|
}
|
|
else
|
|
{
|
|
float weoightedSum = 0;
|
|
for (int j = -kSize2; j <= kSize2; j++)
|
|
weoightedSum += optoData[i + j].RefFlow * kernel[j + kSize2];
|
|
|
|
filtered[i % kSize] = weoightedSum;
|
|
}
|
|
}
|
|
|
|
for (int k = optoDataCount - kSize; k < optoDataCount; k++)
|
|
{
|
|
if (k >= 0) optoData[k].RefFlow = filtered[k % kSize];
|
|
}
|
|
}
|
|
}
|
|
}
|