365 lines
16 KiB
C#
365 lines
16 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
|
|
using TBF.Rig.RegisterReaders.GenesisRegReader.communication;
|
|
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
|
using TBF.Rig.Sequences;
|
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
|
|
|
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
|
|
{
|
|
[TestClass]
|
|
public class GenesisReaderTests
|
|
{
|
|
|
|
[TestMethod]
|
|
public void Debug_ProcessOptoLine_FormatVariants()
|
|
{
|
|
var reader = new GenesisSmartReader();
|
|
InitializeReaderForTest(reader);
|
|
|
|
string[] variants =
|
|
{
|
|
"@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
|
|
"@he 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
|
|
"@he\t1\t0\t0A1F59C4\t00017A43\t00115C45\t72E1596B\t00000400\t00001998\t7D91B652\t7D4F37E6\t000191E6\t0C\t062E4A9C\t5331",
|
|
"@he \t1\t0\t0A1F59C4\t00017A43\t00115C45\t72E1596B\t00000400\t00001998\t7D91B652\t7D4F37E6\t000191E6\t0C\t062E4A9C\t5331"
|
|
};
|
|
|
|
for (int i = 0; i < variants.Length; i++)
|
|
{
|
|
bool blockCompleted = false;
|
|
reader.ProcessOptoLine(variants[i], DataStreamState.ProcessAndSave, out blockCompleted);
|
|
|
|
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
|
|
Console.WriteLine($"Variant {i}: {variants[i]}");
|
|
Console.WriteLine($" blockCompleted={blockCompleted}");
|
|
Console.WriteLine($" optoDataCount={optoDataCount}");
|
|
Console.WriteLine("--------------------------------");
|
|
}
|
|
|
|
Assert.Fail("Inspect which variant, if any, is accepted.");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RealInput_ShouldCalculate_StartEndVolumes_AndTimes()
|
|
{
|
|
var reader = new GenesisSmartReader();
|
|
InitializeReaderForTest(reader);
|
|
|
|
string[] realInputLines = LoadRealInputLines();
|
|
Assert.IsTrue(realInputLines.Length > 0, "No test input lines were provided.");
|
|
|
|
int processedCount = 0;
|
|
int firstValidIx = -1;
|
|
int lastValidIx = -1;
|
|
|
|
Console.WriteLine("=== BEGIN INPUT PARSING ===");
|
|
|
|
for (int i = 0; i < realInputLines.Length; i++)
|
|
{
|
|
string line = realInputLines[i];
|
|
|
|
bool blockCompleted = false;
|
|
Exception parseException = null;
|
|
|
|
try
|
|
{
|
|
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
parseException = ex;
|
|
}
|
|
|
|
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
|
|
Console.WriteLine($"[{i}] line = {line}");
|
|
Console.WriteLine($"[{i}] blockCompleted = {blockCompleted}");
|
|
Console.WriteLine($"[{i}] optoDataCount = {optoDataCount}");
|
|
|
|
if (parseException != null)
|
|
{
|
|
Console.WriteLine($"[{i}] EXCEPTION = {parseException.GetType().Name}: {parseException.Message}");
|
|
}
|
|
|
|
if (optoDataCount > processedCount)
|
|
{
|
|
if (firstValidIx < 0)
|
|
firstValidIx = processedCount;
|
|
|
|
lastValidIx = optoDataCount - 1;
|
|
processedCount = optoDataCount;
|
|
|
|
Console.WriteLine(
|
|
$"[{i}] ACCEPTED -> processedCount={processedCount}, firstValidIx={firstValidIx}, lastValidIx={lastValidIx}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"[{i}] NOT ACCEPTED");
|
|
}
|
|
|
|
Console.WriteLine("--------------------------------------------------");
|
|
}
|
|
|
|
Console.WriteLine("=== END INPUT PARSING ===");
|
|
Console.WriteLine($"Final processedCount = {processedCount}");
|
|
Console.WriteLine($"Final firstValidIx = {firstValidIx}");
|
|
Console.WriteLine($"Final lastValidIx = {lastValidIx}");
|
|
|
|
if (processedCount <= 0)
|
|
{
|
|
Assert.Fail(
|
|
"No valid calibration telegrams were parsed.\n" +
|
|
"Check the following:\n" +
|
|
"1. exact telegram prefix (for example @h vs @he)\n" +
|
|
"2. exact separators (spaces vs tabs)\n" +
|
|
"3. exact CRC / checksum\n" +
|
|
"4. whether ProcessOptoLine expects a complete multi-line block format\n" +
|
|
"See test output for per-line diagnostics.");
|
|
}
|
|
|
|
reader.TestStartTelegramIx = firstValidIx;
|
|
reader.TestEndTelegramIx = lastValidIx;
|
|
|
|
Console.WriteLine("=== BEFORE POST PROCESSING ===");
|
|
Console.WriteLine($"TestStartTelegramIx = {reader.TestStartTelegramIx}");
|
|
Console.WriteLine($"TestEndTelegramIx = {reader.TestEndTelegramIx}");
|
|
|
|
object[] markArgs = { 0, 0 };
|
|
|
|
try
|
|
{
|
|
InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
|
|
Console.WriteLine("AddTestStartEndMarksToData OK");
|
|
|
|
InvokePrivate(reader, "DataStreamPostProcessing");
|
|
Console.WriteLine("DataStreamPostProcessing OK");
|
|
|
|
InvokePrivate(reader, "PrepareCalculatedChannelData");
|
|
Console.WriteLine("PrepareCalculatedChannelData OK");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Assert.Fail(
|
|
"Post-processing failed.\n" +
|
|
$"Exception: {ex.GetType().Name}: {ex.Message}\n" +
|
|
$"StackTrace:\n{ex.StackTrace}");
|
|
}
|
|
|
|
Console.WriteLine("=== CALCULATED VALUES ===");
|
|
Console.WriteLine($"NoSamples = {reader.NoSamples}");
|
|
Console.WriteLine($"VolumeLtrStart = {reader.VolumeLtrStart}");
|
|
Console.WriteLine($"VolumeLtrEnd = {reader.VolumeLtrEnd}");
|
|
Console.WriteLine($"TimestampSecStart = {reader.TimestampSecStart}");
|
|
Console.WriteLine($"TimestampSecEnd = {reader.TimestampSecEnd}");
|
|
|
|
Assert.IsFalse(double.IsNaN(reader.VolumeLtrStart), "VolumeLtrStart is NaN");
|
|
Assert.IsFalse(double.IsNaN(reader.VolumeLtrEnd), "VolumeLtrEnd is NaN");
|
|
Assert.IsFalse(double.IsNaN(reader.TimestampSecStart), "TimestampSecStart is NaN");
|
|
Assert.IsFalse(double.IsNaN(reader.TimestampSecEnd), "TimestampSecEnd is NaN");
|
|
|
|
Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart,
|
|
$"Timestamp ordering invalid: start={reader.TimestampSecStart}, end={reader.TimestampSecEnd}");
|
|
|
|
Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart,
|
|
$"Volume ordering invalid: start={reader.VolumeLtrStart}, end={reader.VolumeLtrEnd}");
|
|
|
|
// When parser input is finally correct, replace these expected values:
|
|
const double expectedVolumeStart = 0.0;
|
|
const double expectedVolumeEnd = 0.0;
|
|
const double expectedTimeStart = 0.0;
|
|
const double expectedTimeEnd = 0.0;
|
|
const double tolerance = 0.000001;
|
|
|
|
Console.WriteLine("=== EXPECTED VS ACTUAL ===");
|
|
Console.WriteLine($"expectedVolumeStart = {expectedVolumeStart}, actual = {reader.VolumeLtrStart}");
|
|
Console.WriteLine($"expectedVolumeEnd = {expectedVolumeEnd}, actual = {reader.VolumeLtrEnd}");
|
|
Console.WriteLine($"expectedTimeStart = {expectedTimeStart}, actual = {reader.TimestampSecStart}");
|
|
Console.WriteLine($"expectedTimeEnd = {expectedTimeEnd}, actual = {reader.TimestampSecEnd}");
|
|
|
|
Assert.AreEqual(expectedVolumeStart, reader.VolumeLtrStart, tolerance, "VolumeLtrStart mismatch");
|
|
Assert.AreEqual(expectedVolumeEnd, reader.VolumeLtrEnd, tolerance, "VolumeLtrEnd mismatch");
|
|
Assert.AreEqual(expectedTimeStart, reader.TimestampSecStart, tolerance, "TimestampSecStart mismatch");
|
|
Assert.AreEqual(expectedTimeEnd, reader.TimestampSecEnd, tolerance, "TimestampSecEnd mismatch");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RealInput_ShouldSupport_RolloverNormalization()
|
|
{
|
|
var reader = new GenesisSmartReader();
|
|
InitializeReaderForTest(reader);
|
|
|
|
string[] realInputLines = LoadRealInputLinesWithRollover();
|
|
Assert.IsTrue(realInputLines.Length > 0, "No rollover input lines were provided.");
|
|
|
|
for (int i = 0; i < realInputLines.Length; i++)
|
|
{
|
|
string line = realInputLines[i];
|
|
|
|
bool blockCompleted;
|
|
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted);
|
|
|
|
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
|
|
Console.WriteLine($"[{i}] line={line}");
|
|
Console.WriteLine($"[{i}] optoDataCount={optoDataCount}, blockCompleted={blockCompleted}");
|
|
}
|
|
|
|
int finalOptoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
|
|
Assert.IsTrue(
|
|
finalOptoDataCount > 1,
|
|
"Need at least 2 valid telegrams. Check exact telegram format / CRC in LoadRealInputLinesWithRollover().");
|
|
|
|
reader.TestStartTelegramIx = 0;
|
|
reader.TestEndTelegramIx = finalOptoDataCount - 1;
|
|
|
|
object[] markArgs = { 0, 0 };
|
|
InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
|
|
InvokePrivate(reader, "DataStreamPostProcessing");
|
|
InvokePrivate(reader, "PrepareCalculatedChannelData");
|
|
|
|
Console.WriteLine($"VolumeLtrStart={reader.VolumeLtrStart}");
|
|
Console.WriteLine($"VolumeLtrEnd={reader.VolumeLtrEnd}");
|
|
Console.WriteLine($"TimestampSecStart={reader.TimestampSecStart}");
|
|
Console.WriteLine($"TimestampSecEnd={reader.TimestampSecEnd}");
|
|
|
|
Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart,
|
|
"Normalized end time should be >= start time");
|
|
Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart,
|
|
"Normalized end volume should be >= start volume");
|
|
}
|
|
|
|
private static void InitializeReaderForTest(GenesisSmartReader reader)
|
|
{
|
|
const int channelCount = 3;
|
|
|
|
SetPrivateField(reader, "volumeRawExtLast", new double[channelCount]);
|
|
SetPrivateField(reader, "timestampExtLast", new double[channelCount]);
|
|
|
|
SetPrivateField(reader, "lastTimestamp", new double[channelCount]);
|
|
SetPrivateField(reader, "timestampSec", Enumerable.Repeat(double.NaN, channelCount).ToArray());
|
|
SetPrivateField(reader, "timestampSec0", Enumerable.Repeat(double.NaN, channelCount).ToArray());
|
|
|
|
SetPrivateField(reader, "lastVolumeRaw", new double[channelCount]);
|
|
SetPrivateField(reader, "volumeLtr", Enumerable.Repeat(double.NaN, channelCount).ToArray());
|
|
SetPrivateField(reader, "volumeLtr0", Enumerable.Repeat(double.NaN, channelCount).ToArray());
|
|
|
|
var optoData = new OptoTelegramRaw[GenesisSmartReader.OptoDataBufferSize];
|
|
for (int i = 0; i < optoData.Length; i++)
|
|
optoData[i] = new OptoTelegramRaw();
|
|
|
|
SetPrivateField(reader, "optoData", optoData);
|
|
SetPrivateField(reader, "optoDataCount", 0);
|
|
SetPrivateField(reader, "toBeFlushed", new OptoTelegramRaw());
|
|
|
|
SetPrivateField(reader, "flowDirectionDetection", new FlowDirectionDetection());
|
|
SetPrivateField(reader, "dataStreamState", DataStreamState.ProcessAndSave);
|
|
SetPrivateField(reader, "synchronized", false);
|
|
SetPrivateField(reader, "synchronized2", false);
|
|
SetPrivateField(reader, "partOfTelegram", string.Empty);
|
|
SetPrivateField(reader, "startDataProcessing", true);
|
|
|
|
reader.TestStartTelegramIx = 0;
|
|
reader.TestEndTelegramIx = 0;
|
|
}
|
|
|
|
private static string[] LoadRealInputLines()
|
|
{
|
|
return new[]
|
|
{
|
|
// Replace these with REAL, unchanged captured telegram lines from the device.
|
|
"@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
|
|
"@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
|
|
"@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
|
|
};
|
|
}
|
|
|
|
private static string[] LoadRealInputLinesWithRollover()
|
|
{
|
|
return new[]
|
|
{
|
|
// Replace these with REAL, unchanged captured telegram lines spanning the rollover case.
|
|
"@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
|
|
"@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
|
|
"@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
|
|
};
|
|
}
|
|
|
|
private static void SetPrivateField(object target, string fieldName, object value)
|
|
{
|
|
Type type = target.GetType();
|
|
|
|
while (type != null)
|
|
{
|
|
var field = type.GetField(
|
|
fieldName,
|
|
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
|
|
|
if (field != null)
|
|
{
|
|
field.SetValue(target, value);
|
|
return;
|
|
}
|
|
|
|
type = type.BaseType;
|
|
}
|
|
|
|
Assert.Fail($"Field '{fieldName}' not found.");
|
|
}
|
|
|
|
private static T GetPrivateField<T>(object target, string fieldName)
|
|
{
|
|
Type type = target.GetType();
|
|
|
|
while (type != null)
|
|
{
|
|
var field = type.GetField(
|
|
fieldName,
|
|
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
|
|
|
if (field != null)
|
|
return (T)field.GetValue(target);
|
|
|
|
type = type.BaseType;
|
|
}
|
|
|
|
Assert.Fail($"Field '{fieldName}' not found.");
|
|
return default;
|
|
}
|
|
|
|
private static object InvokePrivate(object target, string methodName, object[] args = null)
|
|
{
|
|
Type type = target.GetType();
|
|
|
|
while (type != null)
|
|
{
|
|
var methods = type.GetMethods(
|
|
BindingFlags.Instance |
|
|
BindingFlags.NonPublic |
|
|
BindingFlags.Public |
|
|
BindingFlags.DeclaredOnly)
|
|
.Where(m => m.Name == methodName)
|
|
.ToList();
|
|
|
|
foreach (var method in methods)
|
|
{
|
|
if (args == null && method.GetParameters().Length == 0)
|
|
return method.Invoke(target, null);
|
|
|
|
if (args != null && method.GetParameters().Length == args.Length)
|
|
return method.Invoke(target, args);
|
|
}
|
|
|
|
type = type.BaseType;
|
|
}
|
|
|
|
Assert.Fail($"Method '{methodName}' not found.");
|
|
return null;
|
|
}
|
|
}
|
|
} |