Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8f6af7112 | ||
|
|
684513ee59 |
@@ -1407,11 +1407,22 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
}
|
||||
|
||||
public string WriteQ3Calibration(TestMethodCfg cfg, Test test, WaterMeter wm)
|
||||
{
|
||||
return WriteQ3Calibration(cfg, test, wm, test);
|
||||
}
|
||||
|
||||
/// <param name="test">The preceding Q3 measurement test whose result receives the factors.</param>
|
||||
/// <param name="validationTest">The current Write Q3 Calibration activity; its error limits validate the factors.</param>
|
||||
public string WriteQ3Calibration(TestMethodCfg cfg, Test test, WaterMeter wm, Test validationTest)
|
||||
{
|
||||
if (genesisHead != null && genesisHead.DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
if (test == null || wm == null) return "Missing Genesis meter or test";
|
||||
if (!genesisHead.CalculateQ3Calibration() || !genesisHead.Q3CalibValid)
|
||||
double errorLimitLo;
|
||||
double errorLimitHi;
|
||||
GenesisSmartReader.TryGetEffectiveQ3CalibrationErrorLimits(validationTest == null ? 0 : validationTest.ErrLimLo,
|
||||
validationTest == null ? 0 : validationTest.ErrLimHi, out errorLimitLo, out errorLimitHi);
|
||||
if (!genesisHead.CalculateQ3CalibrationWithErrorLimits(errorLimitLo, errorLimitHi) || !genesisHead.Q3CalibValid)
|
||||
return "Q3 simulation requires valid measurement data";
|
||||
var result = ProcessData.BatchRslts.GetTestRslt(test.Name, test.Part);
|
||||
StoreCalibrationValuesResults(result, genesisHead, wm);
|
||||
@@ -1434,7 +1445,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
return "Missing Test";
|
||||
}
|
||||
|
||||
return Task.Run(() => WriteQ3Calibration_Async(genesisHead, cfg, test, wm))
|
||||
return Task.Run(() => WriteQ3Calibration_Async(genesisHead, cfg, test, wm, validationTest))
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
@@ -1443,7 +1454,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
|
||||
private async Task<string> WriteQ3Calibration_Async(GenesisSmartReader genesisSmartReader,
|
||||
TestMethodCfg cfg,
|
||||
Test test, WaterMeter wm)
|
||||
Test test, WaterMeter wm, Test validationTest)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1489,7 +1500,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
|
||||
CancellationToken token = default;
|
||||
|
||||
bool areInitialisedData = genesisSmartReader.CalculateQ3Calibration();
|
||||
Test effectiveValidationTest = validationTest ?? test;
|
||||
double errorLimitLo;
|
||||
double errorLimitHi;
|
||||
bool procedureLimitsConfigured = GenesisSmartReader.TryGetEffectiveQ3CalibrationErrorLimits(
|
||||
effectiveValidationTest == null ? 0 : effectiveValidationTest.ErrLimLo,
|
||||
effectiveValidationTest == null ? 0 : effectiveValidationTest.ErrLimHi,
|
||||
out errorLimitLo, out errorLimitHi);
|
||||
log.Info($"WriteQ3Calibration_Async( Slot: {genesisSmartReader.GetSlotNr}) - Q3 factor validation limits: {errorLimitLo} .. {errorLimitHi}% ({(procedureLimitsConfigured ? "Write Q3 Calibration activity" : "default")})");
|
||||
|
||||
bool areInitialisedData = genesisSmartReader.CalculateQ3CalibrationWithErrorLimits(errorLimitLo, errorLimitHi);
|
||||
if (!areInitialisedData)
|
||||
{
|
||||
log.Error("WriteQ3Calibration_Async() - CalculateQ3Calibration Initialised Data not valid");
|
||||
@@ -1501,7 +1521,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
if (!genesisSmartReader.Q3CalibValid)
|
||||
{
|
||||
log.Error("WriteQ3Calibration_Async() - Q3Channel not valid");
|
||||
return "Q3Channel not valid";
|
||||
return $"Q3Channel not valid ({errorLimitLo} .. {errorLimitHi}%)";
|
||||
}
|
||||
|
||||
//Get Activity Status
|
||||
@@ -2056,4 +2076,4 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3928,6 +3928,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>Compatibility limit used when the Write Q3 Calibration activity has no error limits configured.</summary>
|
||||
public const double DefaultQ3CalibrationFactorErrorLimit = 5.0;
|
||||
|
||||
private double[] q3CalibInitial = {Double.NaN,Double.NaN,Double.NaN};
|
||||
private bool[] isChQ3CalibValid = { false,false,false};
|
||||
private double[] q3CalibCh = {Double.NaN,Double.NaN,Double.NaN};
|
||||
@@ -3979,6 +3982,17 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
|
||||
|
||||
public bool CalculateQ3Calibration()
|
||||
{
|
||||
return CalculateQ3CalibrationWithErrorLimits(-DefaultQ3CalibrationFactorErrorLimit,
|
||||
DefaultQ3CalibrationFactorErrorLimit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates Q3 factors using the limits configured on the procedure activity that writes them.
|
||||
/// A pair for which the high limit is not greater than the low limit (the normal unset 0 / 0
|
||||
/// value) falls back to the historical +/- 5 % validation.
|
||||
/// </summary>
|
||||
public bool CalculateQ3CalibrationWithErrorLimits(double errorLimitLo, double errorLimitHi)
|
||||
{
|
||||
if (Double.IsNaN(refVolume) || Double.IsInfinity(refVolume) || refVolume <= 0 || Double.IsNaN(refTime) || Double.IsInfinity(refTime) || refTime <= 0)
|
||||
{
|
||||
@@ -3986,13 +4000,24 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
return false;
|
||||
}
|
||||
|
||||
CalculateQ3Calibration( refVolume, refTime);
|
||||
CalculateQ3Calibration(refVolume, refTime, errorLimitLo, errorLimitHi);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CalculateQ3Calibration(double refVolume, double refTime)
|
||||
{
|
||||
GetQ3Calibration(refVolume, refTime, q3CalibInitial, ref isChQ3CalibValid,ref q3DiffPercentageCalibCh, ref q3CalibCh);
|
||||
CalculateQ3Calibration(refVolume, refTime, -DefaultQ3CalibrationFactorErrorLimit,
|
||||
DefaultQ3CalibrationFactorErrorLimit);
|
||||
}
|
||||
|
||||
public void CalculateQ3Calibration(double refVolume, double refTime, double errorLimitLo, double errorLimitHi)
|
||||
{
|
||||
double effectiveErrorLimitLo;
|
||||
double effectiveErrorLimitHi;
|
||||
TryGetEffectiveQ3CalibrationErrorLimits(errorLimitLo, errorLimitHi,
|
||||
out effectiveErrorLimitLo, out effectiveErrorLimitHi);
|
||||
GetQ3Calibration(refVolume, refTime, q3CalibInitial, effectiveErrorLimitLo,
|
||||
effectiveErrorLimitHi, ref isChQ3CalibValid, ref q3DiffPercentageCalibCh, ref q3CalibCh);
|
||||
}
|
||||
|
||||
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor, ref bool[] isChQ3CalibValid, ref double[] q3CalibCh)
|
||||
@@ -4002,6 +4027,36 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
|
||||
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor, ref bool[] isChQ3CalibValid, ref double[] calibDiffPercent, ref double[] q3CalibCh)
|
||||
{
|
||||
GetQ3Calibration(refVolume, refTime, initCalibFactor,
|
||||
-DefaultQ3CalibrationFactorErrorLimit, DefaultQ3CalibrationFactorErrorLimit,
|
||||
ref isChQ3CalibValid, ref calibDiffPercent, ref q3CalibCh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the error limits used to validate a calculated Q3 factor. TBF stores an unset
|
||||
/// limit pair as equal values (normally 0 / 0), so only an ordered finite pair is considered set.
|
||||
/// </summary>
|
||||
public static bool TryGetEffectiveQ3CalibrationErrorLimits(double errorLimitLo, double errorLimitHi,
|
||||
out double effectiveErrorLimitLo, out double effectiveErrorLimitHi)
|
||||
{
|
||||
if (!Double.IsNaN(errorLimitLo) && !Double.IsInfinity(errorLimitLo) &&
|
||||
!Double.IsNaN(errorLimitHi) && !Double.IsInfinity(errorLimitHi) &&
|
||||
errorLimitHi > errorLimitLo)
|
||||
{
|
||||
effectiveErrorLimitLo = errorLimitLo;
|
||||
effectiveErrorLimitHi = errorLimitHi;
|
||||
return true;
|
||||
}
|
||||
|
||||
effectiveErrorLimitLo = -DefaultQ3CalibrationFactorErrorLimit;
|
||||
effectiveErrorLimitHi = DefaultQ3CalibrationFactorErrorLimit;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor,
|
||||
double errorLimitLo, double errorLimitHi, ref bool[] isChQ3CalibValid,
|
||||
ref double[] calibDiffPercent, ref double[] q3CalibCh)
|
||||
{
|
||||
log.Debug("=== Q3 CALIBRATION START ===");
|
||||
|
||||
@@ -4021,7 +4076,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug($"Inputs: refVolume={refVolume}, refTime={refTime}, initCalibFactors={string.Join(",", initCalibFactor)}");
|
||||
log.Debug($"Inputs: refVolume={refVolume}, refTime={refTime}, initCalibFactors={string.Join(",", initCalibFactor)}, errorLimits={errorLimitLo}..{errorLimitHi}%");
|
||||
|
||||
if (_rawStartEndByChannel == null)
|
||||
{
|
||||
@@ -4110,10 +4165,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
|
||||
q3CalibCh[iChannel] = (refVolume / recalculatedDeltaVolume) * initCalibFactor[iChannel];
|
||||
double diffPercent = Math.Abs((initCalibFactor[iChannel] - q3CalibCh[iChannel] ) / initCalibFactor[iChannel]) * 100.0;
|
||||
isChQ3CalibValid[iChannel] = diffPercent <= 5.0 && !double.IsNaN(q3CalibCh[iChannel]) && !double.IsInfinity(q3CalibCh[iChannel]) && q3CalibCh[iChannel] >= 1 && q3CalibCh[iChannel] <= ushort.MaxValue;
|
||||
// Procedure error limits are an ordered range, e.g. -2 .. +2, so validation
|
||||
// must retain the direction of the factor change. Keep the persisted difference
|
||||
// absolute for compatibility with the existing calibration-result fields.
|
||||
double signedDiffPercent = ((q3CalibCh[iChannel] - initCalibFactor[iChannel]) / initCalibFactor[iChannel]) * 100.0;
|
||||
double diffPercent = Math.Abs(signedDiffPercent);
|
||||
isChQ3CalibValid[iChannel] = signedDiffPercent >= errorLimitLo && signedDiffPercent <= errorLimitHi &&
|
||||
!double.IsNaN(q3CalibCh[iChannel]) && !double.IsInfinity(q3CalibCh[iChannel]) &&
|
||||
q3CalibCh[iChannel] >= 1 && q3CalibCh[iChannel] <= ushort.MaxValue;
|
||||
calibDiffPercent[iChannel] = diffPercent;
|
||||
log.Debug($"Calculated Q3Calib Ch[{iChannel}] ={q3CalibCh[iChannel]} DiffPercent={diffPercent}% isValid[{isChQ3CalibValid[iChannel]}] IninitCalibFactor={initCalibFactor}");
|
||||
log.Debug($"Calculated Q3Calib Ch[{iChannel}] ={q3CalibCh[iChannel]} SignedDiffPercent={signedDiffPercent}% DiffPercent={diffPercent}% isValid[{isChQ3CalibValid[iChannel]}] IninitCalibFactor={initCalibFactor}");
|
||||
}
|
||||
|
||||
log.Debug("=== Q3 CALIBRATION END ===");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
///
|
||||
///
|
||||
/// Copyright (c) 2015-2019 Sensus Metering Systems
|
||||
/// Author: Milan Hanajík
|
||||
///
|
||||
@@ -7,8 +7,44 @@ using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||
|
||||
namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
{
|
||||
/// <summary>Tracks one completion per worker, activity and group.</summary>
|
||||
public sealed class GenesisWorkerGroupCompletion
|
||||
{
|
||||
private readonly int workerCount;
|
||||
private readonly System.Collections.Generic.HashSet<int> completed = new System.Collections.Generic.HashSet<int>();
|
||||
private int activity, group;
|
||||
private bool released;
|
||||
public GenesisWorkerGroupCompletion(int workerCount)
|
||||
{
|
||||
if (workerCount < 1 || workerCount > 10) throw new ArgumentOutOfRangeException(nameof(workerCount));
|
||||
this.workerCount = workerCount;
|
||||
}
|
||||
public void Begin(int activityStep, int groupNumber)
|
||||
{
|
||||
lock (completed) { activity = activityStep; group = groupNumber; released = false; completed.Clear(); }
|
||||
}
|
||||
public bool Complete(int activityStep, int groupNumber, int worker)
|
||||
{
|
||||
lock (completed)
|
||||
{
|
||||
if (released || activityStep != activity || groupNumber != group || worker < 0 || worker >= workerCount) return false;
|
||||
if (!completed.Add(worker) || completed.Count != workerCount) return false;
|
||||
released = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public static System.Collections.Generic.IEnumerable<int> BoardIndexes(int worker, int workers, int boards)
|
||||
{
|
||||
if (workers < 1 || workers > 10 || worker < 0 || worker >= workers || boards < 0) throw new ArgumentOutOfRangeException();
|
||||
for (int index = worker; index < boards; index += workers) yield return index;
|
||||
}
|
||||
}
|
||||
|
||||
public class CommCompletedEventArgs : EventArgs
|
||||
{
|
||||
public bool WorkerGroupCompleted;
|
||||
public int ActivityStep;
|
||||
public int Group;
|
||||
public int ThreadId;
|
||||
public int WMNr0; /// 0-based water meter position
|
||||
public GenesisSmartReader Ihead;
|
||||
|
||||
@@ -204,14 +204,14 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
static IList<iPerlCommunicationParams> multiTestParams;
|
||||
|
||||
|
||||
static int currentActivityStep;
|
||||
static int currentGroup; /// form -> worker thread (0 = none)
|
||||
static volatile int currentActivityStep;
|
||||
static volatile int currentGroup; /// form -> worker thread (0 = none)
|
||||
static int lastGroup;
|
||||
static int completedCommCount; /// Number of completed communication steps
|
||||
private GenesisWorkerGroupCompletion groupCompletion;
|
||||
|
||||
static IList<Thread> workerThreads;
|
||||
static IList<int> muxBrdOrGroup14Nrs;
|
||||
static bool stopWorkerThreads; /// form -> worker thread
|
||||
static volatile bool stopWorkerThreads; /// form -> worker thread
|
||||
|
||||
|
||||
/// <summary> Parameterless constructor (without watermeters, threads) </summary>
|
||||
@@ -384,7 +384,6 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
///
|
||||
currentActivityStep = 0;
|
||||
currentGroup = 0;
|
||||
completedCommCount = 0;
|
||||
stopWorkerThreads = false;
|
||||
|
||||
/// group numbers are >=1, lastGroup == 0 means there is no group
|
||||
@@ -409,6 +408,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
if (!muxBrdOrGroup14Nrs.Contains(iPerl.MuxBoardNrOrGroup14)) muxBrdOrGroup14Nrs.Add(iPerl.MuxBoardNrOrGroup14);
|
||||
}
|
||||
|
||||
groupCompletion = new GenesisWorkerGroupCompletion(workerThreads.Count);
|
||||
groupCompletion.Begin(0, 1);
|
||||
log.InfoFormat("Genesis workers configured: Threads={0}, Group1Count={1}, LastGroup2={2}", workerThreads.Count, muxBrdOrGroup14Nrs.Count, lastGroup);
|
||||
log.WarnFormat("nrThreads = {0}", workerThreads.Count);
|
||||
}
|
||||
|
||||
@@ -666,6 +668,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
|
||||
for (int i = 0; i < multiTestParams.Count; i++ )
|
||||
{
|
||||
while (activityStep != currentActivityStep && !stopWorkerThreads) Thread.Sleep(50);
|
||||
if (stopWorkerThreads) break;
|
||||
Test currentTest = tests[i];
|
||||
iPerlCommunicationParams currentTestParams = multiTestParams[i];
|
||||
string currentActivity = currentTestParams.Activity; /// Current activity
|
||||
@@ -707,7 +711,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
}
|
||||
|
||||
//HOLD ON - if activity is like HoldSlotStr, ignore loop and do activity like HoldSlotStr
|
||||
if (currentActivity.ToLower().Equals(HoldSlotStr.ToLower()))
|
||||
bool isHoldActivity = currentActivity.ToLower().Equals(HoldSlotStr.ToLower());
|
||||
if (isHoldActivity)
|
||||
{
|
||||
|
||||
log.Debug($"Activity '{currentActivity}' is HoldSlotStr, skipping loop and performing HoldOn operation. Thread: {threadID}");
|
||||
@@ -716,7 +721,6 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
string resultStr = string.Empty;
|
||||
error = HoldOn(threadID, ref resultStr); // wait for the hold on to complete - one time for each thread
|
||||
ProcessWaterMetersDialogConfirmation(threadID, resultStr,error);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int group = 1; group <= lastGroup; group++)
|
||||
@@ -729,10 +733,12 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
|
||||
if (stopWorkerThreads) break;
|
||||
|
||||
if (!isHoldActivity)
|
||||
{
|
||||
#if TURA_SPECIAL
|
||||
int threadIx = threadID; /// Just one thread for TURA_SPECIAL
|
||||
#else
|
||||
for (int threadIx = threadID; threadIx < threadID + 10; threadIx += cfg.NrThreads)// - max threads = 4 => 10
|
||||
foreach (int threadIx in GenesisWorkerGroupCompletion.BoardIndexes(threadID, workerThreads.Count, muxBrdOrGroup14Nrs.Count))
|
||||
#endif
|
||||
{
|
||||
bool wmFound = false;
|
||||
@@ -884,8 +890,13 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
if (stopWorkerThreads) break;
|
||||
}
|
||||
|
||||
if (stopWorkerThreads) break;
|
||||
} /// for (int group
|
||||
}
|
||||
|
||||
if (stopWorkerThreads) break;
|
||||
log.DebugFormat("Genesis worker group completed: Activity={0}, Group2={1}, Worker={2}", activityStep, group, threadID);
|
||||
OnCommCompleted(null, new CommCompletedEventArgs(threadID, -1, null, null, string.Empty, CommErr.None)
|
||||
{ WorkerGroupCompleted = true, ActivityStep = activityStep, Group = group });
|
||||
} /// for (int group
|
||||
|
||||
TBF.UiBridge.Bridge.OnTestProgress(null, new TBF.UiBridge.TestProgressEventArgs(tests[i], Progress.Completed));
|
||||
activityStep++;
|
||||
@@ -1077,7 +1088,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
BeforeTest = currentTest;
|
||||
}
|
||||
|
||||
var gciFullLoginResult = iHead.OptoHeadTest.WriteQ3Calibration(cfg, BeforeTest, wm);
|
||||
// Keep the Q3 measurement result on the preceding test, but validate against the
|
||||
// limits configured on the current "Write Q3 Calibration Slot" activity.
|
||||
var gciFullLoginResult = iHead.OptoHeadTest.WriteQ3Calibration(cfg, BeforeTest, wm, currentTest);
|
||||
if (!string.IsNullOrEmpty(gciFullLoginResult) &&
|
||||
gciFullLoginResult.Equals(TBF.Rig.RegisterReaders.GenesisRegReader.communication.OptoHeadTest.ResultOk))
|
||||
{
|
||||
@@ -1608,21 +1621,17 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
}
|
||||
}
|
||||
|
||||
#if !TURA_SPECIAL
|
||||
///
|
||||
/// Branch
|
||||
///
|
||||
lock (this)
|
||||
{
|
||||
if (++completedCommCount < 4) return;
|
||||
completedCommCount = 0;
|
||||
}
|
||||
#endif
|
||||
// Per-meter results update the UI only. Advance after every worker
|
||||
// has finished all of its Group 1 assignments for this Group 2.
|
||||
if (!data.WorkerGroupCompleted || !groupCompletion.Complete(data.ActivityStep, data.Group, data.ThreadId)) return;
|
||||
log.InfoFormat("Genesis group completed: Activity={0}, Group2={1}, Workers={2}", data.ActivityStep, data.Group, workerThreads.Count);
|
||||
|
||||
|
||||
if (currentGroup < lastGroup)
|
||||
{
|
||||
/// Go to the next step / next group
|
||||
currentGroup++;
|
||||
groupCompletion.Begin(currentActivityStep, currentGroup);
|
||||
}
|
||||
else if (currentActivityStep + 1 < multiTestParams.Count)
|
||||
{
|
||||
@@ -1630,16 +1639,17 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
currentActivityStep++;
|
||||
activityLabel.Text = multiTestParams[currentActivityStep].Activity;
|
||||
currentGroup++;
|
||||
groupCompletion.Begin(currentActivityStep, currentGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Debug("Wait for finish workerThreads 4 seconds");
|
||||
log.Debug("Waiting for all Genesis workers; results remain visible for 4 seconds afterwards.");
|
||||
|
||||
Thread lastThread = workerThreads[data.ThreadId];
|
||||
Thread[] finishingThreads = workerThreads.ToArray();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
lastThread.Join(2000);
|
||||
foreach (var thread in finishingThreads) thread.Join();
|
||||
|
||||
if (!CanUseUi()) return;
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.TestMethods.GenesisCommunication;
|
||||
|
||||
namespace TBFTests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("GenesisParallelScheduling")]
|
||||
public class GenesisParallelSchedulingTests
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow(1)] [DataRow(2)] [DataRow(3)] [DataRow(4)] [DataRow(5)]
|
||||
[DataRow(6)] [DataRow(7)] [DataRow(8)] [DataRow(9)] [DataRow(10)]
|
||||
public void EveryBoardIsAssignedExactlyOnceForAllSupportedWorkerCounts(int workers)
|
||||
{
|
||||
foreach (int boards in new[] { 0, 1, 2, 7, 10 })
|
||||
{
|
||||
var actual = Enumerable.Range(0, workers)
|
||||
.SelectMany(worker => GenesisWorkerGroupCompletion.BoardIndexes(worker, workers, boards)).OrderBy(x => x).ToArray();
|
||||
CollectionAssert.AreEqual(Enumerable.Range(0, boards).ToArray(), actual);
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(1)] [DataRow(2)] [DataRow(3)] [DataRow(4)] [DataRow(5)]
|
||||
[DataRow(6)] [DataRow(7)] [DataRow(8)] [DataRow(9)] [DataRow(10)]
|
||||
public void GroupAdvancesOnlyAfterEveryDistinctWorkerFinishes(int count)
|
||||
{
|
||||
var completion = new GenesisWorkerGroupCompletion(count);
|
||||
completion.Begin(0, 1);
|
||||
Assert.IsFalse(completion.Complete(0, 2, 0));
|
||||
Assert.IsFalse(completion.Complete(1, 1, 0));
|
||||
Assert.IsFalse(completion.Complete(0, 1, -1));
|
||||
Assert.IsFalse(completion.Complete(0, 1, count));
|
||||
for (int worker = 0; worker < count; worker++)
|
||||
{
|
||||
Assert.AreEqual(worker == count - 1, completion.Complete(0, 1, worker));
|
||||
Assert.IsFalse(completion.Complete(0, 1, worker), "Duplicate completion must not advance the group.");
|
||||
}
|
||||
completion.Begin(0, 2);
|
||||
Assert.IsFalse(completion.Complete(0, 1, 0), "Late event from the previous group.");
|
||||
for (int worker = count - 1; worker >= 0; worker--)
|
||||
Assert.AreEqual(worker == 0, completion.Complete(0, 2, worker));
|
||||
completion.Begin(1, 1);
|
||||
Assert.IsFalse(completion.Complete(0, 2, 0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MultipleActivitiesAndGroupsRequireAllWorkersIncludingIdleWorkers()
|
||||
{
|
||||
var completion = new GenesisWorkerGroupCompletion(10);
|
||||
for (int activity = 0; activity < 3; activity++)
|
||||
for (int group = 1; group <= 10; group++)
|
||||
{
|
||||
completion.Begin(activity, group);
|
||||
Assert.IsFalse(completion.Complete(activity - 1, group, 0));
|
||||
Assert.IsFalse(completion.Complete(activity, group - 1, 0));
|
||||
// Also models HOLD ON: no board requests, but each worker must finish.
|
||||
for (int worker = 0; worker < 10; worker++)
|
||||
Assert.AreEqual(worker == 9, completion.Complete(activity, group, worker));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TenWorkersCanRunConcurrentlyAndSlowTenthWorkerHoldsGroup()
|
||||
{
|
||||
var completion = new GenesisWorkerGroupCompletion(10);
|
||||
completion.Begin(0, 1);
|
||||
using (var started = new CountdownEvent(10))
|
||||
using (var firstNine = new CountdownEvent(9))
|
||||
using (var run = new ManualResetEventSlim(false))
|
||||
using (var slow = new ManualResetEventSlim(false))
|
||||
{
|
||||
int advances = 0;
|
||||
var threads = Enumerable.Range(0, 10).Select(worker => new Thread(() =>
|
||||
{
|
||||
started.Signal();
|
||||
run.Wait();
|
||||
if (worker == 9) slow.Wait();
|
||||
if (completion.Complete(0, 1, worker)) Interlocked.Increment(ref advances);
|
||||
if (worker != 9) firstNine.Signal();
|
||||
}) { IsBackground = true }).ToArray();
|
||||
foreach (var thread in threads) thread.Start();
|
||||
try
|
||||
{
|
||||
Assert.IsTrue(started.Wait(5000), "All ten workers must start before any finishes.");
|
||||
run.Set();
|
||||
Assert.IsTrue(firstNine.Wait(5000));
|
||||
Assert.AreEqual(0, Volatile.Read(ref advances), "Nine completions must not release a ten-worker group.");
|
||||
slow.Set();
|
||||
}
|
||||
finally
|
||||
{
|
||||
run.Set(); slow.Set();
|
||||
foreach (var thread in threads) thread.Join(5000);
|
||||
}
|
||||
Assert.AreEqual(1, advances);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parallel_Group1OneToTen_Group2One_AllTenCallsOverlap()
|
||||
{
|
||||
VerifyProcessingScenario(10, 10, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serial_Group1One_Group2OneToTen_NoCallsOverlap()
|
||||
{
|
||||
VerifyProcessingScenario(10, 1, 10);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Combined_FiveGroup1Boards_TwoGroup2Groups_ParallelWithinSerialBetween()
|
||||
{
|
||||
VerifyProcessingScenario(10, 5, 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Combined_FourWorkers_TenBoards_ThreeGroups_EveryMeterRunsOnce()
|
||||
{
|
||||
VerifyProcessingScenario(4, 10, 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serial_OneWorker_TenGroup1Boards_AllCallsRunOnce()
|
||||
{
|
||||
VerifyProcessingScenario(1, 10, 1);
|
||||
}
|
||||
|
||||
// Hardware-free harness using the production assignment and completion helpers.
|
||||
// Calls are held at a rendezvous, so overlap is proven without timing guesses.
|
||||
private static void VerifyProcessingScenario(int workers, int boards, int groups)
|
||||
{
|
||||
var completion = new GenesisWorkerGroupCompletion(workers);
|
||||
completion.Begin(0, 1);
|
||||
var sync = new object();
|
||||
int currentGroup = 1, active = 0, advances = 0;
|
||||
bool abort = false;
|
||||
var calls = new int[groups, boards];
|
||||
var finished = new int[groups];
|
||||
var peaks = new int[groups];
|
||||
var failures = new System.Collections.Generic.List<Exception>();
|
||||
int parallelism = Math.Min(workers, boards);
|
||||
var rendezvous = Enumerable.Range(0, groups).Select(_ => new CountdownEvent(parallelism)).ToArray();
|
||||
var threads = Enumerable.Range(0, workers).Select(worker => new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int group = 1; group <= groups; group++)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
while (currentGroup != group && !abort)
|
||||
if (!Monitor.Wait(sync, 10000)) throw new TimeoutException("Group did not advance.");
|
||||
if (abort) return;
|
||||
}
|
||||
bool firstCall = true;
|
||||
foreach (int board in GenesisWorkerGroupCompletion.BoardIndexes(worker, workers, boards))
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
for (int previous = 0; previous < group - 1; previous++)
|
||||
Assert.AreEqual(boards, finished[previous], "Next Group 2 started before previous group completed.");
|
||||
calls[group - 1, board]++;
|
||||
active++;
|
||||
peaks[group - 1] = Math.Max(peaks[group - 1], active);
|
||||
}
|
||||
if (firstCall)
|
||||
{
|
||||
rendezvous[group - 1].Signal();
|
||||
Assert.IsTrue(rendezvous[group - 1].Wait(10000), "Assigned workers did not enter calls concurrently.");
|
||||
firstCall = false;
|
||||
}
|
||||
lock (sync) { active--; finished[group - 1]++; }
|
||||
}
|
||||
if (completion.Complete(0, group, worker))
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
Assert.AreEqual(boards, finished[group - 1]);
|
||||
Assert.AreEqual(0, active);
|
||||
advances++;
|
||||
completion.Begin(0, group + 1);
|
||||
currentGroup++;
|
||||
Monitor.PulseAll(sync);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
lock (sync) { failures.Add(error); abort = true; Monitor.PulseAll(sync); }
|
||||
}
|
||||
}) { IsBackground = true }).ToArray();
|
||||
foreach (var thread in threads) thread.Start();
|
||||
bool allJoined = true;
|
||||
foreach (var thread in threads) allJoined &= thread.Join(15000);
|
||||
Assert.IsTrue(allJoined, "Workers did not terminate.");
|
||||
foreach (var item in rendezvous) item.Dispose();
|
||||
Assert.AreEqual(0, failures.Count, string.Join("\n", failures.Select(x => x.ToString())));
|
||||
Assert.AreEqual(groups, advances);
|
||||
for (int group = 0; group < groups; group++)
|
||||
{
|
||||
Assert.AreEqual(parallelism, peaks[group], "Unexpected maximum concurrent calls.");
|
||||
for (int board = 0; board < boards; board++)
|
||||
Assert.AreEqual(1, calls[group, board], "Meter was skipped or called more than once.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.communication;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader
|
||||
{
|
||||
// Golden frames have CRCs generated independently with Python binascii.crc_hqx.
|
||||
// No ports, databases, GCI engine or production-code changes are required.
|
||||
[TestClass]
|
||||
[TestCategory("GenesisReadRegression")]
|
||||
public class GenesisReadDataRegressionTests
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000000 00008000 00400000 00800000 FFFFF000 0C 00018000 8D2C", 1, 1024, 0.001024)]
|
||||
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000200 00008000 00400000 00800000 FFFFF000 0C 00018000 7BAC", 1, 512, 0.002048)]
|
||||
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 700D", 1, 1024, 0.001024)]
|
||||
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000800 00008000 00400000 00800000 FFFFF000 0C 00018000 674F", 1, 2048, 0.000512)]
|
||||
[DataRow("@h 2 0 10000000 FFFFFF00 00000400 00100000 00000000 00008000 00400000 00800000 FFFFF000 0C 00018000 3F43", 2, 1024, 0.001024)]
|
||||
[DataRow("@h 2 0 10000000 FFFFFF00 00000400 00100000 00000200 00008000 00400000 00800000 FFFFF000 0C 00018000 C9C3", 2, 512, 0.002048)]
|
||||
[DataRow("@h 2 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 C262", 2, 1024, 0.001024)]
|
||||
[DataRow("@h 2 0 10000000 FFFFFF00 00000400 00100000 00000800 00008000 00400000 00800000 FFFFF000 0C 00018000 D520", 2, 2048, 0.000512)]
|
||||
[DataRow("@h 3 0 10000000 FFFFFF00 00000400 00100000 00000000 00008000 00400000 00800000 FFFFF000 0C 00018000 A179", 3, 1024, 0.001024)]
|
||||
[DataRow("@h 3 0 10000000 FFFFFF00 00000400 00100000 00000200 00008000 00400000 00800000 FFFFF000 0C 00018000 57F9", 3, 512, 0.002048)]
|
||||
[DataRow("@h 3 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 5C58", 3, 1024, 0.001024)]
|
||||
[DataRow("@h 3 0 10000000 FFFFFF00 00000400 00100000 00000800 00008000 00400000 00800000 FFFFF000 0C 00018000 4B1A", 3, 2048, 0.000512)]
|
||||
public void ProtocolH_GoldenFramesPreserveChannelUnitsAndScale(string frame, int channel, int scale, double volume)
|
||||
{
|
||||
var decoder = new StreamingDecoder();
|
||||
Assert.IsTrue(decoder.DecodeMsg(frame));
|
||||
var data = decoder.DataCalib;
|
||||
Assert.IsNotNull(data);
|
||||
Assert.IsTrue(data.IsValid);
|
||||
Assert.AreEqual(channel, data.Channel);
|
||||
Assert.AreEqual(scale, data.VolumeScaleRawPerMl);
|
||||
Assert.AreEqual(volume, data.VolumeCm, 1e-12);
|
||||
Assert.AreEqual(1048576d, data.AccuVolumeRaw);
|
||||
Assert.AreEqual(1024d, data.DeltaVolumeRaw);
|
||||
Assert.AreEqual(volume / 1024, data.DeltaVolumeQm, 1e-15);
|
||||
Assert.AreEqual(1.5, data.TimeS, 1e-12);
|
||||
Assert.AreEqual(.5, data.SampleIntervalS, 1e-12);
|
||||
Assert.AreEqual(.001, data.AmplitudeUpV, 1e-12);
|
||||
Assert.AreEqual(.002, data.AmplitudeDownV, 1e-12);
|
||||
Assert.AreEqual(-1d, data.TemperatureDegC, 1e-12);
|
||||
Assert.AreEqual(65536d, data.OverflowTimeS);
|
||||
Assert.IsNull(decoder.DataFlowTest);
|
||||
double previousVolume = double.NaN, previousTime = double.NaN;
|
||||
var telegram = new OptoTelegramRaw();
|
||||
telegram.UpdateFromSmart(data, 17, 2.5f, ref previousVolume, ref previousTime);
|
||||
Assert.AreEqual(channel - 1, telegram.iChannel);
|
||||
Assert.AreEqual(volume * 1000, telegram.VolumeRawExt, 1e-9);
|
||||
Assert.AreEqual(1.5, telegram.TimestampExt, 1e-12);
|
||||
Assert.AreEqual(17, telegram.Counter);
|
||||
Assert.AreEqual(2.5f, telegram.RefFlow);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("@h 1 0 00000000 00000000 00000400 00100000 00000400 00008000 00400000 00800000 00000000 0C 00018000 0A39", 0, 0, 0)]
|
||||
[DataRow("@h 1 0 7FFFFFFF 7FFFFFFF 00000400 00100000 00000400 00008000 00400000 00800000 7FFFFFFF 0C 00018000 89B0", 2147483647, 2147483647, 2147483647)]
|
||||
[DataRow("@h 1 0 80000000 80000000 00000400 00100000 00000400 00008000 00400000 00800000 80000000 0C 00018000 3E06", -2147483648, -2147483648, -2147483648)]
|
||||
[DataRow("@h 1 0 FFFFFFFF FFFFFFFF 00000400 00100000 00000400 00008000 00400000 00800000 FFFFFFFF 0C 00018000 6870", -1, -1, -1)]
|
||||
public void ProtocolH_SignedFieldsPreserveTwosComplement(string frame, int total, int delta, int temperature)
|
||||
{
|
||||
var decoder = new StreamingDecoder();
|
||||
Assert.IsTrue(decoder.DecodeMsg(frame));
|
||||
var data = decoder.DataCalib;
|
||||
Assert.AreEqual(total, data.RawTotalTimeOfFlight);
|
||||
Assert.AreEqual(delta, data.RawDeltaTimeOfFlight);
|
||||
Assert.AreEqual(total / 274877906944d, data.TotalTimeOfFlightS, 1e-15);
|
||||
Assert.AreEqual(delta / 274877906944d, data.DeltaTimeOfFlightS, 1e-15);
|
||||
Assert.AreEqual(temperature / 4096d, data.TemperatureDegC, 1e-10);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("@f 00000000 00000000 F603", 0.0, 0.0)]
|
||||
[DataRow("@f 7FFFFFFF FFFFFFFF 3996", 2147.483647, 65535.99998474121)]
|
||||
[DataRow("@f FFFFFFFF 00010000 21AD", -1e-06, 1.0)]
|
||||
[DataRow("@f 80000000 00008000 0541", -2147.483648, 0.5)]
|
||||
public void ProtocolF_PreservesSignedVolumeAndUnsignedTime(string frame, double volume, double time)
|
||||
{
|
||||
var decoder = new StreamingDecoder();
|
||||
Assert.IsTrue(decoder.DecodeMsg(frame));
|
||||
Assert.IsNotNull(decoder.DataFlowTest);
|
||||
Assert.IsTrue(decoder.DataFlowTest.IsValid);
|
||||
Assert.AreEqual(volume, decoder.DataFlowTest.VolumeCm, 1e-9);
|
||||
Assert.AreEqual(time, decoder.DataFlowTest.TimeS, 1e-12);
|
||||
Assert.IsNull(decoder.DataCalib);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow("garbage")]
|
||||
[DataRow("@h")]
|
||||
[DataRow("@h 1")]
|
||||
[DataRow("@h 1 invalid")]
|
||||
[DataRow("@f 00000001 00010000 ZZZZ")]
|
||||
[DataRow("@h\t1\t0\t10000000\tFFFFFF00\t00000400\t00100000\t00000400\t00008000\t00400000\t00800000\tFFFFF000\t0C\t00018000\t700D")]
|
||||
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 700D ")]
|
||||
[DataRow("@he 1 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 700D")]
|
||||
public void InvalidOrUnsupportedInputProducesNoMeasurement(string frame)
|
||||
{
|
||||
var decoder = new StreamingDecoder();
|
||||
decoder.DecodeMsg(frame);
|
||||
Assert.IsNull(decoder.DataCalib);
|
||||
Assert.IsNull(decoder.DataFlowTest);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(1)]
|
||||
[DataRow(2)]
|
||||
[DataRow(3)]
|
||||
[DataRow(4)]
|
||||
[DataRow(5)]
|
||||
[DataRow(6)]
|
||||
[DataRow(7)]
|
||||
[DataRow(8)]
|
||||
[DataRow(9)]
|
||||
[DataRow(10)]
|
||||
[DataRow(11)]
|
||||
[DataRow(12)]
|
||||
[DataRow(13)]
|
||||
public void AlteringAnyCalibrationFieldWithoutUpdatingCrcRejectsRecord(int field)
|
||||
{
|
||||
var words = Golden.Split(' ');
|
||||
words[field] = words[field] == "0" ? "1" : "0";
|
||||
var decoder = new StreamingDecoder();
|
||||
Assert.IsFalse(decoder.DecodeMsg(string.Join(" ", words)));
|
||||
Assert.IsNull(decoder.DataCalib);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("en-US")]
|
||||
[DataRow("sk-SK")]
|
||||
[DataRow("de-DE")]
|
||||
public void HexDecodingIsIndependentOfCulture(string culture)
|
||||
{
|
||||
var previous = System.Threading.Thread.CurrentThread.CurrentCulture;
|
||||
try
|
||||
{
|
||||
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
|
||||
var decoder = new StreamingDecoder();
|
||||
Assert.IsTrue(decoder.DecodeMsg(Golden));
|
||||
Assert.AreEqual(.001024, decoder.DataCalib.VolumeCm, 1e-12);
|
||||
Assert.AreEqual(-1d, decoder.DataCalib.TemperatureDegC);
|
||||
}
|
||||
finally { System.Threading.Thread.CurrentThread.CurrentCulture = previous; }
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DiagnosticModeRetainsBadCrcButMarksRecordInvalid()
|
||||
{
|
||||
var corrupted = Golden.Substring(0, Golden.Length - 4) + "0000";
|
||||
var decoder = new StreamingDecoder(false);
|
||||
Assert.IsFalse(decoder.DecodeMsg(corrupted));
|
||||
Assert.IsNotNull(decoder.DataCalib);
|
||||
Assert.IsFalse(decoder.DataCalib.IsValid);
|
||||
Assert.AreEqual(.001024, decoder.DataCalib.VolumeCm, 1e-12);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CrcMatchesIndependentCcittFalseCheckVector()
|
||||
{
|
||||
Assert.AreEqual((ushort)0x29B1, Crc16Ccitt.CalculateMsb1021(Encoding.ASCII.GetBytes("123456789")));
|
||||
Assert.AreEqual((ushort)0xFFFF, Crc16Ccitt.CalculateMsb1021(new byte[0]));
|
||||
}
|
||||
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.StopQueueData = false;
|
||||
|
||||
reader.TestStartTelegramIx = 0;
|
||||
reader.TestEndTelegramIx = 0;
|
||||
}
|
||||
|
||||
private static void SetPrivateField(object target, string name, object value)
|
||||
{
|
||||
var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.IsNotNull(field, name);
|
||||
field.SetValue(target, value);
|
||||
}
|
||||
private static T ReadField<T>(object target, string name)
|
||||
{
|
||||
return (T)target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(target);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessOptoLinePreservesPayloadAndCounterThroughReaderPipeline()
|
||||
{
|
||||
var reader = new GenesisSmartReader(new TBF.Rig.RegisterReaders.GenesisRegReader.GenesisCfg(new TBF.Rig.RegisterReaders.GenesisRegReader.Factory()));
|
||||
InitializeReaderForTest(reader);
|
||||
bool complete;
|
||||
reader.ProcessOptoLine(Golden, DataStreamState.ProcessAndSave, out complete);
|
||||
Assert.IsFalse(complete);
|
||||
Assert.AreEqual(1, ReadField<int>(reader, "optoDataCount"));
|
||||
var rows = ReadField<OptoTelegramRaw[]>(reader, "optoData");
|
||||
Assert.AreEqual(0, rows[0].iChannel);
|
||||
Assert.AreEqual(1.024, rows[0].VolumeRawExt, 1e-9);
|
||||
Assert.AreEqual(1.5, rows[0].TimestampExt, 1e-12);
|
||||
Assert.AreEqual(0, rows[0].Counter);
|
||||
reader.ProcessOptoLine("invalid telegram", DataStreamState.ProcessAndSave, out complete);
|
||||
Assert.AreEqual(1, ReadField<int>(reader, "optoDataCount"));
|
||||
reader.ProcessOptoLine(Golden, DataStreamState.ProcessAndSave, out complete);
|
||||
Assert.AreEqual(2, ReadField<int>(reader, "optoDataCount"));
|
||||
Assert.AreEqual(1, rows[1].Counter);
|
||||
Assert.AreEqual(rows[0].VolumeRawExt, rows[1].VolumeRawExt, 1e-9);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("stop")]
|
||||
[DataRow("queue")]
|
||||
[DataRow("processing")]
|
||||
public void ReaderStopGatesPreventMeasurementsFromBeingAppended(string gate)
|
||||
{
|
||||
var reader = new GenesisSmartReader(new TBF.Rig.RegisterReaders.GenesisRegReader.GenesisCfg(new TBF.Rig.RegisterReaders.GenesisRegReader.Factory()));
|
||||
InitializeReaderForTest(reader);
|
||||
if (gate == "stop") SetPrivateField(reader, "_isStopping", true);
|
||||
if (gate == "queue") reader.StopQueueData = true;
|
||||
if (gate == "processing") SetPrivateField(reader, "startDataProcessing", false);
|
||||
bool complete;
|
||||
reader.ProcessOptoLine(Golden, DataStreamState.ProcessAndSave, out complete);
|
||||
Assert.AreEqual(0, ReadField<int>(reader, "optoDataCount"));
|
||||
Assert.IsFalse(complete);
|
||||
}
|
||||
|
||||
private const string Golden = "@h 1 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 700D";
|
||||
}
|
||||
}
|
||||
+35
-1
@@ -236,6 +236,40 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
Assert.AreEqual(15625.0, calib[1], 0.001);
|
||||
Assert.AreEqual(15625.0, calib[2], 0.001);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetQ3Calibration_ShouldUseWriteActivityLimits_AndFallbackWhenTheyAreUnset()
|
||||
{
|
||||
double limitLo;
|
||||
double limitHi;
|
||||
Assert.IsFalse(GenesisSmartReader.TryGetEffectiveQ3CalibrationErrorLimits(0.0, 0.0, out limitLo, out limitHi));
|
||||
Assert.AreEqual(-5.0, limitLo);
|
||||
Assert.AreEqual(5.0, limitHi);
|
||||
Assert.IsTrue(GenesisSmartReader.TryGetEffectiveQ3CalibrationErrorLimits(-2.0, 2.0, out limitLo, out limitHi));
|
||||
Assert.AreEqual(-2.0, limitLo);
|
||||
Assert.AreEqual(2.0, limitHi);
|
||||
|
||||
var sut = new GenesisSmartReader();
|
||||
// At refTime 120 s, this gives a calculated factor 3 % above the initial factor.
|
||||
var raw = CreateKnownStartEndData(
|
||||
(100.0, 100.0 + (200.0 / 1.03 / 12.0), 10.0, 20.0),
|
||||
(200.0, 200.0 + (200.0 / 1.03 / 12.0), 10.0, 20.0),
|
||||
(300.0, 300.0 + (200.0 / 1.03 / 12.0), 10.0, 20.0));
|
||||
SetPrivateField(sut, "_rawStartEndByChannel", raw);
|
||||
SetPrivateField(sut, "_recalculatedStartEndByChannel", raw);
|
||||
SetPrivateField(sut, "optoDataCount", 2);
|
||||
sut.TestStartTelegramIx = 0;
|
||||
sut.TestEndTelegramIx = 1;
|
||||
|
||||
var valid = new bool[3];
|
||||
var differences = new double[3];
|
||||
var factors = new double[3];
|
||||
sut.GetQ3Calibration(200.0, 120.0, ValidInitFactors, -2.0, 2.0,
|
||||
ref valid, ref differences, ref factors);
|
||||
|
||||
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
|
||||
Assert.IsTrue(differences.All(x => x > 2.9 && x < 3.1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CalculateQ3Calibration_ShouldComputeExpectedChannelValues_FromSimulationData()
|
||||
@@ -424,4 +458,4 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
Assert.Fail($"Field '{fieldName}' not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<ItemGroup><Compile Include="GenesisRecoveryTests.cs" /></ItemGroup>
|
||||
<ItemGroup><Compile Include="GenesisRecoveryTests.cs" /><Compile Include="GenesisParallelSchedulingTests.cs" /></ItemGroup>
|
||||
<!-- Project dependencies may copy an older SQLite interop DLL with a newer timestamp. -->
|
||||
<Target Name="EnsureMatchingSQLiteInterop" AfterTargets="Build">
|
||||
<Copy SourceFiles="@(SQLiteInteropFiles)"
|
||||
|
||||
Reference in New Issue
Block a user