diff --git a/TBF/Properties/AssemblyInfo.cs b/TBF/Properties/AssemblyInfo.cs index 65a82848c..9cf01f57a 100644 --- a/TBF/Properties/AssemblyInfo.cs +++ b/TBF/Properties/AssemblyInfo.cs @@ -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")] diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs index 6eda98542..648ab375b 100644 --- a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs @@ -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) { @@ -3885,20 +3909,61 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations } } #endregion - - private bool isQ3CalibValid = false; - private double q3Calib = 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}; - 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) @@ -3938,7 +4004,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations log.Debug($"Channel {iChannel}: SKIPPED (no data)"); continue; } - + var start = channelData[0]; var end = channelData[1]; @@ -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; - } - - double scaling = 15625.0; - log.Debug($"Scaling={scaling}"); - - 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"); + + if (Math.Abs(recalculatedDeltaVolume) <= Double.Epsilon) + { + log.Debug($"Channel {iChannel}: recalculatedDeltaVolume is zero"); + continue; + } + + 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}"); + } log.Debug("=== Q3 CALIBRATION END ==="); diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs index 045dbeac8..fe4c7c1c9 100644 --- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs +++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs @@ -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 taskPool = new List(); - private long startTime; - private long incommingTime; - - public List 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 => { }); } + private readonly List taskPool = new List(); + private long startTimeMs; + private long incommingTimeMs; - public void StartAll() + public List 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; + get { return incommingTimeMs; } } - public bool TimeOutReceived(long timeout) - { - return (DateTime.Now.Ticks - startTime) > timeout; - } - public CliRunner(bool isCliLogging) { - startTime = DateTime.Now.Ticks; + ResetStartTime(); if (isCliLogging) { @@ -60,88 +43,180 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop { log = new ScopedLoggerFactory().CreateLogger( @"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}"); } } } - /// - /// - /// - /// - /// - /// - public void AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg) + public void ResetStartTime() { - var task = SendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg)); - taskPool.Add(task); + startTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } - - public void AddSendAsync(string fileName, string args) - { - var task = SendAsync(fileName, args); - taskPool.Add(task); - } - - - public async Task SendAsync(string fileName, string args, CancellationToken ct = default) - { - var psi = new ProcessStartInfo - { - FileName = fileName, - Arguments = args, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - using var process = new Process { StartInfo = psi, EnableRaisingEvents = true }; - process.Start(); - - Task stdOutTask = process.StandardOutput.ReadToEndAsync(); - Task stdErrTask = process.StandardError.ReadToEndAsync(); - - try + public void Clear() + { + foreach (var item in taskPool) { - await Task.WhenAll(stdOutTask, stdErrTask, process.WaitForExitAsync(ct)); - } - catch (OperationCanceledException) - { - if (!process.HasExited) + try + { + item?.Cts?.Dispose(); + } + catch { - process.Kill(); } - throw; + try + { + if (item?.Process != null) + { + item.Process.Dispose(); + } + } + catch + { + } } - //comming answer from serial port - good place for time stamp - incommingTime = DateTime.Now.Ticks; - string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? ""); - log?.Debug(allOutput); - return allOutput; + taskPool.Clear(); } - - public void AddRunAndCaptureJsonAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg) where T : new() + public void WaitAll() { - var task = RunAndCaptureJsonAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg)); - taskPool.Add(task); + 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 async Task RunAndCaptureJsonAsync(string fileName, string args, CancellationToken ct = default) where T : new() + + 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) + { + AddSendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg)); + } + + public void AddSendAsync(string fileName, string args) + { + 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 SendAsync(string fileName, string args, CliTaskInfo info, CancellationToken ct = default) { var psi = new ProcessStartInfo { @@ -153,80 +228,210 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop CreateNoWindow = true }; - using var process = new Process { StartInfo = psi, EnableRaisingEvents = true }; - process.Start(); + using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true }) + { + info.Process = process; - var stdoutTask = process.StandardOutput.ReadToEndAsync(); - var stderrTask = process.StandardError.ReadToEndAsync(); + try + { + process.Start(); - await Task.WhenAll(stdoutTask, stderrTask, process.WaitForExitAsync(ct)); + Task stdOutTask = process.StandardOutput.ReadToEndAsync(); + Task stdErrTask = process.StandardError.ReadToEndAsync(); - string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? ""); - log?.Debug(allOutput); + try + { + await Task.WhenAll(stdOutTask, stdErrTask, process.WaitForExitAsync(ct)); + } + catch (OperationCanceledException) + { + if (info.State != CliTaskState.TimedOut) + info.State = CliTaskState.Canceled; - string json = ExtractJson(allOutput); - if (TryJsonStringDeserialize(json, out T result)) return result; - return default; + if (!process.HasExited) + { + process.Kill(); + } + + throw; + } + + 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 bool TryJsonStringDeserialize(string json, out T runAndCaptureJsonAsync) where T : new() + public void AddRunAndCaptureJsonAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg) where T : new() + { + AddRunAndCaptureJsonAsync( + data.SerialPortCmdClientPath, + data.DefaultArgSettings(eMeterArg)); + } + + public void AddRunAndCaptureJsonAsync(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(fileName, args, info, cts.Token); + + info.Task = task; + taskPool.Add(info); + } + + public async Task RunAndCaptureJsonAsync(string fileName, string args, CliTaskInfo info, CancellationToken ct = default) where T : new() + { + var psi = new ProcessStartInfo + { + FileName = fileName, + Arguments = args, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = 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); + T result; + if (TryJsonStringDeserialize(json, out result)) + { + info.State = CliTaskState.Completed; + return result; + } + + 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(string json, out T value) where T : new() { if (json != null) { try { - runAndCaptureJsonAsync = JsonConvert.DeserializeObject(json); + value = JsonConvert.DeserializeObject(json); return true; } catch (Exception ex) { - log.Debug(ex.Message); - runAndCaptureJsonAsync = TryConvert(json); + log?.Debug(ex.Message); + value = TryConvert(json); return true; } } - runAndCaptureJsonAsync = default; + value = default(T); return false; } - private static T TryConvert(string json) where T : new() { - - T obj = new T(); + T obj = new T(); - try + try + { + JObject jObject = JObject.Parse(json); + + foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { - JObject jObject = JObject.Parse(json); + if (!prop.CanWrite) + continue; - foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) + JToken token; + if (jObject.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out token)) { - if (!prop.CanWrite) continue; - - JToken token; - if (jObject.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out token)) + try + { + object value = token.ToObject(prop.PropertyType); + prop.SetValue(obj, value); + } + catch { - try - { - object value = token.ToObject(prop.PropertyType); - prop.SetValue(obj, value); - } - catch - { - // leave default if conversion fails - } } - // else → keep default value } } - catch (Exception ex) - { - Console.WriteLine($"TryConvert failed: {ex.Message}"); - } + } + catch (Exception ex) + { + Console.WriteLine($"TryConvert failed: {ex.Message}"); + } - return obj; - + 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 + }); + } } - } \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs new file mode 100644 index 000000000..aabb6ec79 --- /dev/null +++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskState.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskState.cs new file mode 100644 index 000000000..3cd761926 --- /dev/null +++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskState.cs @@ -0,0 +1,13 @@ + +namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop +{ + public enum CliTaskState + { + Running, + Completed, + TimedOut, + Canceled, + Faulted + } + +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs index 7451e9cb6..1d0347c5f 100644 --- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs +++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs @@ -336,10 +336,12 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop /// for debug purposes what time will consume answer /// 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); - return Event.Busy; + _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; } - return Event.Busy; + 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); + var firstTaskInfo = CliRunner.TaskPool + .FindLast(t => t.Task is Task && t.UseResult); - - if (firstTask != null && firstTask is Task) + if (firstTaskInfo != null) { - string data = null; - data = (firstTask as Task).Result; + var task = (Task)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 + 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(serialPort, SerialPortData.EMeterArg.AllParams); + + CliRunner.Clear(); + _lastOpTimedOut = false; + CliRunner.AddRunAndCaptureJsonAsync(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); - if (first != null && first is Task) - { - data = (first as Task).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 && t.UseResult); + + if (firstTaskInfo != null) + { + var task = (Task)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); - if (_isReadingStart) - { - //volume - beginWMState = VolumeLi; - } - else - { - //volume - endWMState = VolumeLi; - } - } + double volumeLi = Units.ConvertFrom(Unit.USgal, volume); + if (_isReadingStart) + beginWMState = volumeLi; + else + endWMState = volumeLi; + } } - - //Finish reading and loop - _currentOp = CurrentPoseidonOp.Done; + + CliRunner.Clear(); + _currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done; return Event.Done; } + return Event.None; - return Event.None; } + /// Stop this operation public void Stop() { diff --git a/TBF/Rig/TestMethods/FlyingStartMassCollection/FlyingStartMassCollectionSeq.cs b/TBF/Rig/TestMethods/FlyingStartMassCollection/FlyingStartMassCollectionSeq.cs index a30f894f8..6dffef161 100644 --- a/TBF/Rig/TestMethods/FlyingStartMassCollection/FlyingStartMassCollectionSeq.cs +++ b/TBF/Rig/TestMethods/FlyingStartMassCollection/FlyingStartMassCollectionSeq.cs @@ -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) diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index 3729549b8..d832d8d0d 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -1484,6 +1484,8 @@ RRCfgCtrl.cs + + diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs index cf2b272c4..a15483b34 100644 --- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs +++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs @@ -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(reader, "optoDataCount"); + counts[i] = GetPrivateField(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(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}"); + InvokePrivate(reader, "AddTestStartEndMarksToData", new object[] { 0, 0 }); + InvokePrivate(reader, "DataStreamPostProcessing"); + InvokePrivate(reader, "PrepareCalculatedChannelData"); - object[] markArgs = { 0, 0 }; - - try - { - InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs); - Console.WriteLine("AddTestStartEndMarksToData OK"); - - InvokePrivate(reader, "DataStreamPostProcessing"); - Console.WriteLine("DataStreamPostProcessing OK"); - - InvokePrivate(reader, "PrepareCalculatedChannelData"); - Console.WriteLine("PrepareCalculatedChannelData OK"); - } - catch (Exception ex) - { - Assert.Fail( - "Post-processing failed.\n" + - $"Exception: {ex.GetType().Name}: {ex.Message}\n" + - $"StackTrace:\n{ex.StackTrace}"); - } - - Console.WriteLine("=== CALCULATED VALUES ==="); - Console.WriteLine($"NoSamples = {reader.NoSamples}"); - Console.WriteLine($"VolumeLtrStart = {reader.VolumeLtrStart}"); - Console.WriteLine($"VolumeLtrEnd = {reader.VolumeLtrEnd}"); - Console.WriteLine($"TimestampSecStart = {reader.TimestampSecStart}"); - Console.WriteLine($"TimestampSecEnd = {reader.TimestampSecEnd}"); - - Assert.IsFalse(double.IsNaN(reader.VolumeLtrStart), "VolumeLtrStart is NaN"); - Assert.IsFalse(double.IsNaN(reader.VolumeLtrEnd), "VolumeLtrEnd is NaN"); - Assert.IsFalse(double.IsNaN(reader.TimestampSecStart), "TimestampSecStart is NaN"); - Assert.IsFalse(double.IsNaN(reader.TimestampSecEnd), "TimestampSecEnd is NaN"); - - Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart, - $"Timestamp ordering invalid: start={reader.TimestampSecStart}, end={reader.TimestampSecEnd}"); - - Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart, - $"Volume ordering invalid: start={reader.VolumeLtrStart}, end={reader.VolumeLtrEnd}"); - - // When parser input is finally correct, replace these expected values: - const double expectedVolumeStart = 0.0; - const double expectedVolumeEnd = 0.0; - const double expectedTimeStart = 0.0; - const double expectedTimeEnd = 0.0; - const double tolerance = 0.000001; - - Console.WriteLine("=== EXPECTED VS ACTUAL ==="); - Console.WriteLine($"expectedVolumeStart = {expectedVolumeStart}, actual = {reader.VolumeLtrStart}"); - Console.WriteLine($"expectedVolumeEnd = {expectedVolumeEnd}, actual = {reader.VolumeLtrEnd}"); - Console.WriteLine($"expectedTimeStart = {expectedTimeStart}, actual = {reader.TimestampSecStart}"); - Console.WriteLine($"expectedTimeEnd = {expectedTimeEnd}, actual = {reader.TimestampSecEnd}"); - - Assert.AreEqual(expectedVolumeStart, reader.VolumeLtrStart, tolerance, "VolumeLtrStart mismatch"); - Assert.AreEqual(expectedVolumeEnd, reader.VolumeLtrEnd, tolerance, "VolumeLtrEnd mismatch"); - Assert.AreEqual(expectedTimeStart, reader.TimestampSecStart, tolerance, "TimestampSecStart mismatch"); - Assert.AreEqual(expectedTimeEnd, reader.TimestampSecEnd, tolerance, "TimestampSecEnd mismatch"); + 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,29 +166,25 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations int finalOptoDataCount = GetPrivateField(reader, "optoDataCount"); - Assert.IsTrue( - finalOptoDataCount > 1, - "Need at least 2 valid telegrams. Check exact telegram format / CRC in LoadRealInputLinesWithRollover()."); - - reader.TestStartTelegramIx = 0; - reader.TestEndTelegramIx = finalOptoDataCount - 1; - - object[] markArgs = { 0, 0 }; - InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs); - InvokePrivate(reader, "DataStreamPostProcessing"); - InvokePrivate(reader, "PrepareCalculatedChannelData"); - - Console.WriteLine($"VolumeLtrStart={reader.VolumeLtrStart}"); - Console.WriteLine($"VolumeLtrEnd={reader.VolumeLtrEnd}"); - Console.WriteLine($"TimestampSecStart={reader.TimestampSecStart}"); - Console.WriteLine($"TimestampSecEnd={reader.TimestampSecEnd}"); - - Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart, - "Normalized end time should be >= start time"); - Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart, - "Normalized end volume should be >= start volume"); + Assert.AreEqual(3, finalOptoDataCount, + "Expected all provided rollover input lines to be parsed as telegrams."); } + + [TestMethod] + public void PrepareCalculatedChannelDataSimulationForTest_ShouldProduceUsableSamples() + { + var reader = new GenesisSmartReader(CreateCfg()); + InitializeReaderForTest(reader); + reader.PrepareCalculatedChannelDataSimulationForTest(); + + 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) { const int channelCount = 3; @@ -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; } diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.cs index 823d713bd..50e86ba40 100644 --- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.cs +++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderChannelAveragingTests.cs @@ -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); diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderStopTests.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderStopTests.cs index e28abd4ea..79af15151 100644 --- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderStopTests.cs +++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderStopTests.cs @@ -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(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(reader, "dataStreamState"); var stopQueueData = GetPrivateField(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."); } diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs index f080eb425..2bebb3947 100644 --- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs +++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs @@ -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(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(reader, "volumeRawExtLast"); @@ -284,27 +308,25 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations var optoData = GetPrivateField(reader, "optoData"); var optoDataCount = GetPrivateField(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,39 +461,37 @@ 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"); - 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."); + 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] @@ -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() - { - - } + } } \ No newline at end of file diff --git a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader_Q3Test.cs b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader_Q3Test.cs index bee7df28c..71830e2be 100644 --- a/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader_Q3Test.cs +++ b/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader_Q3Test.cs @@ -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 }; } diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTest.cs index c4a89e8df..7fd7fba15 100644 --- a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTest.cs +++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTest.cs @@ -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(json, out var dto); + bool ok = cliRunner.TryJsonStringDeserialize(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); - - // Check if the executable exists in current directory + SerialPortData serialPort = new SerialPortData("COM3", "cmdSleepTest.exe", 74); + 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(serialPort, SerialPortData.EMeterArg.AllParams); + DateTime startTime = DateTime.Now; + + cliRunnerOne.AddRunAndCaptureJsonAsync( + serialPort, + SerialPortData.EMeterArg.AllParams); + cliRunnerOne.WaitAll(); - TimeSpan OneEndTime = DateTime.Now - StartTime; - - StartTime = DateTime.Now; - Console.WriteLine($"Start Time Loop: {StartTime.ToString("yyyy-MM-dd HH:mm:ss")}"); - + + 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(serialPort, SerialPortData.EMeterArg.AllParams); + cliRunner.AddRunAndCaptureJsonAsync( + 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} " + - $"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.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 (Task task in cliRunner.TaskPool) + + foreach (CliTaskInfo taskInfo in cliRunner.TaskPool) { - if (task != null) + Assert.IsNotNull(taskInfo); + Assert.IsNotNull(taskInfo.Task); + + if (taskInfo.UseResult) { + var task = taskInfo.Task as Task; + Assert.IsNotNull(task, "Expected Task."); + 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); - - // Check if the executable exists in current directory + SerialPortData serialPort = new SerialPortData("COM3", "cmdSleepTest.exe", 74); + 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 cliRunnerList = new List(); - + for (int i = 0; i < 20; i++) + { cliRunnerList.Add(new CliRunner(false)); - + } + CliRunner cliRunnerOne = new CliRunner(false); - DateTime StartTime = DateTime.Now; - cliRunnerOne.AddRunAndCaptureJsonAsync(serialPort, SerialPortData.EMeterArg.AllParams); + DateTime startTime = DateTime.Now; + + cliRunnerOne.AddRunAndCaptureJsonAsync( + serialPort, + SerialPortData.EMeterArg.AllParams); + cliRunnerOne.WaitAll(); - TimeSpan OneEndTime = DateTime.Now - StartTime; - - StartTime = DateTime.Now; - Console.WriteLine($"Start Time Loop: {StartTime.ToString("yyyy-MM-dd HH:mm:ss")}"); + + 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(serialPort, + cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync( + serialPort, SerialPortData.EMeterArg.AllParams); for (int i = 0; i < 20; i++) { - cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync(serialPort, + cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync( + 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} " + - $"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.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 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; + Assert.IsNotNull(task, "Expected Task."); + 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); } } } \ No newline at end of file diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutTest.cs new file mode 100644 index 000000000..66a0a303a --- /dev/null +++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutTest.cs @@ -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(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(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(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; + 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(exePath, "sleep=50"); + cliRunner.AddRunAndCaptureJsonAsync(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; + 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(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(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(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(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; + 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."); + } + } + } +} \ No newline at end of file diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutUnitTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutUnitTest.cs new file mode 100644 index 000000000..79ab85aea --- /dev/null +++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutUnitTest.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj index 165cb4003..349f89f5d 100644 --- a/TBFTests/TBFTests.csproj +++ b/TBFTests/TBFTests.csproj @@ -125,6 +125,8 @@ + +