Refactor GenesisSmartReader Q3 calibration tests and logic:

- Expand Q3 calibration tests with diverse scenarios in `GenesisSmartReader_Q3Test`.
- Introduce channel-specific validation for calibration tests.
- Adjust methods for Q3 calibration in `GenesisSmartReader` to support per-channel factors.
- Add utilities for preparing simulation data and refinements for test consistency.
- Update `CliRunner` with improved task management, timeout handling, and logging enhancements.
This commit is contained in:
Michal Buzik 2026-04-17 16:05:16 +02:00
parent 33ef914984
commit 5a8478e782
17 changed files with 1464 additions and 549 deletions

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
[assembly: AssemblyVersion("3.9.3056.1")]
[assembly: AssemblyFileVersion("3.9.3056.1")]
[assembly: AssemblyVersion("3.9.3057.1")]
[assembly: AssemblyFileVersion("3.9.3057.1")]

View File

@ -633,8 +633,31 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public double TimestampSecEndCh1 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 0, 1);
public double TimestampSecEndCh2 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 1, 1);
public double TimestampSecEndCh3 => NoSamples ? 0 : GetCachedTime(_recalculatedStartEndByChannel, 2, 1);
public double VolumeLtrStartRaw => NoSamples ? 0 : AverageCachedVolume(_rawStartEndByChannel, 0);
public double VolumeLtrEndRaw => NoSamples ? 0 : AverageCachedVolume(_rawStartEndByChannel, 1);
public double VolumeLtrStartAverage => NoSamples ? 0 : AverageCachedVolume(_recalculatedStartEndByChannel, 0);
public double VolumeLtrEndAwerage => NoSamples ? 0 : AverageCachedVolume(_recalculatedStartEndByChannel, 1);
public double VolumeLtrStartRawRaw => NoSamples ? 0 : AverageCachedVolume(_rawStartEndByChannel, 0);
public double VolumeLtrEndRawRaw => NoSamples ? 0 : AverageCachedVolume(_rawStartEndByChannel, 1);
public double VolumeLtrStartRawCh1 => NoSamples ? 0 : GetCachedVolume(_rawStartEndByChannel, 0, 0);
public double VolumeLtrStartRawCh2 => NoSamples ? 0 : GetCachedVolume(_rawStartEndByChannel, 1, 0);
public double VolumeLtrStartRawCh3 => NoSamples ? 0 : GetCachedVolume(_rawStartEndByChannel, 2, 0);
public double VolumeLtrEndRawCh1 => NoSamples ? 0 : GetCachedVolume(_rawStartEndByChannel, 0, 1);
public double VolumeLtrEndRawCh2 => NoSamples ? 0 : GetCachedVolume(_rawStartEndByChannel, 1, 1);
public double VolumeLtrEndRawCh3 => NoSamples ? 0 : GetCachedVolume(_rawStartEndByChannel, 2, 1);
public double TimestampSecStartRawCh1 => NoSamples ? 0 : GetCachedTime(_rawStartEndByChannel, 0, 0);
public double TimestampSecStartRawCh2 => NoSamples ? 0 : GetCachedTime(_rawStartEndByChannel, 1, 0);
public double TimestampSecStartRawCh3 => NoSamples ? 0 : GetCachedTime(_rawStartEndByChannel, 2, 0);
public double TimestampSecEndRawCh1 => NoSamples ? 0 : GetCachedTime(_rawStartEndByChannel, 0, 1);
public double TimestampSecEndRawCh2 => NoSamples ? 0 : GetCachedTime(_rawStartEndByChannel, 1, 1);
public double TimestampSecEndRawCh3 => NoSamples ? 0 : GetCachedTime(_rawStartEndByChannel, 2, 1);
@ -643,7 +666,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
get
{
return VolumeLtrStartRaw;
return VolumeLtrStartAverage;
}
}
@ -652,7 +675,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
get
{
return VolumeLtrEndRaw;
return VolumeLtrEndAwerage;
}
}
@ -1302,7 +1325,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
try
{
log.DebugFormat("DataStreamPostProcessing() - harcoded call GetQ3Calibration(200.0, 120.0, 15625.0);");
GetQ3Calibration(200.0, 120.0, 15625.0);
SetQ3Calibration(new double[]{15625.0,15625.0,15625.0 });
CalculateQ3Calibration(200.0, 120.0);
}
catch (Exception ex)
{
@ -3886,19 +3910,60 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
}
#endregion
private double[] q3CalibInitial = {Double.NaN,Double.NaN,Double.NaN};
private bool[] isChQ3CalibValid = { false,false,false};
private double[] q3CalibCh = {Double.NaN,Double.NaN,Double.NaN};
private bool isQ3CalibValid = false;
private double q3Calib = 0;
public bool Q3CalibValid { get => isQ3CalibValid; }
public double Q3CalibValue { get => q3Calib; }
public bool Q3CalibValid
{
get
{
foreach (var b in isChQ3CalibValid)
{
if (!b)
return false;
}
public void GetQ3Calibration(double refVolume, double refTime, double initCalibFactor)
return true;
}
}
public double[] Q3CalibValue { get => q3CalibInitial; }
public bool Q3Calib_Ch1Valid { get => isChQ3CalibValid[0]; }
public bool Q3Calib_Ch2Valid { get => isChQ3CalibValid[1]; }
public bool Q3Calib_Ch3Valid { get => isChQ3CalibValid[2]; }
public double Q3Calib_Ch1Value { get => q3CalibCh[0]; }
public double Q3Calib_Ch2Value { get => q3CalibCh[1]; }
public double Q3Calib_Ch3Value { get => q3CalibCh[2]; }
void SetQ3Calibration(double[] q3CalibInitial) { this.q3CalibInitial = q3CalibInitial; }
public void CalculateQ3Calibration(double refVolume, double refTime)
{
GetQ3Calibration(refVolume, refTime, q3CalibInitial, ref isChQ3CalibValid, ref q3CalibCh);
}
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor, ref bool[] isChQ3CalibValid, ref double[] q3CalibCh)
{
log.Debug("=== Q3 CALIBRATION START ===");
isQ3CalibValid = false;
q3Calib = 0.0;
//initialisation
for (int iChannel = 0; iChannel < ChannelCount; iChannel++)
{
q3CalibCh[iChannel] = 0.0;
isChQ3CalibValid[iChannel] = false;
}
foreach (var init in initCalibFactor)
{
if (Double.IsNaN(init))
{
log.Error("Q3 Calibration: Initial calibration value is NaN");
return;
}
}
log.Debug($"Inputs: refVolume={refVolume}, refTime={refTime}, initCalibFactor={initCalibFactor}");
@ -3914,7 +3979,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return;
}
if (refVolume <= 0 || refTime <= 0 || initCalibFactor <= 0)
if (refVolume <= 0 || refTime <= 0)
{
log.Debug("EXIT: Invalid input values (<= 0)");
return;
@ -3931,6 +3996,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
for (int iChannel = 0; iChannel < ChannelCount; iChannel++)
{
var channelData = _rawStartEndByChannel[iChannel];
if (channelData == null || channelData.Length < 2)
@ -3962,10 +4028,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
log.Debug($" End: T={end.TimestampExt}, V={end.VolumeRawExt}");
log.Debug($" Delta: dT={deltaTime}, dV={deltaVolume}");
double recalculatedDeltaVolume = 0.0f;
if (deltaTime > 0)
{
double timeCoef = refTime / deltaTime;
double recalculatedDeltaVolume = deltaVolume * timeCoef;
double timeCoef = refTime / deltaTime; //?
recalculatedDeltaVolume = deltaVolume * timeCoef;
recalculatedVariablesByChannel[iChannel][1].VolumeRawExt =
recalculatedVariablesByChannel[iChannel][0].VolumeRawExt + recalculatedDeltaVolume;
@ -3979,52 +4046,18 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
recalculatedVariablesByChannel[iChannel][0].TimestampExt = 0;
recalculatedVariablesByChannel[iChannel][1].TimestampExt = refTime;
if (Math.Abs(recalculatedDeltaVolume) <= Double.Epsilon)
{
log.Debug($"Channel {iChannel}: recalculatedDeltaVolume is zero");
continue;
}
double scaling = 15625.0;
log.Debug($"Scaling={scaling}");
q3CalibCh[iChannel] = (refVolume / recalculatedDeltaVolume) * initCalibFactor[iChannel];
double diffPercent = Math.Abs((initCalibFactor[iChannel] - q3CalibCh[iChannel] ) / initCalibFactor[iChannel]) * 100.0;
isChQ3CalibValid[iChannel] = diffPercent <= 5.0;
log.Debug($"Calculated Q3Calib Ch[{iChannel}] ={q3CalibCh[iChannel]} DiffPercent={diffPercent}% isValid[{isChQ3CalibValid[iChannel]}] IninitCalibFactor={initCalibFactor}");
if (scaling <= 0)
{
log.Debug("EXIT: scaling <= 0");
return;
}
double avgRawVolume = AverageCachedVolume(recalculatedVariablesByChannel, 0);
log.Debug($"AverageCachedVolume={avgRawVolume}");
if (avgRawVolume <= 0)
{
log.Debug("EXIT: avgRawVolume <= 0");
return;
}
double measuredVolume = avgRawVolume / scaling;
log.Debug($"MeasuredVolume={measuredVolume}");
if (measuredVolume <= 0)
{
log.Debug("EXIT: measuredVolume <= 0");
return;
}
q3Calib = (refVolume / measuredVolume) * initCalibFactor;
log.Debug($"Q3Calib (raw)={q3Calib}");
double diffPercent =
Math.Abs((q3Calib - initCalibFactor) / initCalibFactor) * 100.0;
log.Debug($"DiffPercent={diffPercent}%");
isQ3CalibValid = diffPercent <= 5.0;
log.Debug($"Validation: {(isQ3CalibValid ? "VALID" : "INVALID")}");
if (!isQ3CalibValid)
{
q3Calib = 0.0;
log.Debug("Q3Calib reset to 0 due to invalid result");
}
log.Debug("=== Q3 CALIBRATION END ===");

View File

@ -13,46 +13,29 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
{
public class CliRunner
{
//static readonly ILog log = LogManager.GetLogger(typeof(CliRunner));
private readonly ILog log;
private List<Task> taskPool = new List<Task>();
private long startTime;
private long incommingTime;
private readonly List<CliTaskInfo> taskPool = new List<CliTaskInfo>();
private long startTimeMs;
private long incommingTimeMs;
public List<Task> TaskPool { get { return taskPool; } }
public void AddTask(Task task) { taskPool.Add(task); }
public void WaitAll() { Task.WaitAll(taskPool.ToArray()); }
public void Clear() { taskPool.Clear(); }
public void CancelAll() { Task.WhenAll(taskPool).ContinueWith(t => { }); }
public void StartAll()
public List<CliTaskInfo> TaskPool
{
taskPool.ForEach(t => t.Start());
}
public bool AreTasksDone()
{
return taskPool.All(task => task.IsCompleted);
get { return taskPool; }
}
public long StartTime
{
get => startTime;
get { return startTimeMs; }
}
public long IncommingTime
{
get => incommingTime;
}
public bool TimeOutReceived(long timeout)
{
return (DateTime.Now.Ticks - startTime) > timeout;
get { return incommingTimeMs; }
}
public CliRunner(bool isCliLogging)
{
startTime = DateTime.Now.Ticks;
ResetStartTime();
if (isCliLogging)
{
@ -60,42 +43,180 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
{
log = new ScopedLoggerFactory().CreateLogger<CliRunner>(
@"C:\TBF\Logs\CliRunner.txt",
10, // maxFileSizeMB
7, // maxBackups
10,
7,
log4net.Core.Level.Debug,
true, // zipRolledFiles
true, // singleZipPerDay
TimeSpan.FromMinutes(2) // zipScanInterval
true,
true,
TimeSpan.FromMinutes(2)
);
}
catch (Exception ex)
{
// Fallback to console or handle gracefully
Console.WriteLine($"Failed to initialize logger: {ex.Message}");
}
}
}
/// <summary>
///
/// </summary>
/// <param name="fileName"></param>
/// <param name="args"></param>
/// <typeparam name="T"></typeparam>
public void ResetStartTime()
{
startTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
public void Clear()
{
foreach (var item in taskPool)
{
try
{
item?.Cts?.Dispose();
}
catch
{
}
try
{
if (item?.Process != null)
{
item.Process.Dispose();
}
}
catch
{
}
}
taskPool.Clear();
}
public void WaitAll()
{
var tasks = taskPool
.Where(t => t != null && t.Task != null)
.Select(t => t.Task)
.ToArray();
if (tasks.Length == 0)
return;
try
{
Task.WaitAll(tasks);
}
catch (AggregateException)
{
RefreshTaskStates();
}
}
public bool AreTasksDone()
{
RefreshTaskStates();
return taskPool.Count > 0 && taskPool.All(t => t.State != CliTaskState.Running);
}
public bool TimeOutReceived(long timeoutMs)
{
long nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
return (nowMs - startTimeMs) > timeoutMs;
}
public void RefreshTaskStates()
{
foreach (var item in taskPool)
{
if (item == null || item.Task == null)
continue;
if (item.State == CliTaskState.TimedOut)
continue;
if (!item.Task.IsCompleted)
{
item.State = CliTaskState.Running;
}
else if (item.Task.IsCanceled)
{
item.State = CliTaskState.Canceled;
}
else if (item.Task.IsFaulted)
{
item.State = CliTaskState.Faulted;
}
else
{
item.State = CliTaskState.Completed;
}
}
}
public void CancelUndoneTasksAsTimedOut()
{
foreach (var item in taskPool)
{
if (item == null || item.Task == null)
continue;
if (item.Task.IsCompleted)
{
if (item.Task.IsCanceled)
item.State = CliTaskState.Canceled;
else if (item.Task.IsFaulted)
item.State = CliTaskState.Faulted;
else
item.State = CliTaskState.Completed;
continue;
}
item.State = CliTaskState.TimedOut;
try
{
item.Cts?.Cancel();
}
catch (Exception ex)
{
log?.Warn("Cancel token failed", ex);
}
try
{
if (item.Process != null && !item.Process.HasExited)
{
item.Process.Kill();
}
}
catch (Exception ex)
{
log?.Warn("Kill process failed", ex);
}
}
}
public void AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg)
{
var task = SendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
taskPool.Add(task);
AddSendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
}
public void AddSendAsync(string fileName, string args)
{
var task = SendAsync(fileName, args);
taskPool.Add(task);
ResetStartTime();
var cts = new CancellationTokenSource();
var info = new CliTaskInfo
{
Cts = cts,
Name = "SendAsync"
};
var task = SendAsync(fileName, args, info, cts.Token);
info.Task = task;
taskPool.Add(info);
}
public async Task<string> SendAsync(string fileName, string args, CancellationToken ct = default)
public async Task<string> SendAsync(string fileName, string args, CliTaskInfo info, CancellationToken ct = default)
{
var psi = new ProcessStartInfo
{
@ -107,7 +228,12 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
CreateNoWindow = true
};
using var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true })
{
info.Process = process;
try
{
process.Start();
Task<string> stdOutTask = process.StandardOutput.ReadToEndAsync();
@ -119,6 +245,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
}
catch (OperationCanceledException)
{
if (info.State != CliTaskState.TimedOut)
info.State = CliTaskState.Canceled;
if (!process.HasExited)
{
process.Kill();
@ -127,21 +256,56 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
throw;
}
//comming answer from serial port - good place for time stamp
incommingTime = DateTime.Now.Ticks;
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
log?.Debug(allOutput);
info.State = CliTaskState.Completed;
return allOutput;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
info.State = CliTaskState.Faulted;
log?.Error("SendAsync failed", ex);
throw;
}
finally
{
info.Process = null;
}
}
}
public void AddRunAndCaptureJsonAsync<T>(SerialPortData data, SerialPortData.EMeterArg eMeterArg) where T : new()
{
var task = RunAndCaptureJsonAsync<T>(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
taskPool.Add(task);
AddRunAndCaptureJsonAsync<T>(
data.SerialPortCmdClientPath,
data.DefaultArgSettings(eMeterArg));
}
public async Task<T> RunAndCaptureJsonAsync<T>(string fileName, string args, CancellationToken ct = default) where T : new()
public void AddRunAndCaptureJsonAsync<T>(string fileName, string args) where T : new()
{
ResetStartTime();
var cts = new CancellationTokenSource();
var info = new CliTaskInfo
{
Cts = cts,
Name = $"RunAndCaptureJsonAsync<{typeof(T).Name}>"
};
var task = RunAndCaptureJsonAsync<T>(fileName, args, info, cts.Token);
info.Task = task;
taskPool.Add(info);
}
public async Task<T> RunAndCaptureJsonAsync<T>(string fileName, string args, CliTaskInfo info, CancellationToken ct = default) where T : new()
{
var psi = new ProcessStartInfo
{
@ -153,47 +317,90 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
CreateNoWindow = true
};
using var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true })
{
info.Process = process;
try
{
process.Start();
var stdoutTask = process.StandardOutput.ReadToEndAsync();
var stderrTask = process.StandardError.ReadToEndAsync();
try
{
await Task.WhenAll(stdoutTask, stderrTask, process.WaitForExitAsync(ct));
}
catch (OperationCanceledException)
{
if (info.State != CliTaskState.TimedOut)
info.State = CliTaskState.Canceled;
if (!process.HasExited)
{
process.Kill();
}
throw;
}
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
log?.Debug(allOutput);
string json = ExtractJson(allOutput);
if (TryJsonStringDeserialize(json, out T result)) return result;
return default;
T result;
if (TryJsonStringDeserialize(json, out result))
{
info.State = CliTaskState.Completed;
return result;
}
public bool TryJsonStringDeserialize<T>(string json, out T runAndCaptureJsonAsync) where T : new()
info.State = CliTaskState.Completed;
return default(T);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
info.State = CliTaskState.Faulted;
log?.Error("RunAndCaptureJsonAsync failed", ex);
throw;
}
finally
{
info.Process = null;
}
}
}
public bool TryJsonStringDeserialize<T>(string json, out T value) where T : new()
{
if (json != null)
{
try
{
runAndCaptureJsonAsync = JsonConvert.DeserializeObject<T>(json);
value = JsonConvert.DeserializeObject<T>(json);
return true;
}
catch (Exception ex)
{
log.Debug(ex.Message);
runAndCaptureJsonAsync = TryConvert<T>(json);
log?.Debug(ex.Message);
value = TryConvert<T>(json);
return true;
}
}
runAndCaptureJsonAsync = default;
value = default(T);
return false;
}
private static T TryConvert<T>(string json) where T : new()
{
T obj = new T();
try
@ -202,7 +409,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!prop.CanWrite) continue;
if (!prop.CanWrite)
continue;
JToken token;
if (jObject.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out token))
@ -214,10 +422,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
}
catch
{
// leave default if conversion fails
}
}
// else → keep default value
}
}
catch (Exception ex)
@ -226,7 +432,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
}
return obj;
}
public string ExtractJson(string text)
@ -241,6 +446,20 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
return null;
}
}
internal void AddTaskForTest(Task task, string name = "TestTask", CancellationTokenSource cts = null)
{
taskPool.Add(new CliTaskInfo
{
Task = task,
Cts = cts,
Name = name,
State = task.IsCompleted
? (task.IsCanceled ? CliTaskState.Canceled :
task.IsFaulted ? CliTaskState.Faulted :
CliTaskState.Completed)
: CliTaskState.Running
});
}
}
}

View File

@ -0,0 +1,25 @@
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
{
public class CliTaskInfo
{
public Task Task { get; set; }
public CancellationTokenSource Cts { get; set; }
public Process Process { get; set; }
public CliTaskState State { get; set; } = CliTaskState.Running;
public string Name { get; set; }
public bool UseResult
{
get
{
return State == CliTaskState.Completed
&& Task != null
&& Task.Status == TaskStatus.RanToCompletion;
}
}
}
}

View File

@ -0,0 +1,13 @@
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
{
public enum CliTaskState
{
Running,
Completed,
TimedOut,
Canceled,
Faulted
}
}

View File

@ -336,10 +336,12 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
/// for debug purposes what time will consume answer
/// </summary>
private long startTimeInMilis = -1, fullTimeInMilis = -1;
/// 30 seconds
private static long SafetyTimeOut = 30 * 1000;
private long incommingTime = -1;
private bool _lastOpTimedOut;
/// 30 seconds
public long DeltaTime { get{return fullTimeInMilis;}}
public long IncommingTime { get{return incommingTime;}}
@ -356,108 +358,146 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
if (_currentOp == CurrentPoseidonOp.SendStartDataStream)
{
CliRunner.Clear();
_lastOpTimedOut = false;
CliRunner.AddSendAsync(serialPort, SerialPortData.EMeterArg.AllParams);
_currentOp = CurrentPoseidonOp.SendStartDataStream_Runing;
return Event.Busy;
}
else if (_currentOp == CurrentPoseidonOp.SendStartDataStream_Runing)
{
if (CliRunner.AreTasksDone()
|| CliRunner.TimeOutReceived(SafetyTimeOut))
if (CliRunner.AreTasksDone())
{
_currentOp = CurrentPoseidonOp.SendStartDataStream_Done;
}
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
{
_lastOpTimedOut = true;
CliRunner.CancelUndoneTasksAsTimedOut();
_currentOp = CurrentPoseidonOp.SendStartDataStream_Done;
}
return Event.Busy;
}
else if (_currentOp == CurrentPoseidonOp.SendStartDataStream_Done)
{
var firstTask = CliRunner.TaskPool.FindLast(t => t is Task<string>);
var firstTaskInfo = CliRunner.TaskPool
.FindLast(t => t.Task is Task<string> && t.UseResult);
if (firstTask != null && firstTask is Task<string>)
if (firstTaskInfo != null)
{
string data = null;
data = (firstTask as Task<string>).Result;
var task = (Task<string>)firstTaskInfo.Task;
string data = task.Result;
if (data != null && TryGetDeviceId(data, out wmSerialNr))
if (!string.IsNullOrEmpty(data))
{
TryGetDeviceId(data, out wmSerialNr);
}
}
else if (_lastOpTimedOut)
{
log.Warn(
$"PoseidonReader {Name}: SendStartDataStream timed out, no completed result will be used.");
}
_currentOp = CurrentPoseidonOp.Done;
CliRunner.Clear();
_currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done;
return Event.Done;
}
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
{
startTimeInMilis = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
incommingTime = -1;
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
CliRunner.Clear();
_lastOpTimedOut = false;
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
SerialPortData.EMeterArg.AllParams);
_currentOp = CurrentPoseidonOp.ReadDatastream_Running;
return Event.Busy;
}else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Running)
}
else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Running)
{
if (CliRunner.AreTasksDone()
|| CliRunner.TimeOutReceived(SafetyTimeOut))
if (CliRunner.AreTasksDone())
{
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
//set time stamp - end of reading
incommingTime = CliRunner.IncommingTime;
}
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
{
_lastOpTimedOut = true;
CliRunner.CancelUndoneTasksAsTimedOut();
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
incommingTime = CliRunner.IncommingTime;
}
return Event.Busy;
}
else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Done)
{
fullTimeInMilis = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - startTimeInMilis;
log.Debug($"PoseidonReader.Run() Name= {Cfg.Name} fullTimeInMilis = {fullTimeInMilis} ");
JsonDataFromPoseidon data = null;
var first = CliRunner.TaskPool.FindLast(t => t is Task<JsonDataFromPoseidon>);
if (first != null && first is Task<JsonDataFromPoseidon>)
{
data = (first as Task<JsonDataFromPoseidon>).Result;
fullTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTimeInMilis;
log.Debug($"PoseidonReader.Run() Name= {Cfg.Name} fullTimeInMilis = {fullTimeInMilis}");
//GET serial number
JsonDataFromPoseidon data = null;
var firstTaskInfo = CliRunner.TaskPool
.FindLast(t => t.Task is Task<JsonDataFromPoseidon> && t.UseResult);
if (firstTaskInfo != null)
{
var task = (Task<JsonDataFromPoseidon>)firstTaskInfo.Task;
data = task.Result;
}
else
{
if (_lastOpTimedOut)
{
log.Warn($"PoseidonReader {Name}: ReadDatastream timed out, no completed result available.");
}
else
{
log.Warn("No completed JsonDataFromPoseidon task available.");
}
}
if (data != null)
{
if (string.IsNullOrEmpty(wmSerialNr))
{
try
{
wmSerialNr = data?.DeviceId ?? wmSerialNr;
wmSerialNr = data.DeviceId ?? wmSerialNr;
}
catch (Exception e)
{
log.Error("Serial Nr - parse error!");
log.Error("Serial Nr - parse error!", e);
}
}
//GET volume
if (data != null && Double.TryParse(data.Reading, out double Volume))
double volume;
if (Double.TryParse(data.Reading, out volume))
{
double VolumeLi = Units.ConvertFrom(Unit.USgal, Volume);
//double VolumeM3 = Units.ConvertTo(Unit.m3, VolumeLi);
double volumeLi = Units.ConvertFrom(Unit.USgal, volume);
if (_isReadingStart)
{
//volume
beginWMState = VolumeLi;
}
beginWMState = volumeLi;
else
{
//volume
endWMState = VolumeLi;
endWMState = volumeLi;
}
}
}
//Finish reading and loop
_currentOp = CurrentPoseidonOp.Done;
CliRunner.Clear();
_currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done;
return Event.Done;
}
return Event.None;
}
/// <summary>Stop this operation</summary>
public void Stop()
{

View File

@ -1396,18 +1396,18 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
chanelXMeterRslt?.CopyContentFrom(meterRslt);
if (iCH == 0)
{
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartCh1,
genesisSmart.TimestampSecEndCh1, genesisSmart.VolumeLtrStartCh1, genesisSmart.VolumeLtrEndCh1);
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh1,
genesisSmart.TimestampSecEndRawCh1, genesisSmart.VolumeLtrStartRawCh1, genesisSmart.VolumeLtrEndRawCh1);
}
else if (iCH == 1)
{
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartCh2,
genesisSmart.TimestampSecEndCh2, genesisSmart.VolumeLtrStartCh2, genesisSmart.VolumeLtrEndCh2);
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh2,
genesisSmart.TimestampSecEndRawCh2, genesisSmart.VolumeLtrStartRawCh2, genesisSmart.VolumeLtrEndRawCh2);
}
else if (iCH == 2)
{
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartCh3,
genesisSmart.TimestampSecEndCh3, genesisSmart.VolumeLtrStartCh3, genesisSmart.VolumeLtrEndCh3);
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh3,
genesisSmart.TimestampSecEndRawCh3, genesisSmart.VolumeLtrStartRawCh3, genesisSmart.VolumeLtrEndRawCh3);
}
if (!genesisSmart.EnableShowChanels)

View File

@ -1484,6 +1484,8 @@
<DependentUpon>RRCfgCtrl.cs</DependentUpon>
</Compile>
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunner.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliTaskInfo.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliTaskState.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\Factory.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\JsonDataFromPoseidon.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonCfg.cs" />

View File

@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.GenesisRegReader;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
@ -14,10 +15,16 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
public class GenesisReaderTests
{
private static GenesisCfg CreateCfg()
{
Factory factory = new Factory();
return new GenesisCfg(factory);
}
[TestMethod]
public void Debug_ProcessOptoLine_FormatVariants()
{
var reader = new GenesisSmartReader();
var reader = new GenesisSmartReader(CreateCfg());
InitializeReaderForTest(reader);
string[] variants =
@ -28,26 +35,28 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
"@he \t1\t0\t0A1F59C4\t00017A43\t00115C45\t72E1596B\t00000400\t00001998\t7D91B652\t7D4F37E6\t000191E6\t0C\t062E4A9C\t5331"
};
int[] counts = new int[variants.Length];
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");
counts[i] = GetPrivateField<int>(reader, "optoDataCount");
Console.WriteLine($"Variant {i}: {variants[i]}");
Console.WriteLine($" blockCompleted={blockCompleted}");
Console.WriteLine($" optoDataCount={optoDataCount}");
Console.WriteLine($" optoDataCount={counts[i]}");
Console.WriteLine("--------------------------------");
}
Assert.Fail("Inspect which variant, if any, is accepted.");
Assert.IsTrue(counts.Any(c => c > 0), "At least one telegram format variant should be accepted.");
}
[TestMethod]
public void RealInput_ShouldCalculate_StartEndVolumes_AndTimes()
public void RealInput_ShouldParseCalibrationTelegrams()
{
var reader = new GenesisSmartReader();
var reader = new GenesisSmartReader(CreateCfg());
InitializeReaderForTest(reader);
string[] realInputLines = LoadRealInputLines();
@ -110,87 +119,33 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
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.");
}
Assert.AreEqual(3, processedCount, "Expected all 3 calibration telegrams to be parsed.");
Assert.AreEqual(0, firstValidIx);
Assert.AreEqual(2, lastValidIx);
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
Assert.IsNotNull(optoData);
Assert.AreEqual(0, optoData[0].IChannel());
Assert.AreEqual(1, optoData[1].IChannel());
Assert.AreEqual(2, optoData[2].IChannel());
// With only one telegram per channel, full start/end reconstruction is not expected yet.
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, "AddTestStartEndMarksToData", new object[] { 0, 0 });
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");
Assert.IsTrue(reader.NoSamples,
"With only one telegram per channel, recalculated start/end samples should still be unavailable.");
}
[TestMethod]
public void RealInput_ShouldSupport_RolloverNormalization()
public void RealInput_ShouldParseRolloverInputLines()
{
var reader = new GenesisSmartReader();
var reader = new GenesisSmartReader(CreateCfg());
InitializeReaderForTest(reader);
string[] realInputLines = LoadRealInputLinesWithRollover();
@ -211,27 +166,23 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
int finalOptoDataCount = GetPrivateField<int>(reader, "optoDataCount");
Assert.IsTrue(
finalOptoDataCount > 1,
"Need at least 2 valid telegrams. Check exact telegram format / CRC in LoadRealInputLinesWithRollover().");
Assert.AreEqual(3, finalOptoDataCount,
"Expected all provided rollover input lines to be parsed as telegrams.");
}
reader.TestStartTelegramIx = 0;
reader.TestEndTelegramIx = finalOptoDataCount - 1;
[TestMethod]
public void PrepareCalculatedChannelDataSimulationForTest_ShouldProduceUsableSamples()
{
var reader = new GenesisSmartReader(CreateCfg());
InitializeReaderForTest(reader);
object[] markArgs = { 0, 0 };
InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
InvokePrivate(reader, "DataStreamPostProcessing");
InvokePrivate(reader, "PrepareCalculatedChannelData");
reader.PrepareCalculatedChannelDataSimulationForTest();
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");
Assert.IsFalse(reader.NoSamples);
Assert.AreEqual(105.0, reader.VolumeLtrStartAverage, 0.0001);
Assert.AreEqual(157.0, reader.VolumeLtrEndAwerage, 0.0001);
Assert.AreEqual(10.0, reader.TimestampSecStart, 0.0001);
Assert.AreEqual(20.0, reader.TimestampSecEnd, 0.0001);
}
private static void InitializeReaderForTest(GenesisSmartReader reader)
@ -264,6 +215,8 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
SetPrivateField(reader, "partOfTelegram", string.Empty);
SetPrivateField(reader, "startDataProcessing", true);
reader.StopQueueData = false;
reader.TestStartTelegramIx = 0;
reader.TestEndTelegramIx = 0;
}

View File

@ -133,13 +133,13 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
InvokePrepareCalculatedChannelData(reader);
Assert.AreEqual(200.666666666667, reader.VolumeLtrStart, 1e-9);
Assert.AreEqual(219.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(219.666666666667, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStart, 1e-9);
Assert.AreEqual(20.0, reader.TimestampSecEnd, 1e-9);
Assert.AreEqual(200.666666666667, reader.VolumeLtrStartRaw, 1e-9);
Assert.AreEqual(219.0, reader.VolumeLtrEndRaw, 1e-9);
Assert.AreEqual(200.666666666667, reader.VolumeLtrStartAverage, 1e-9);
Assert.AreEqual(219.666666666667, reader.VolumeLtrEndAwerage, 1e-9);
}
[TestMethod]
@ -162,11 +162,6 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
index++;
}
// channel 1 loses its last two valid records
// valid ch1 becomes: 200..207 with timestamps 1..8
// longest channel deltaTime is 9 (channels 0 and 2)
// ch1 deltaVolume = 7, deltaTime = 7 => recalculated deltaVolume = 7 * 9 / 7 = 9
// recalculated end ch1 = 200 + 9 = 209
optoData[25].Flags = OptoTelegramFlags.InvalidTelegram;
optoData[28].Flags = OptoTelegramFlags.InvalidTelegram;
@ -177,14 +172,14 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
InvokePrepareCalculatedChannelData(reader);
Assert.AreEqual(200.666666666667, reader.VolumeLtrStart, 1e-9);
Assert.AreEqual(208.33333333333334, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(208.33333333333334, reader.VolumeLtrEndRaw, 1e-9);
Assert.AreEqual(209.666666666667, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(209.666666666667, reader.VolumeLtrEndAwerage, 1e-9);
Assert.AreEqual(101.0, reader.VolumeLtrStartCh1, 1e-9);
Assert.AreEqual(201.0, reader.VolumeLtrStartCh2, 1e-9);
Assert.AreEqual(300.0, reader.VolumeLtrStartCh3, 1e-9);
Assert.AreEqual(200.666666666667, reader.VolumeLtrStartRaw, 1e-9);
Assert.AreEqual(200.666666666667, reader.VolumeLtrStartAverage, 1e-9);
Assert.AreEqual(110.0, reader.VolumeLtrEndCh1, 1e-9);
Assert.AreEqual(210.0, reader.VolumeLtrEndCh2, 1e-9);
@ -272,24 +267,13 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.AreEqual(20.0, reader.TimestampSecEndCh2, 1e-9);
Assert.AreEqual(20.0, reader.TimestampSecEndCh3, 1e-9);
Assert.AreEqual(101.0, reader.VolumeLtrStartCh1, 1e-9);
Assert.AreEqual(201.0, reader.VolumeLtrStartCh2, 1e-9);
Assert.AreEqual(300.0, reader.VolumeLtrStartCh3, 1e-9);
Assert.AreEqual(219.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(219.666666666667, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(1.0, reader.TimestampSecStart, 1e-9);
Assert.AreEqual(20.0, reader.TimestampSecEnd, 1e-9);
Assert.AreEqual(200.666666666667, reader.VolumeLtrStartRaw, 1e-9);
Assert.AreEqual(219.0, reader.VolumeLtrEndRaw, 1e-9);
Assert.AreEqual(120.0, reader.VolumeLtrEndCh1, 1e-9);
Assert.AreEqual(220.0, reader.VolumeLtrEndCh2, 1e-9);
Assert.AreEqual(319.0, reader.VolumeLtrEndCh3, 1e-9);
Assert.AreEqual(219.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(219.0, reader.VolumeLtrEndRaw, 1e-9);
Assert.AreEqual(200.666666666667, reader.VolumeLtrStartAverage, 1e-9);
Assert.AreEqual(219.666666666667, reader.VolumeLtrEndAwerage, 1e-9);
}
[TestMethod]
@ -313,7 +297,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
InvokePrepareCalculatedChannelData(reader);
Assert.AreEqual(15.5, reader.VolumeLtrStart, 1e-9);
Assert.AreEqual(19.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(19.5, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(11.0, reader.VolumeLtrStartCh1, 1e-9);
Assert.AreEqual(20.0, reader.VolumeLtrStartCh2, 1e-9);

View File

@ -1,6 +1,8 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
@ -36,6 +38,15 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
return (T)field.GetValue(target);
}
private static void WaitForStopToFinish(GenesisSmartReader reader, int timeoutMs = 3000)
{
var stopTask = GetPrivateField<Task>(reader, "_backgroundStopTask");
if (stopTask != null)
{
stopTask.Wait(timeoutMs);
}
}
private static OptoTelegramRaw CreateOptoRecord(int channel, double volumeRawExt, double timestampExt)
{
return new OptoTelegramRaw
@ -87,11 +98,10 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.AreEqual(DataStreamState.Flush, finalState, "Stop() should finish in Flush state.");
Assert.IsFalse(fake.IsOpen, "Serial port should be closed by Stop().");
// These values are populated only if DataStreamPostProcessing() ran.
Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStartRaw, 1e-9,
"Expected post-processing to prepare raw start values.");
Assert.AreEqual((15.0 + 25.0 + 35.0) / 3.0, reader.VolumeLtrEndRaw, 1e-9,
"Expected post-processing to prepare raw end values.");
Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStartAverage, 1e-9,
"Expected post-processing to prepare raw/recalculated start values.");
Assert.AreEqual((15.0 + 25.0 + 36.0) / 3.0, reader.VolumeLtrEndAwerage, 1e-9,
"Expected post-processing to prepare recalculated end values.");
}
[TestMethod]
@ -111,6 +121,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.optoSerialPort = fake;
reader.Stop();
WaitForStopToFinish(reader);
var finalState = GetPrivateField<DataStreamState>(reader, "dataStreamState");
var stopQueueData = GetPrivateField<bool>(reader, "_stopQueueData");
@ -119,10 +130,9 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.IsTrue(stopQueueData, "Immediate stop path should stop queueing.");
Assert.IsFalse(fake.IsOpen, "Serial port should be closed by Stop().");
// Should still be default because DataStreamPostProcessing() must NOT run.
Assert.AreEqual(0.0, reader.VolumeLtrStartRaw, 1e-9,
Assert.AreEqual(0.0, reader.VolumeLtrStartAverage, 1e-9,
"Post-processing should not run when previous state was ProcessAndSave.");
Assert.AreEqual(0.0, reader.VolumeLtrEndRaw, 1e-9,
Assert.AreEqual(0.0, reader.VolumeLtrEndAwerage, 1e-9,
"Post-processing should not run when previous state was ProcessAndSave.");
}

View File

@ -79,6 +79,15 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.StopQueueData = false;
}
private static void WaitForStopToFinish(GenesisSmartReader reader, int timeoutMs = 3000)
{
var stopTask = GetPrivateField<Task>(reader, "_backgroundStopTask");
if (stopTask != null)
{
stopTask.Wait(timeoutMs);
}
}
[TestMethod]
public void Start_ShouldOpenPort_AndEnableProcessing()
{
@ -129,9 +138,10 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.TestEndTelegramIx = 1;
reader.Stop();
WaitForStopToFinish(reader);
Assert.IsFalse(fake.IsOpen);
Assert.AreEqual(2, fake.CloseCalls);
Assert.IsTrue(fake.CloseCalls >= 1);
}
@ -165,6 +175,9 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.Initialize();
reader.optoSerialPort = fake;
InitializeThreeChannelState(reader);
EnableProcessingLoopForTests(reader);
bool reset;
reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset);
@ -199,6 +212,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.optoSerialPort = fake;
InitializeThreeChannelState(reader);
EnableProcessingLoopForTests(reader);
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool resetDataBuffer);
@ -254,6 +268,7 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.optoSerialPort = fake;
InitializeThreeChannelState(reader);
EnableProcessingLoopForTests(reader);
var line =
"@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD";
@ -265,9 +280,15 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
}
[DataTestMethod]
[DataRow("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", 0)]
[DataRow("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", 1)]
[DataRow("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", 2)]
[DataRow(
"@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
0)]
[DataRow(
"@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
1)]
[DataRow(
"@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
2)]
public void ProcessOptoLine_ShouldInsertTelegramAndUpdateExpectedChannel(string line, int expectedChannelIndex)
{
var fake = new FakeSerialDriver();
@ -277,6 +298,9 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.Initialize();
reader.optoSerialPort = fake;
InitializeThreeChannelState(reader);
EnableProcessingLoopForTests(reader);
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool resetDataBuffer);
var volumeRawExtLast = GetPrivateField<double[]>(reader, "volumeRawExtLast");
@ -284,27 +308,25 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
var optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
Assert.AreEqual(1, optoDataCount, "Exactly one telegram should be inserted.");
Assert.AreEqual(1, optoDataCount);
var inserted = optoData[0];
Assert.IsNotNull(inserted, "Inserted telegram should not be null.");
Assert.IsNotNull(inserted);
Assert.AreEqual(expectedChannelIndex, inserted.IChannel(), "Inserted telegram channel does not match expected channel.");
Assert.AreEqual(0, inserted.Counter, "First inserted telegram should have counter 0.");
Assert.AreEqual(OptoTelegramFlags.OK, inserted.Flags, "Inserted telegram should be marked OK.");
Assert.AreNotEqual(default(DateTime), inserted.DateTime, "Inserted telegram DateTime should be initialized.");
Assert.AreEqual(expectedChannelIndex, inserted.IChannel());
Assert.AreEqual(0, inserted.Counter);
Assert.AreEqual(OptoTelegramFlags.OK, inserted.Flags);
Assert.AreNotEqual(0.0, inserted.VolumeRaw, "Inserted telegram VolumeRaw should be set.");
Assert.AreNotEqual(0.0, inserted.VolumeRawExt, "Inserted telegram VolumeRawExt should be set.");
Assert.AreNotEqual(0.0, inserted.Timestamp, "Inserted telegram Timestamp should be set.");
Assert.AreNotEqual(0.0, inserted.TimestampExt, "Inserted telegram TimestampExt should be set.");
Assert.AreNotEqual(0.0, inserted.VolumeRawExt);
Assert.AreNotEqual(0.0, inserted.TimestampExt);
for (int i = 0; i < ChannelCount; i++)
{
if (i == expectedChannelIndex)
{
Assert.AreNotEqual(0.0, volumeRawExtLast[i], $"Expected channel {i} volume cache was not updated.");
Assert.AreNotEqual(0.0, timestampExtLast[i], $"Expected channel {i} timestamp cache was not updated.");
Assert.AreNotEqual(0.0, timestampExtLast[i],
$"Expected channel {i} timestamp cache was not updated.");
}
else
{
@ -313,11 +335,8 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
}
}
Assert.AreEqual(volumeRawExtLast[expectedChannelIndex], inserted.VolumeRawExt, 1e-9,
"Inserted telegram VolumeRawExt should match updated channel cache.");
Assert.AreEqual(timestampExtLast[expectedChannelIndex], inserted.TimestampExt, 1e-9,
"Inserted telegram TimestampExt should match updated channel cache.");
Assert.AreEqual(volumeRawExtLast[expectedChannelIndex], inserted.VolumeRawExt, 1e-9);
Assert.AreEqual(timestampExtLast[expectedChannelIndex], inserted.TimestampExt, 1e-9);
}
[TestMethod]
@ -344,13 +363,16 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
InvokePrepareCalculatedChannelData(reader);
Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStart, 1e-9);
Assert.AreEqual((15.0 + 25.0 + 35.0) / 3.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual((15.0 + 25.0 + 36.0) / 3.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(100.0, reader.TimestampSecStart, 1e-9);
Assert.AreEqual(105.0, reader.TimestampSecEnd, 1e-9);
Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStartRaw, 1e-9);
Assert.AreEqual((15.0 + 25.0 + 35.0) / 3.0, reader.VolumeLtrEndRaw, 1e-9);
Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStartAverage, 1e-9);
Assert.AreEqual((15.0 + 25.0 + 36.0) / 3.0, reader.VolumeLtrEndAwerage, 1e-9);
Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStartRawRaw, 1e-9);
Assert.AreEqual((15.0 + 25.0 + 35.0) / 3.0, reader.VolumeLtrEndRawRaw, 1e-9);
}
private static OptoTelegramRaw CreateOptoRecord(int channel, double volumeRawExt, double timestampExt)
@ -388,7 +410,21 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
InvokePrepareCalculatedChannelData(reader);
Assert.AreEqual(15.5, reader.VolumeLtrStart, 1e-9);
Assert.AreEqual(19.0, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(19.5, reader.VolumeLtrEnd, 1e-9);
Assert.AreEqual(15.5, reader.VolumeLtrStartAverage, 1e-9);
Assert.AreEqual(19.5, reader.VolumeLtrEndAwerage, 1e-9);
Assert.AreEqual(15.5, reader.VolumeLtrStartRawRaw, 1e-9);
Assert.AreEqual(19.0, reader.VolumeLtrEndRawRaw, 1e-9);
Assert.AreEqual(11.0, reader.VolumeLtrStartRawCh1, 1e-9);
Assert.AreEqual(20.0, reader.VolumeLtrStartRawCh2, 1e-9);
Assert.AreEqual(0.0, reader.VolumeLtrStartRawCh3, 1e-9);
Assert.AreEqual(14.0, reader.VolumeLtrEndRawCh1, 1e-9);
Assert.AreEqual(24.0, reader.VolumeLtrEndRawCh2, 1e-9);
Assert.AreEqual(0.0, reader.VolumeLtrEndRawCh3, 1e-9);
}
[TestMethod]
@ -425,40 +461,38 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
}
[TestMethod]
public void ReadOptoData_FullBlock_ShouldDiscardBuffersAfterClosingF()
public void ProcessingLoop_FullBlock_ShouldDiscardBuffersAfterClosingF()
{
var fake = new FakeSerialDriver();
fake.EnqueueLine("@f AA754B 4D0CEE78 5D89");
fake.EnqueueLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331");
fake.EnqueueLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD");
fake.EnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E");
fake.EnqueueLine("@f AA7C01 4D0CFE76 B08F");
fake.Open();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
fake.Open();
reader.optoSerialPort = fake;
reader.ResetAfterBlockRepetitions = 1;
var readMethod = typeof(GenesisSmartReader).GetMethod(
"ReadOptoData",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(DataStreamState) },
null);
InitializeThreeChannelState(reader);
EnableProcessingLoopForTests(reader);
Assert.IsNotNull(readMethod);
for (int i = 0; i < 5; i++)
reader.StartProcessingLoop();
try
{
readMethod.Invoke(reader, new object[] { DataStreamState.ProcessAndSave });
}
reader.TestEnqueueLine("@f AA754B 4D0CEE78 5D89");
reader.TestEnqueueLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331");
reader.TestEnqueueLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD");
reader.TestEnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E");
reader.TestEnqueueLine("@f AA7C01 4D0CFE76 B08F");
Thread.Sleep(300);
Assert.AreEqual(1, fake.DiscardInCalls, "Input buffer should be discarded once after completed block.");
Assert.AreEqual(1, fake.DiscardOutCalls, "Output buffer should be discarded once after completed block.");
}
finally
{
reader.StopProcessingLoop();
}
}
[TestMethod]
public void ProcessOptoLine_LastF_AfterH3_ShouldRequestBufferReset()
@ -471,10 +505,13 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.optoSerialPort = fake;
reader.ResetAfterBlockRepetitions = 1;
InitializeThreeChannelState(reader);
EnableProcessingLoopForTests(reader);
bool reset;
reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Opening @f must not reset buffers.");
Assert.IsFalse(reset);
reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset);
@ -529,24 +566,24 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.optoSerialPort = fake;
reader.ResetAfterBlockRepetitions = 3;
InitializeThreeChannelState(reader);
EnableProcessingLoopForTests(reader);
bool reset;
for (int repetition = 1; repetition <= 3; repetition++)
{
reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen on opening @f, repetition " + repetition);
Assert.IsFalse(reset);
reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after @h1, repetition " + repetition);
reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset);
reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after @h2, repetition " + repetition);
reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset);
reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after @h3, repetition " + repetition);
reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset);
reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset);
@ -568,24 +605,24 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.optoSerialPort = fake;
reader.ResetAfterBlockRepetitions = 5;
InitializeThreeChannelState(reader);
EnableProcessingLoopForTests(reader);
bool reset;
for (int repetition = 1; repetition <= 5; repetition++)
{
reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen on opening @f, repetition " + repetition);
Assert.IsFalse(reset);
reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after @h1, repetition " + repetition);
reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset);
reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after @h2, repetition " + repetition);
reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset);
reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset, "Reset must not happen after @h3, repetition " + repetition);
reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", DataStreamState.ProcessAndSave, out reset);
Assert.IsFalse(reset);
reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset);
@ -742,18 +779,12 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.AreEqual(20.0, sut.TimestampSecEndCh2, 0.0001);
Assert.AreEqual(20.0, sut.TimestampSecEndCh3, 0.0001);
Assert.AreEqual(105.0, sut.VolumeLtrStartRaw, 0.0001);
Assert.AreEqual(157.0, sut.VolumeLtrEndRaw, 0.0001);
Assert.AreEqual(105.0, sut.VolumeLtrStartAverage, 0.0001);
Assert.AreEqual(157.0, sut.VolumeLtrEndAwerage, 0.0001);
Assert.AreEqual(10.0, sut.TimestampSecStart, 0.0001);
Assert.AreEqual(20.0, sut.TimestampSecEnd, 0.0001);
}
[TestMethod]
public void METHOD()
{
}
}
}

View File

@ -10,22 +10,33 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
[TestClass]
public class GenesisSmartReader_Q3Test
{
private static readonly double[] ValidInitFactors = { 15625.0, 15625.0, 15625.0 };
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenRawDataIsNull()
public void GetQ3Calibration_ShouldRemainInvalid_WhenRawDataIsNull()
{
var sut = new GenesisSmartReader();
InitializeReaderForQ3Test(sut);
SetPrivateField(sut, "_rawStartEndByChannel", null);
sut.GetQ3Calibration(refVolume: 1000.0, refTime: 120.0, initCalibFactor: 15625.0);
var valid = new bool[3];
var calib = new double[3];
sut.GetQ3Calibration(
refVolume: 1000.0,
refTime: 120.0,
initCalibFactor: ValidInitFactors,
ref valid,
ref calib);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
}
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenNoSamplesConditionIsTrue()
public void GetQ3Calibration_ShouldRemainInvalid_WhenNoSamplesIsTrue()
{
var sut = new GenesisSmartReader();
InitializeReaderForQ3Test(sut);
@ -34,69 +45,255 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.IsTrue(sut.NoSamples, "Expected NoSamples to be true for this test.");
sut.GetQ3Calibration(refVolume: 1000.0, refTime: 120.0, initCalibFactor: 15625.0);
var valid = new bool[3];
var calib = new double[3];
sut.GetQ3Calibration(
refVolume: 1000.0,
refTime: 120.0,
initCalibFactor: ValidInitFactors,
ref valid,
ref calib);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
}
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenRefVolumeIsZero()
public void GetQ3Calibration_ShouldRemainInvalid_WhenRefVolumeIsZero()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
Assert.IsFalse(sut.NoSamples);
Assert.IsFalse(sut.NoSamples, "Prepared data should produce valid samples.");
var valid = new bool[3];
var calib = new double[3];
sut.GetQ3Calibration(refVolume: 0.0, refTime: 120.0, initCalibFactor: 15625.0);
sut.GetQ3Calibration(
refVolume: 0.0,
refTime: 120.0,
initCalibFactor: ValidInitFactors,
ref valid,
ref calib);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
}
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenRefTimeIsZero()
public void GetQ3Calibration_ShouldRemainInvalid_WhenRefTimeIsZero()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
Assert.IsFalse(sut.NoSamples);
Assert.IsFalse(sut.NoSamples, "Prepared data should produce valid samples.");
var valid = new bool[3];
var calib = new double[3];
sut.GetQ3Calibration(refVolume: 1000.0, refTime: 0.0, initCalibFactor: 15625.0);
sut.GetQ3Calibration(
refVolume: 1000.0,
refTime: 0.0,
initCalibFactor: ValidInitFactors,
ref valid,
ref calib);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
}
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenInitCalibFactorIsZero()
public void GetQ3Calibration_ShouldMarkInvalid_WhenInitFactorsAreZero()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
Assert.IsFalse(sut.NoSamples);
Assert.IsFalse(sut.NoSamples, "Prepared data should produce valid samples.");
var init = new[] { 0.0, 0.0, 0.0 };
var valid = new bool[3];
var calib = new double[3];
sut.GetQ3Calibration(refVolume: 1000.0, refTime: 120.0, initCalibFactor: 0.0);
sut.GetQ3Calibration(
refVolume: 1000.0,
refTime: 120.0,
initCalibFactor: init,
ref valid,
ref calib);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
}
[TestMethod]
public void GetQ3Calibration_ShouldProduceNonNegativeResult_WithPreparedSimulationData()
public void CalculateQ3Calibration_ShouldPopulatePerChannelValues_WithPreparedSimulationData()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
InvokePrivateByName(sut, "SetQ3Calibration", new object[] { ValidInitFactors });
Assert.IsFalse(sut.NoSamples, "Prepared data should produce valid samples.");
Assert.IsFalse(sut.NoSamples);
sut.GetQ3Calibration(refVolume: 1.0, refTime: 120.0, initCalibFactor: 15625.0);
sut.CalculateQ3Calibration(200.0, 120.0);
Assert.IsTrue(sut.Q3CalibValue >= 0.0);
Assert.IsNotNull(sut.Q3CalibValue);
Assert.AreEqual(3, sut.Q3CalibValue.Length);
Assert.IsTrue(sut.Q3CalibValue.All(v => !Double.IsNaN(v)));
Assert.IsTrue(sut.Q3CalibValue.All(v => v >= 0.0));
}
[TestMethod]
public void GetQ3Calibration_ShouldCalculateExpectedValues_ForKnownInput()
{
var sut = new GenesisSmartReader();
var raw = CreateKnownStartEndData(
// start volume / end volume / start time / end time
(100.0, 150.0, 10.0, 20.0), // dV = 50, dT = 10
(105.0, 165.0, 10.0, 22.0), // dV = 60, dT = 12
(110.0, 180.0, 10.0, 25.0) // dV = 70, dT = 15
);
SetPrivateField(sut, "_rawStartEndByChannel", raw);
SetPrivateField(sut, "_recalculatedStartEndByChannel", raw); // important for NoSamples == false
SetPrivateField(sut, "optoDataCount", 2);
sut.TestStartTelegramIx = 0;
sut.TestEndTelegramIx = 1;
Assert.IsFalse(sut.NoSamples, "Expected prepared data to produce valid samples.");
var init = new[] { 15625.0, 15625.0, 15625.0 };
var valid = new bool[3];
var calib = new double[3];
sut.GetQ3Calibration(
refVolume: 200.0,
refTime: 120.0,
initCalibFactor: init,
ref valid,
ref calib);
double expectedCh1 = (200.0 / 600.0) * 15625.0;
double expectedCh2 = (200.0 / 600.0) * 15625.0;
double expectedCh3 = (200.0 / 560.0) * 15625.0;
Assert.AreEqual(expectedCh1, calib[0], 0.0001);
Assert.AreEqual(expectedCh2, calib[1], 0.0001);
Assert.AreEqual(expectedCh3, calib[2], 0.0001);
Assert.IsFalse(valid[0]);
Assert.IsFalse(valid[1]);
Assert.IsFalse(valid[2]);
}
[TestMethod]
public void GetQ3Calibration_ShouldMarkChannelValid_WhenDifferenceIsWithinFivePercent()
{
var sut = new GenesisSmartReader();
// We want recalculatedDeltaVolume == refVolume, so calculated factor == initial factor.
// refVolume = 200, refTime = 120
// choose dT = 10, dV = 16.6666666666667 => coef = 12 => recalculated dV = 200
var raw = CreateKnownStartEndData(
(100.0, 116.6666666666667, 10.0, 20.0),
(200.0, 216.6666666666667, 10.0, 20.0),
(300.0, 316.6666666666667, 10.0, 20.0)
);
SetPrivateField(sut, "_rawStartEndByChannel", raw);
SetPrivateField(sut, "_recalculatedStartEndByChannel", raw);
SetPrivateField(sut, "optoDataCount", 2);
sut.TestStartTelegramIx = 0;
sut.TestEndTelegramIx = 1;
Assert.IsFalse(sut.NoSamples, "Expected prepared data to produce valid samples.");
var init = new[] { 15625.0, 15625.0, 15625.0 };
var valid = new bool[3];
var calib = new double[3];
sut.GetQ3Calibration(
refVolume: 200.0,
refTime: 120.0,
initCalibFactor: init,
ref valid,
ref calib);
Assert.IsTrue(valid[0], $"Ch1 invalid, value={calib[0]}");
Assert.IsTrue(valid[1], $"Ch2 invalid, value={calib[1]}");
Assert.IsTrue(valid[2], $"Ch3 invalid, value={calib[2]}");
Assert.AreEqual(15625.0, calib[0], 0.001);
Assert.AreEqual(15625.0, calib[1], 0.001);
Assert.AreEqual(15625.0, calib[2], 0.001);
}
[TestMethod]
public void CalculateQ3Calibration_ShouldComputeExpectedChannelValues_FromSimulationData()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
InvokePrivateByName(sut, "SetQ3Calibration", new object[] { ValidInitFactors });
sut.CalculateQ3Calibration(200.0, 120.0);
// initial values remain exposed through Q3CalibValue
Assert.AreEqual(15625.0, sut.Q3CalibValue[0], 0.000001);
Assert.AreEqual(15625.0, sut.Q3CalibValue[1], 0.000001);
Assert.AreEqual(15625.0, sut.Q3CalibValue[2], 0.000001);
// calculated values
Assert.AreEqual(5208.33333333333, sut.Q3Calib_Ch1Value, 0.000001);
Assert.AreEqual(5008.01282051282, sut.Q3Calib_Ch2Value, 0.000001);
Assert.AreEqual(4822.53086419753, sut.Q3Calib_Ch3Value, 0.000001);
}
[TestMethod]
public void CalculateQ3Calibration_ShouldKeepInitialArray_AndUpdateCalculatedChannelProperties()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
InvokePrivateByName(sut, "SetQ3Calibration", new object[] { ValidInitFactors });
sut.CalculateQ3Calibration(200.0, 120.0);
// Q3CalibValue currently exposes INITIAL calibration values, not calculated ones
Assert.AreEqual(15625.0, sut.Q3CalibValue[0], 0.000001);
Assert.AreEqual(15625.0, sut.Q3CalibValue[1], 0.000001);
Assert.AreEqual(15625.0, sut.Q3CalibValue[2], 0.000001);
// Per-channel public properties expose calculated values
Assert.IsTrue(sut.Q3Calib_Ch1Value > 0.0);
Assert.IsTrue(sut.Q3Calib_Ch2Value > 0.0);
Assert.IsTrue(sut.Q3Calib_Ch3Value > 0.0);
Assert.AreNotEqual(sut.Q3CalibValue[0], sut.Q3Calib_Ch1Value, 0.000001);
Assert.AreNotEqual(sut.Q3CalibValue[1], sut.Q3Calib_Ch2Value, 0.000001);
Assert.AreNotEqual(sut.Q3CalibValue[2], sut.Q3Calib_Ch3Value, 0.000001);
}
[TestMethod]
public void PrepareCalculatedChannelDataSimulationForTest_ShouldPrepareUsableData()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataSimulationForTest");
Assert.IsFalse(sut.NoSamples);
Assert.IsTrue(sut.VolumeLtrStart >= 0.0);
Assert.IsTrue(sut.VolumeLtrEnd >= 0.0);
Assert.IsTrue(sut.TimestampSecEnd >= sut.TimestampSecStart);
}
private static void InitializeReaderForQ3Test(GenesisSmartReader reader)
@ -115,20 +312,55 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
{
new[]
{
CreateRecord(0.0, 0.0),
CreateRecord(15625.0, 120.0)
CreateRecord(0.0, 0.0, 1),
CreateRecord(15625.0, 120.0, 1)
},
new OptoTelegramRaw[2],
new OptoTelegramRaw[2]
new[]
{
CreateRecord(0.0, 0.0, 2),
CreateRecord(15625.0, 120.0, 2)
},
new[]
{
CreateRecord(0.0, 0.0, 3),
CreateRecord(15625.0, 120.0, 3)
}
};
}
private static OptoTelegramRaw CreateRecord(double volumeRawExt, double timestampExt)
private static OptoTelegramRaw[][] CreateKnownStartEndData(
(double startVolume, double endVolume, double startTime, double endTime) ch1,
(double startVolume, double endVolume, double startTime, double endTime) ch2,
(double startVolume, double endVolume, double startTime, double endTime) ch3)
{
return new[]
{
new[]
{
CreateRecord(ch1.startVolume, ch1.startTime, 1),
CreateRecord(ch1.endVolume, ch1.endTime, 1)
},
new[]
{
CreateRecord(ch2.startVolume, ch2.startTime, 2),
CreateRecord(ch2.endVolume, ch2.endTime, 2)
},
new[]
{
CreateRecord(ch3.startVolume, ch3.startTime, 3),
CreateRecord(ch3.endVolume, ch3.endTime, 3)
}
};
}
private static OptoTelegramRaw CreateRecord(double volumeRawExt, double timestampExt, int channel)
{
return new OptoTelegramRaw
{
VolumeRawExt = volumeRawExt,
TimestampExt = timestampExt
TimestampExt = timestampExt,
iChannel = channel,
Flags = OptoTelegramFlags.OK
};
}

View File

@ -12,35 +12,36 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
[TestSubject(typeof(CliRunner))]
public class CliRunnerTest
{
[TestMethod]
public void ExtractJson_Test()
{
String allOutput = " {\n \"DeviceId\": \"1000000267\"\n}\n";
string allOutput = " {\n \"DeviceId\": \"1000000267\"\n}\n";
CliRunner cliRunner = new CliRunner(false);
string json = cliRunner.ExtractJson(allOutput);
Assert.IsTrue(!string.IsNullOrEmpty(json));
Assert.IsFalse(string.IsNullOrEmpty(json));
}
[TestMethod]
public void ExtractJson_Test2()
{
String allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
string allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
CliRunner cliRunner = new CliRunner(false);
string json = cliRunner.ExtractJson(allOutput);
Assert.IsTrue(!string.IsNullOrEmpty(json));
Assert.IsFalse(string.IsNullOrEmpty(json));
}
[TestMethod]
public void TryJsonStringDeserialize_Test()
{
String allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
string allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
CliRunner cliRunner = new CliRunner(false);
string json = cliRunner.ExtractJson(allOutput);
var ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
bool ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
Assert.IsTrue(ok, "Deserialization failed");
Assert.IsNotNull(dto);
Assert.AreEqual("1000000267", dto.DeviceId);
@ -53,120 +54,165 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
[TestMethod]
public void RunMultipleTimesTestProgram_CheckParalelWork()
{
SerialPortData serialPort = new SerialPortData("COM3","cmdSleepTest.exe",74);
SerialPortData serialPort = new SerialPortData("COM3", "cmdSleepTest.exe", 74);
// Check if the executable exists in current directory
if (!System.IO.File.Exists(serialPort.SerialPortCmdClientPath))
{
Assert.Inconclusive($"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
Assert.Inconclusive(
$"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
return;
}
CliRunner cliRunner = new CliRunner(false);
CliRunner cliRunnerOne = new CliRunner(false);
DateTime StartTime = DateTime.Now;
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
cliRunnerOne.WaitAll();
TimeSpan OneEndTime = DateTime.Now - StartTime;
DateTime startTime = DateTime.Now;
StartTime = DateTime.Now;
Console.WriteLine($"Start Time Loop: {StartTime.ToString("yyyy-MM-dd HH:mm:ss")}");
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
serialPort,
SerialPortData.EMeterArg.AllParams);
cliRunnerOne.WaitAll();
TimeSpan oneEndTime = DateTime.Now - startTime;
startTime = DateTime.Now;
Console.WriteLine($"Start Time Loop: {startTime:yyyy-MM-dd HH:mm:ss}");
for (int i = 0; i < 100; i++)
{
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
serialPort,
SerialPortData.EMeterArg.AllParams);
}
cliRunner.WaitAll();
DateTime EndTime = DateTime.Now;
Console.WriteLine($"End Time Loop: {EndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
TimeSpan delta = EndTime - StartTime;
Console.WriteLine($"Delta Time One process {OneEndTime.Hours:D2}:{OneEndTime.Minutes:D2}:{OneEndTime.Seconds:D2}.{OneEndTime.Milliseconds:D3} " +
DateTime endTime = DateTime.Now;
Console.WriteLine($"End Time Loop: {endTime:yyyy-MM-dd HH:mm:ss}");
TimeSpan delta = endTime - startTime;
Console.WriteLine(
$"Delta Time One process {oneEndTime.Hours:D2}:{oneEndTime.Minutes:D2}:{oneEndTime.Seconds:D2}.{oneEndTime.Milliseconds:D3} " +
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
Console.WriteLine($"Total tasks: {cliRunner.TaskPool.Count}");
foreach (CliTaskInfo taskInfo in cliRunner.TaskPool)
{
Assert.IsNotNull(taskInfo);
Assert.IsNotNull(taskInfo.Task);
foreach (Task<JsonDataFromPoseidon> task in cliRunner.TaskPool)
{
if (task != null)
if (taskInfo.UseResult)
{
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
Assert.IsNotNull(task, "Expected Task<JsonDataFromPoseidon>.");
JsonDataFromPoseidon jsonDataFromPoseidon = task.Result;
Console.WriteLine($"Result: {jsonDataFromPoseidon.DeviceId}");
Console.WriteLine($"Result: {jsonDataFromPoseidon?.DeviceId}");
Assert.IsNotNull(jsonDataFromPoseidon);
}
else
{
Assert.Fail($"Task did not complete successfully. State={taskInfo.State}, Status={taskInfo.Task.Status}");
}
}
// Check that the total time is greater than the sum of the individual times
Assert.IsTrue((cliRunner.TaskPool.Count*OneEndTime.Ticks) > delta.Ticks);
Assert.IsTrue((cliRunner.TaskPool.Count * oneEndTime.Ticks) > delta.Ticks);
}
[TestMethod]
public async Task RunMultipleTimesTestProgram_CheckParalelWorkCumulative()
{
SerialPortData serialPort = new SerialPortData("COM3","cmdSleepTest.exe",74);
SerialPortData serialPort = new SerialPortData("COM3", "cmdSleepTest.exe", 74);
// Check if the executable exists in current directory
if (!System.IO.File.Exists(serialPort.SerialPortCmdClientPath))
{
Assert.Inconclusive($"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
Assert.Inconclusive(
$"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
return;
}
List<CliRunner> cliRunnerList = new List<CliRunner>();
for (int i = 0; i < 20; i++)
{
cliRunnerList.Add(new CliRunner(false));
}
CliRunner cliRunnerOne = new CliRunner(false);
DateTime StartTime = DateTime.Now;
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
cliRunnerOne.WaitAll();
TimeSpan OneEndTime = DateTime.Now - StartTime;
DateTime startTime = DateTime.Now;
StartTime = DateTime.Now;
Console.WriteLine($"Start Time Loop: {StartTime.ToString("yyyy-MM-dd HH:mm:ss")}");
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
serialPort,
SerialPortData.EMeterArg.AllParams);
cliRunnerOne.WaitAll();
TimeSpan oneEndTime = DateTime.Now - startTime;
startTime = DateTime.Now;
Console.WriteLine($"Start Time Loop: {startTime:yyyy-MM-dd HH:mm:ss}");
for (int iCliRunner = 0; iCliRunner < cliRunnerList.Count; iCliRunner++)
{
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
serialPort,
SerialPortData.EMeterArg.AllParams);
for (int i = 0; i < 20; i++)
{
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
serialPort,
SerialPortData.EMeterArg.AllParams);
}
}
// Wait for ALL tasks from ALL runners
var allTasks = cliRunnerList.SelectMany(r => r.TaskPool).ToArray();
Task[] allTasks = cliRunnerList
.SelectMany(r => r.TaskPool)
.Where(ti => ti != null && ti.Task != null)
.Select(ti => ti.Task)
.ToArray();
await Task.WhenAll(allTasks);
DateTime EndTime = DateTime.Now;
Console.WriteLine($"End Time Loop: {EndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
TimeSpan delta = EndTime - StartTime;
Console.WriteLine($"Delta Time One process {OneEndTime.Hours:D2}:{OneEndTime.Minutes:D2}:{OneEndTime.Seconds:D2}.{OneEndTime.Milliseconds:D3} " +
DateTime endTime = DateTime.Now;
Console.WriteLine($"End Time Loop: {endTime:yyyy-MM-dd HH:mm:ss}");
TimeSpan delta = endTime - startTime;
Console.WriteLine(
$"Delta Time One process {oneEndTime.Hours:D2}:{oneEndTime.Minutes:D2}:{oneEndTime.Seconds:D2}.{oneEndTime.Milliseconds:D3} " +
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
Console.WriteLine($"Total tasks: {cliRunnerList.Sum(runner => runner.TaskPool.Count)}");
for (int iCliRunner = 0; iCliRunner < cliRunnerList.Count; iCliRunner++)
{
string results = $"iCliRunner: {iCliRunner}";
foreach (Task<JsonDataFromPoseidon> task in cliRunnerList[iCliRunner].TaskPool)
foreach (CliTaskInfo taskInfo in cliRunnerList[iCliRunner].TaskPool)
{
if (task != null)
Assert.IsNotNull(taskInfo);
Assert.IsNotNull(taskInfo.Task);
if (taskInfo.UseResult)
{
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
Assert.IsNotNull(task, "Expected Task<JsonDataFromPoseidon>.");
JsonDataFromPoseidon jsonDataFromPoseidon = task.Result;
results += ($" ID: {jsonDataFromPoseidon.DeviceId}");
results += $" ID: {jsonDataFromPoseidon?.DeviceId}";
Assert.IsNotNull(jsonDataFromPoseidon);
}
else
{
Assert.Fail(
$"Task did not complete successfully. Runner={iCliRunner}, State={taskInfo.State}, Status={taskInfo.Task.Status}");
}
}
Console.WriteLine(results);
}
// Check that the total time is greater than the sum of the individual times
Assert.IsTrue((cliRunnerList.Count*OneEndTime.Ticks) > delta.Ticks);
Assert.IsTrue((cliRunnerList.Count * oneEndTime.Ticks) > delta.Ticks);
}
}
}

View File

@ -0,0 +1,249 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
{
[TestClass]
[TestSubject(typeof(CliRunner))]
public class CliRunnerTimeoutTest
{
private static string GetCmdSleepTestPath()
{
var serialPort = new SerialPortData("COM3", "cmdSleepTest.exe", 74);
return serialPort.SerialPortCmdClientPath;
}
private static void EnsureExeExists(string exePath)
{
if (!File.Exists(exePath))
{
Assert.Inconclusive(
$"Test executable '{exePath}' not found. " +
"Please ensure cmdSleepTest.exe exists in the test directory.");
}
}
[TestMethod]
public async Task Timeout_ShouldBeDetected_ForLongRunningTask()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(200);
bool timedOut = cliRunner.TimeOutReceived(100);
Assert.IsTrue(timedOut, "Timeout should have been detected.");
}
[TestMethod]
public async Task CancelUndoneTasksAsTimedOut_ShouldMarkRunningTaskAsTimedOut()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(150);
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.AreEqual(1, cliRunner.TaskPool.Count);
var taskInfo = cliRunner.TaskPool.Single();
Assert.IsNotNull(taskInfo);
Assert.AreEqual(CliTaskState.TimedOut, taskInfo.State);
Assert.IsFalse(taskInfo.UseResult, "Timed out task must not be used.");
}
[TestMethod]
public void CancelUndoneTasksAsTimedOut_ShouldKeepCompletedTaskCompleted()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=50");
try
{
cliRunner.WaitAll();
}
catch
{
// let assertions inspect states
}
cliRunner.RefreshTaskStates();
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.AreEqual(1, cliRunner.TaskPool.Count);
var taskInfo = cliRunner.TaskPool.Single();
Assert.IsNotNull(taskInfo);
Assert.AreEqual(CliTaskState.Completed, taskInfo.State);
Assert.IsTrue(taskInfo.UseResult, "Completed task should remain usable.");
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
Assert.IsNotNull(task);
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
Assert.IsNotNull(task.Result);
Assert.IsFalse(string.IsNullOrEmpty(task.Result.DeviceId));
}
[TestMethod]
public async Task CancelUndoneTasksAsTimedOut_ShouldKeepDoneTask_AndMarkUndoneTask()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=50");
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(250);
cliRunner.RefreshTaskStates();
Assert.AreEqual(2, cliRunner.TaskPool.Count);
Assert.AreEqual(1, cliRunner.TaskPool.Count(t => t.State == CliTaskState.Completed),
"Exactly one fast task should be completed before timeout handling.");
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
var doneTask = cliRunner.TaskPool.Single(t => t.State == CliTaskState.Completed);
var timedOutTask = cliRunner.TaskPool.Single(t => t.State == CliTaskState.TimedOut);
Assert.IsTrue(doneTask.UseResult);
Assert.IsFalse(timedOutTask.UseResult);
var completed = doneTask.Task as Task<JsonDataFromPoseidon>;
Assert.IsNotNull(completed);
Assert.AreEqual(TaskStatus.RanToCompletion, completed.Status);
Assert.IsNotNull(completed.Result);
}
[TestMethod]
public async Task AreTasksDone_ShouldReturnTrue_AfterTimeoutCancellation()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(150);
Assert.IsFalse(cliRunner.AreTasksDone(), "Task should still be running before timeout cancellation.");
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.IsTrue(cliRunner.AreTasksDone(),
"After timed out tasks are marked, runner should report no running tasks.");
}
[TestMethod]
public async Task TimedOutTask_ShouldNotHaveUseResult()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
await Task.Delay(150);
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
var taskInfo = cliRunner.TaskPool.Single();
Assert.AreEqual(CliTaskState.TimedOut, taskInfo.State);
Assert.IsFalse(taskInfo.UseResult);
}
[TestMethod]
public async Task MultipleSlowTasks_ShouldAllBeMarkedTimedOut()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
for (int i = 0; i < 5; i++)
{
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
}
await Task.Delay(200);
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.AreEqual(5, cliRunner.TaskPool.Count);
Assert.AreEqual(5, cliRunner.TaskPool.Count(t => t.State == CliTaskState.TimedOut));
Assert.AreEqual(0, cliRunner.TaskPool.Count(t => t.UseResult));
}
[TestMethod]
public void FastTask_WithInvalidJson_ShouldFinishButNotProduceUsefulResult()
{
string exePath = GetCmdSleepTestPath();
EnsureExeExists(exePath);
var cliRunner = new CliRunner(false);
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=50 invalidjson=true");
try
{
cliRunner.WaitAll();
}
catch
{
}
cliRunner.RefreshTaskStates();
Assert.AreEqual(1, cliRunner.TaskPool.Count);
var taskInfo = cliRunner.TaskPool.Single();
Assert.AreEqual(CliTaskState.Completed, taskInfo.State);
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
Assert.IsNotNull(task);
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
// depending on your TryJsonStringDeserialize fallback,
// result may be null or an empty/default object
// so avoid asserting UseResult=false here
// instead assert no valid device id
if (task.Result != null)
{
Assert.IsTrue(string.IsNullOrEmpty(task.Result.DeviceId),
"Invalid JSON should not produce a valid DeviceId.");
}
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
{
[TestClass]
[TestSubject(typeof(CliRunner))]
public class CliRunnerTimeoutUnitTest
{
[TestMethod]
public async Task CancelUndoneTasksAsTimedOut_ShouldMarkOnlyRunningTasks()
{
var cliRunner = new CliRunner(false);
var completedTask = Task.FromResult("done");
var cts = new CancellationTokenSource();
var runningTask = Task.Delay(TimeSpan.FromSeconds(30), cts.Token);
cliRunner.AddTaskForTest(completedTask, "completed");
cliRunner.AddTaskForTest(runningTask, "running", cts);
await Task.Delay(50);
cliRunner.RefreshTaskStates();
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.AreEqual(2, cliRunner.TaskPool.Count);
var completed = cliRunner.TaskPool.First(t => t.Name == "completed");
var timedOut = cliRunner.TaskPool.First(t => t.Name == "running");
Assert.AreEqual(CliTaskState.Completed, completed.State);
Assert.IsTrue(completed.UseResult);
Assert.AreEqual(CliTaskState.TimedOut, timedOut.State);
Assert.IsFalse(timedOut.UseResult);
}
[TestMethod]
public void TimeOutReceived_ShouldReturnTrue_WhenElapsedExceedsTimeout()
{
var cliRunner = new CliRunner(false);
Thread.Sleep(30);
Assert.IsTrue(cliRunner.TimeOutReceived(1));
}
[TestMethod]
public async Task AreTasksDone_ShouldReturnTrue_WhenTimedOutTasksAreMarked()
{
var cliRunner = new CliRunner(false);
var cts = new CancellationTokenSource();
var runningTask = Task.Delay(TimeSpan.FromSeconds(30), cts.Token);
cliRunner.AddTaskForTest(runningTask, "running", cts);
await Task.Delay(50);
Assert.IsFalse(cliRunner.AreTasksDone());
cliRunner.CancelUndoneTasksAsTimedOut();
cliRunner.RefreshTaskStates();
Assert.IsTrue(cliRunner.AreTasksDone());
}
}
}

View File

@ -125,6 +125,8 @@
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\wiredProtocol\TouchReadBaudRateDetectionTests.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\wiredProtocol\TouchReadFrameBuilderTest.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTest.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutTest.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutUnitTest.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReaderTest.cs" />
<Compile Include="Rig\RegisterReaders\PoseidonReader\UniHeadTestCtrlTest.cs" />
<Compile Include="Rig\Scales\MettlerToledo\ReadStableMassOpTest.cs" />