Compare commits

..
18 changed files with 1143 additions and 20 deletions
+2 -2
View File
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
[assembly: AssemblyVersion("3.9.3155.0")]
[assembly: AssemblyFileVersion("3.9.3155.0")]
[assembly: AssemblyVersion("3.9.3159.0")]
[assembly: AssemblyFileVersion("3.9.3159.0")]
@@ -243,6 +243,7 @@ namespace TBF.Rig.ControlBoard.Uni
public void Stop()
{
log.Info("Op.Stop()");
log.WarnFormat("FLYING_BOARD_OPERATION_STOP: state={0}; invoking StopAll(false), hydraulic flow may stop.", opState);
uniCB.SetActivity((opState == OpState.TestCompleted) ? Activity.TestCompleted : Activity.Idle);
uniCB.StopAll(false);
}
+1
View File
@@ -63,6 +63,7 @@ namespace TBF.Rig.Operations
if (Bridge.Ui2MachineQueue.Contains(UI2BenchCmd.Stop))
{
log.Warn("CENTRAL_STOP_DEQUEUED: CheckUIOp received Stop.");
do {
cmd = Bridge.Ui2MachineQueue.Dequeue();
}
@@ -0,0 +1,46 @@
using System;
namespace TBF.Rig.RegisterReaders.AsicReader
{
/// <summary>Meter-time endpoint policy; wall-clock deadline is owned by the sequence.</summary>
internal sealed class AsicFlyingDrain
{
internal double Target { get; }
internal bool Complete { get; private set; }
internal int SelectedIndex { get; private set; }
private readonly bool allowBeyond;
private bool targetReached;
private double lastTime;
internal AsicFlyingDrain(double start, double referenceSeconds, double endAtDiv,
int endIndex, int extraSeconds, bool allowBeyond)
{
if (double.IsNaN(start) || double.IsNaN(endAtDiv) || referenceSeconds <= 0 ||
double.IsInfinity(start) || double.IsInfinity(endAtDiv) || double.IsNaN(referenceSeconds) ||
double.IsInfinity(referenceSeconds) || start <= 0 || endAtDiv <= start)
throw new InvalidOperationException("ASIC drain requires valid initial and final meter timestamps.");
if (extraSeconds != 0 && (extraSeconds < 10 || extraSeconds > 50 || extraSeconds % 10 != 0))
throw new InvalidOperationException("ASIC drain duration must be Measurement time or 10, 20, 30, 40, 50 seconds.");
this.allowBeyond = allowBeyond && extraSeconds != 0;
Target = extraSeconds == 0 ? start + referenceSeconds : endAtDiv + extraSeconds;
if (!this.allowBeyond) Target = Math.Min(Target, start + referenceSeconds);
SelectedIndex = endIndex;
lastTime = endAtDiv;
Complete = endAtDiv >= Target;
}
internal void Observe(int index, double time)
{
if (Complete || double.IsNaN(time) || double.IsInfinity(time) || time < lastTime) return;
lastTime = time;
if (!targetReached)
{
if (allowBeyond || time <= Target) SelectedIndex = index;
if (time >= Target) targetReached = true;
}
// Existing end volume uses a centred 3-sample average. Keep its right neighbour.
if (targetReached && index >= SelectedIndex + TestMethods.iPerlCommunication.iPerlHead.IperlAsicHead.StartEndFilterSamplesCount2)
Complete = true;
}
}
}
@@ -22,6 +22,8 @@ namespace TBF.Rig.RegisterReaders.AsicReader
private readonly object sync = new object();
private StreamWriter writer;
private string currentTestContext;
private DateTime? receiveTimestamp;
private string selectionStatus;
public void Write(
AsicReaderCfg cfg,
@@ -35,7 +37,9 @@ namespace TBF.Rig.RegisterReaders.AsicReader
DiagnosticLedState7Data parsedData,
double extendedVolume,
double extendedTimestamp,
string testContext = null)
string testContext = null,
DateTime? pcReceived = null,
string status = null)
{
try
{
@@ -51,6 +55,8 @@ namespace TBF.Rig.RegisterReaders.AsicReader
lock (sync)
{
currentTestContext = testContext;
receiveTimestamp = pcReceived;
selectionStatus = status;
string extension = IsLogFormat(cfg.OpticalDiagnosticFormat) ? ".log" : ".csv";
OpticalLogArchive.Append(diagnosticRoot, batchNr, directory =>
{
@@ -150,13 +156,13 @@ namespace TBF.Rig.RegisterReaders.AsicReader
string parsedValues = cfg.IncludeParsedValues && parsedData != null ? parsedData.ToString() : String.Empty;
writer.WriteLine(String.Join(";", new[]
{
Csv(DateTime.Now.ToString("O", CultureInfo.InvariantCulture)),
Csv((receiveTimestamp ?? DateTime.Now).ToString("O", CultureInfo.InvariantCulture)),
Csv(batchNr.ToString(CultureInfo.InvariantCulture)),
Csv(wmPosition.ToString(CultureInfo.InvariantCulture)),
Csv(serialNumber),
Csv(telegramIndex.ToString(CultureInfo.InvariantCulture)),
Csv((isMeasurementData ? "Measurement" : "Flush") + ";" + currentTestContext),
Csv(parsedData == null ? "Invalid" : "Parsed"),
Csv(selectionStatus ?? (parsedData == null ? "Invalid" : "Parsed")),
Csv(rawFormat),
Csv(FormatRaw(rawFormat, rawLine, rawBytes)),
Csv(cfg.IncludeParsedValues ? Number(extendedTimestamp) : String.Empty),
@@ -172,8 +178,8 @@ namespace TBF.Rig.RegisterReaders.AsicReader
{
string rawFormat = NormalizedRawFormat(cfg.RawOpticalDataFormat);
writer.WriteLine("{0:O} | batch={1} | wm={2} | serial={3} | telegram={4} | capture={5} | parse={6} | {7}",
DateTime.Now, batchNr, wmPosition, serialNumber ?? String.Empty, telegramIndex,
isMeasurementData ? "Measurement" : "Flush", parsedData == null ? "Invalid" : "Parsed", currentTestContext);
receiveTimestamp ?? DateTime.Now, batchNr, wmPosition, serialNumber ?? String.Empty, telegramIndex,
isMeasurementData ? "Measurement" : "Flush", selectionStatus ?? (parsedData == null ? "Invalid" : "Parsed"), currentTestContext);
writer.WriteLine("RAW [{0}] {1}", rawFormat, FormatRaw(rawFormat, rawLine, rawBytes));
if (cfg.IncludeParsedValues)
{
@@ -1,4 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Linq;
using TBF.Rig.Diagnostics;
using TBF.Rig.Generic;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
@@ -34,6 +39,13 @@ namespace TBF.Rig.RegisterReaders.AsicReader
protected override void OpticalTestStarted(string name, int repetition)
{
if (experimentalWorker != null) FinishExperimentalOpticalInput();
if (ExperimentalSelectionEnabled)
experimentalWorker = new AsicOpticalBatchWorker(asicCfg.EnableOpticalBatchSelection ? asicCfg.OpticalBatchSize : 1,
asicCfg.OnlyNewOpticalMeterSecond, asicCfg.OpticalBatchTimeoutSeconds);
clockProbe = System.Diagnostics.Stopwatch.StartNew();
clockProbeFirstTicks = null;
flyingDrain = null;
OpticalTrace.Configure(name, repetition);
OpticalTrace.Begin();
}
@@ -56,6 +68,98 @@ namespace TBF.Rig.RegisterReaders.AsicReader
}
private readonly AsicReaderCfg asicCfg;
private AsicOpticalBatchWorker experimentalWorker;
protected override void CloseOptoSerialPort()
{
try { FinishExperimentalOpticalInput(); }
finally { base.CloseOptoSerialPort(); }
}
protected override bool ClearOpticalOnOpen => asicCfg == null || !asicCfg.EnableOpticalExperiments || asicCfg.ClearOpticalInputOnOpen;
protected override bool ExperimentalSelectionEnabled => asicCfg != null && asicCfg.EnableOpticalExperiments &&
(asicCfg.EnableOpticalBatchSelection || asicCfg.OnlyNewOpticalMeterSecond);
protected override bool ReadExperimentalOpticalInput()
{
if (experimentalWorker == null) return false;
PublishExperimentalDecisions();
// Drain the available COM input, bounded per scheduler turn so Stop remains responsive.
for (int i = 0; i < 256 && OpticalBytesAvailable > 0; i++)
{
byte[] bytes;
string line;
try { line = ReadOptoLine(out bytes); }
catch (TimeoutException) { break; } // Incomplete line is not a fatal reader failure.
log4net.LogManager.GetLogger("AnsiIPerlOptical").Debug("EXPERIMENT_RX | COM" + OptoComPortNr + " | " + line.Replace('\t', ' '));
experimentalWorker.Enqueue(line, bytes, DateTime.Now, Convert.ToDouble(ProcessData.RefFlow.Val));
}
PublishExperimentalDecisions();
return true;
}
private void PublishExperimentalDecisions()
{
AsicOpticalBatchWorker.Frame frame;
while (experimentalWorker.TryRead(out frame))
{
int selectedIndex = -1;
if (frame.Keep)
{
CommitExperimentalOpticalSample(frame.Data, frame.RefFlow, frame.Volume, frame.Time);
selectedIndex = TestEndTelegramIx;
}
opticalDiagnosticWriter.Write(asicCfg, ProcessData.BatchNr, Position, SerialNr, frame.ReceiveIndex,
true, frame.Line, frame.Bytes, frame.Data, frame.Volume, frame.Time,
OpticalTrace.Context + ";sourceIndex=" + frame.ReceiveIndex + ";selectedIndex=" + selectedIndex,
frame.PcTimestamp, frame.Reason);
}
}
protected override void FinishExperimentalOpticalInput()
{
if (experimentalWorker == null) return;
// Worker has no reader lock or COM access, so joining under the reader lock is safe.
experimentalWorker.Complete();
PublishExperimentalDecisions();
experimentalWorker.Dispose();
experimentalWorker = null;
}
private System.Diagnostics.Stopwatch clockProbe;
private uint? clockProbeFirstTicks;
private double clockProbeFirstHost, clockProbeLastLog;
private AsicFlyingDrain flyingDrain;
internal bool FlyingDrainEnabled => asicCfg != null && asicCfg.EnableFlyingDrain && !asicCfg.EnableFlyingEarlyCapture;
internal bool FlyingEarlyCaptureEnabled => asicCfg != null && asicCfg.EnableFlyingEarlyCapture;
internal int FlyingEarlyCaptureSeconds => Math.Max(1, Math.Min(60, asicCfg.FlyingEarlyCaptureSeconds));
internal bool FlyingDrainCompleted { get { lock (this) return flyingDrain == null ||
(flyingDrain.Complete && HasFlyingEndpointWindow(flyingDrain.SelectedIndex)); } }
internal void BeginFlyingDrain(double referenceSeconds)
{
if (!FlyingDrainEnabled) return;
lock (this)
{
HoldFlyingEndpoint();
flyingDrain = new AsicFlyingDrain(TimestampSecStart, referenceSeconds, TimestampSecEnd,
TestEndTelegramIx, asicCfg.FlyingDrainSeconds, asicCfg.FlyingDrainAllowBeyondTestTime);
WriteDrainMarker("DRAIN_BEGIN;target=" + flyingDrain.Target.ToString("R", System.Globalization.CultureInfo.InvariantCulture));
ReplayFlyingSamplesAfter(TestEndTelegramIx, flyingDrain.Observe);
SelectFlyingEndpoint(flyingDrain.SelectedIndex);
}
}
internal void FinishFlyingDrain(string reason)
{
lock (this)
{
if (flyingDrain == null) return;
SelectFlyingEndpoint(flyingDrain.SelectedIndex);
WriteDrainMarker("DRAIN_END;reason=" + reason + ";selected=" + flyingDrain.SelectedIndex);
flyingDrain = null;
}
}
private void WriteDrainMarker(string message)
{
log4net.LogManager.GetLogger("ASIC_OPTO").Info("reader=" + Name + ";WM=" + Position + ";" + message);
OpticalTrace.Selected(false, TestEndTelegramIx, VolumeLtrEnd, TimestampSecEnd, message);
}
private readonly AsicOpticalDiagnosticWriter opticalDiagnosticWriter =
new AsicOpticalDiagnosticWriter();
@@ -113,6 +217,35 @@ namespace TBF.Rig.RegisterReaders.AsicReader
if (asicCfg == null)
return;
if (isMeasurementData && parsedData != null && clockProbe != null)
{
double host = clockProbe.Elapsed.TotalSeconds;
if (!clockProbeFirstTicks.HasValue)
{
clockProbeFirstTicks = parsedData.AsicTimestampTicks;
clockProbeFirstHost = host;
clockProbeLastLog = host - 5;
}
if (host - clockProbeLastLog >= 5)
{
uint ticks = unchecked(parsedData.AsicTimestampTicks - clockProbeFirstTicks.Value);
double hostDelta = host - clockProbeFirstHost;
string probe = string.Format(System.Globalization.CultureInfo.InvariantCulture,
"ASIC_CLOCK_PROBE;reader={0};COM={1};sample={2};pcUtc={3:O};hostDeltaS={4:F3};rawTicks={5};deltaTicks={6};ticksPerHostSecond={7:F3};parsedTime={8:R}",
Name, OptoComPortNr, telegramIndex, DateTime.UtcNow, hostDelta, parsedData.AsicTimestampTicks,
ticks, hostDelta > 0 ? ticks / hostDelta : 0, extendedTimestamp);
log4net.LogManager.GetLogger(typeof(AsicReader)).Info(probe);
OpticalTrace.Selected(false, telegramIndex, extendedVolume, extendedTimestamp, probe);
clockProbeLastLog = host;
}
}
if (isMeasurementData && parsedData != null && flyingDrain != null && IsValidFlyingSample(telegramIndex))
{
flyingDrain.Observe(telegramIndex, extendedTimestamp);
SelectFlyingEndpoint(flyingDrain.SelectedIndex);
}
opticalDiagnosticWriter.Write(
asicCfg,
ProcessData.BatchNr,
@@ -128,4 +261,109 @@ namespace TBF.Rig.RegisterReaders.AsicReader
OpticalTrace.Context);
}
}
/// <summary>Experimental selection worker. Never owns or closes a COM port.</summary>
internal sealed class AsicOpticalBatchWorker : IDisposable
{
internal sealed class Frame
{
internal string Line, Reason;
internal byte[] Bytes;
internal DateTime PcTimestamp;
internal int ReceiveIndex;
internal double RefFlow, Volume = double.NaN, Time = double.NaN;
internal DiagnosticLedState7Data Data;
internal bool Keep;
}
private readonly BlockingCollection<Frame> input = new BlockingCollection<Frame>(8192);
private readonly ConcurrentQueue<Frame> output = new ConcurrentQueue<Frame>();
private readonly int batchSize, timeoutMs;
private readonly bool newSeconds;
private readonly Task worker;
private int receiveIndex;
internal AsicOpticalBatchWorker(int batchSize, bool newSeconds, int timeoutSeconds)
{
if (batchSize < 1 || batchSize > 1000) throw new ArgumentOutOfRangeException(nameof(batchSize));
if (timeoutSeconds < 1 || timeoutSeconds > 60) throw new ArgumentOutOfRangeException(nameof(timeoutSeconds));
this.batchSize = batchSize;
this.newSeconds = newSeconds;
timeoutMs = timeoutSeconds * 1000;
worker = Task.Factory.StartNew(Work, System.Threading.CancellationToken.None,
TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
internal void Enqueue(string line, byte[] bytes, DateTime received, double refFlow)
{
if (!input.TryAdd(new Frame { Line = line, Bytes = bytes, PcTimestamp = received,
ReceiveIndex = receiveIndex++, RefFlow = refFlow }))
throw new InvalidOperationException("ASIC experimental input queue overflow; no silent data loss permitted.");
}
internal bool TryRead(out Frame frame)
{
if (worker.IsFaulted) worker.GetAwaiter().GetResult();
return output.TryDequeue(out frame);
}
internal void Complete()
{
input.CompleteAdding();
if (!worker.Wait(5000)) throw new TimeoutException("ASIC experimental selection worker failed to stop within 5 seconds.");
}
private void Work()
{
var parser = new TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.DiagnosticLedParser(
TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.DiagnosticLedState.State7);
var batch = new List<Frame>();
var elapsed = Stopwatch.StartNew();
double volume = double.NaN, time = double.NaN, lastSecond = double.NaN;
while (!input.IsCompleted)
{
Frame frame;
if (input.TryTake(out frame, 50))
{
if (batch.Count == 0) elapsed.Restart();
try
{
frame.Data = parser.ParseLine(frame.Line, false) as DiagnosticLedState7Data;
if (frame.Data != null)
{
// Unwrap EVERY valid telegram before selection, including discarded records.
var raw = new TBF.Rig.TestMethods.iPerlCommunication.common.OptoTelegramRaw();
raw.UpdateFromSmart(frame.Data, frame.ReceiveIndex, (float)frame.RefFlow, ref volume, ref time);
frame.Volume = volume;
frame.Time = time;
double second = Math.Floor(time);
if (!newSeconds || double.IsNaN(lastSecond) || second > lastSecond)
{
frame.Keep = true;
frame.Reason = "RETAINED";
lastSecond = second;
}
else frame.Reason = "DISCARDED_SECOND";
}
else frame.Reason = "INVALID";
}
catch (Exception ex) { frame.Reason = "INVALID:" + ex.GetType().Name; frame.Data = null; }
batch.Add(frame);
}
if (batch.Count >= batchSize || (batch.Count > 0 && elapsed.ElapsedMilliseconds >= timeoutMs))
Publish(batch);
}
Publish(batch); // Last partial batch is never lost on normal stop.
}
private void Publish(List<Frame> batch)
{
var candidates = batch.Where(frame => frame.Keep).ToList();
foreach (var frame in candidates)
{
frame.Keep = ReferenceEquals(frame, candidates[0]) || ReferenceEquals(frame, candidates[candidates.Count - 1]);
frame.Reason = frame.Keep ? "RETAINED_BOUNDARY" : "DISCARDED_BATCH";
}
foreach (var frame in batch)
{
if (output.Count >= 16384) throw new InvalidOperationException("ASIC experimental result queue overflow.");
output.Enqueue(frame);
}
batch.Clear();
}
public void Dispose() { input.Dispose(); }
}
}
@@ -20,6 +20,17 @@ namespace TBF.Rig.RegisterReaders.AsicReader
public string OpticalDiagnosticFormat = "Csv";
public string RawOpticalDataFormat = "Hex";
public bool IncludeParsedValues = true;
public bool EnableOpticalExperiments;
public bool ClearOpticalInputOnOpen = true;
public bool EnableOpticalBatchSelection;
public int OpticalBatchSize = 5;
public int OpticalBatchTimeoutSeconds = 30;
public bool OnlyNewOpticalMeterSecond;
public bool EnableFlyingDrain;
public bool EnableFlyingEarlyCapture;
public int FlyingEarlyCaptureSeconds = 10;
public int FlyingDrainSeconds; // 0 = measurement time; otherwise 10..50 seconds.
public bool FlyingDrainAllowBeyondTestTime;
public override IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities)
{
@@ -10,6 +10,14 @@ namespace TBF.Rig.RegisterReaders.AsicReader
settingsTabPage = new System.Windows.Forms.TabPage();
manualTestTabPage = new System.Windows.Forms.TabPage();
logsTabPage = new System.Windows.Forms.TabPage();
measurementTabPage = new System.Windows.Forms.TabPage();
measurementCfgCtrl = new AsicReaderMeasurementCfgCtrl();
measurementTabPage.Controls.Add(measurementCfgCtrl);
measurementTabPage.Name = "measurementTabPage";
measurementTabPage.Text = "Measurement";
measurementTabPage.Size = new System.Drawing.Size(603,403);
measurementTabPage.UseVisualStyleBackColor = true;
measurementCfgCtrl.Dock = System.Windows.Forms.DockStyle.Fill;
settingsCfgCtrl = new TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfgCtrl();
manualTestCfgCtrl = new TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfgCtrl();
logsCfgCtrl = new AsicReaderLogsCfgCtrl();
@@ -23,6 +31,7 @@ namespace TBF.Rig.RegisterReaders.AsicReader
logsCfgCtrl.Location = new System.Drawing.Point(-4,-4); logsCfgCtrl.Name = "logsCfgCtrl"; logsCfgCtrl.Size = new System.Drawing.Size(611,432);
AutoScaleDimensions = new System.Drawing.SizeF(8F,16F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; Controls.Add(tabControl); Name = "AsicReaderCfgCtrl"; Size = new System.Drawing.Size(617,438);
tabControl.ResumeLayout(false); settingsTabPage.ResumeLayout(false); manualTestTabPage.ResumeLayout(false); logsTabPage.ResumeLayout(false); ResumeLayout(false);
tabControl.Controls.Add(measurementTabPage);
}
private System.Windows.Forms.TabControl tabControl;
private System.Windows.Forms.TabPage settingsTabPage;
@@ -31,5 +40,7 @@ namespace TBF.Rig.RegisterReaders.AsicReader
private TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfgCtrl settingsCfgCtrl;
private TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfgCtrl manualTestCfgCtrl;
private AsicReaderLogsCfgCtrl logsCfgCtrl;
private System.Windows.Forms.TabPage measurementTabPage;
private AsicReaderMeasurementCfgCtrl measurementCfgCtrl;
}
}
@@ -9,7 +9,12 @@ namespace TBF.Rig.RegisterReaders.AsicReader
public partial class AsicReaderCfgCtrl : UserControl, IComponentCfgCtrl
{
private AsicReaderCfg config;
public AsicReaderCfgCtrl() { InitializeComponent(); }
public AsicReaderCfgCtrl()
{
InitializeComponent();
string culture = System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
measurementTabPage.Text = culture == "sk" ? "Meranie" : culture == "cs" ? "Měření" : "Measurement";
}
public bool ShowMore { get { return false; } }
public IComponentCfg Config
{
@@ -22,20 +27,21 @@ namespace TBF.Rig.RegisterReaders.AsicReader
manualTestCfgCtrl.Config = config;
manualTestCfgCtrl.ShowOnlyTab(true);
logsCfgCtrl.Config = config;
measurementCfgCtrl.Config = config;
}
}
public void Unlock() { settingsCfgCtrl.Unlock(); logsCfgCtrl.Unlock(); }
public void Unlock() { settingsCfgCtrl.Unlock(); logsCfgCtrl.Unlock(); measurementCfgCtrl.Unlock(); }
public void Closing() { settingsCfgCtrl.Closing(); manualTestCfgCtrl.Closing(); }
public CfgUpdateFlags VerifyCfg(ref string message)
{
CfgUpdateFlags flags = settingsCfgCtrl.VerifyCfg(ref message);
return flags == CfgUpdateFlags.None ? logsCfgCtrl.VerifyCfg(ref message) : flags;
return flags | logsCfgCtrl.VerifyCfg(ref message) | measurementCfgCtrl.VerifyCfg(ref message);
}
public CfgUpdateFlags UpdateCfg()
{
CfgUpdateFlags flags = settingsCfgCtrl.UpdateCfg();
if ((flags & CfgUpdateFlags.Error) != 0) return flags;
return flags | logsCfgCtrl.UpdateCfg();
return flags | logsCfgCtrl.UpdateCfg() | measurementCfgCtrl.UpdateCfg();
}
}
}
@@ -0,0 +1,101 @@
namespace TBF.Rig.RegisterReaders.AsicReader
{
partial class AsicReaderMeasurementCfgCtrl
{
private System.ComponentModel.IContainer components;
private System.Windows.Forms.Label infoLabel;
private System.Windows.Forms.CheckBox enableEarlyCaptureCheckBox;
private System.Windows.Forms.NumericUpDown earlyCaptureSeconds;
private System.Windows.Forms.Label earlyCaptureLabel;
private System.Windows.Forms.CheckBox experimentsCheckBox;
private System.Windows.Forms.CheckBox clearInputCheckBox;
private System.Windows.Forms.CheckBox batchCheckBox;
private System.Windows.Forms.NumericUpDown batchSize;
private System.Windows.Forms.Label batchSizeLabel;
private System.Windows.Forms.CheckBox newSecondCheckBox;
private System.Windows.Forms.NumericUpDown batchTimeout;
private System.Windows.Forms.Label batchTimeoutLabel;
protected override void Dispose(bool disposing)
{
if (disposing && components != null) components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.infoLabel = new System.Windows.Forms.Label();
this.enableEarlyCaptureCheckBox = new System.Windows.Forms.CheckBox();
this.earlyCaptureSeconds = new System.Windows.Forms.NumericUpDown();
this.earlyCaptureLabel = new System.Windows.Forms.Label();
this.SuspendLayout();
this.experimentsCheckBox = new System.Windows.Forms.CheckBox();
this.clearInputCheckBox = new System.Windows.Forms.CheckBox();
this.batchCheckBox = new System.Windows.Forms.CheckBox();
this.batchSize = new System.Windows.Forms.NumericUpDown();
this.batchSizeLabel = new System.Windows.Forms.Label();
this.newSecondCheckBox = new System.Windows.Forms.CheckBox();
this.batchTimeout = new System.Windows.Forms.NumericUpDown();
this.batchTimeoutLabel = new System.Windows.Forms.Label();
this.experimentsCheckBox.Location = new System.Drawing.Point(20, 135);
this.experimentsCheckBox.Size = new System.Drawing.Size(560, 25);
this.experimentsCheckBox.Name = "experimentsCheckBox";
this.clearInputCheckBox.Location = new System.Drawing.Point(40, 165);
this.clearInputCheckBox.Size = new System.Drawing.Size(540, 25);
this.clearInputCheckBox.Name = "clearInputCheckBox";
this.batchCheckBox.Location = new System.Drawing.Point(40, 195);
this.batchCheckBox.Size = new System.Drawing.Size(540, 25);
this.batchCheckBox.Name = "batchCheckBox";
this.batchSizeLabel.Location = new System.Drawing.Point(60, 228);
this.batchSizeLabel.Size = new System.Drawing.Size(280, 25);
this.batchSize.Location = new System.Drawing.Point(350, 225);
this.batchSize.Name = "batchSize";
this.batchSize.Minimum = 2;
this.batchSize.Maximum = 1000;
this.batchSize.Value = 5;
this.newSecondCheckBox.Location = new System.Drawing.Point(40, 260);
this.newSecondCheckBox.Size = new System.Drawing.Size(540, 25);
this.newSecondCheckBox.Name = "newSecondCheckBox";
this.batchTimeoutLabel.Location = new System.Drawing.Point(60, 296);
this.batchTimeoutLabel.Size = new System.Drawing.Size(280, 25);
this.batchTimeout.Location = new System.Drawing.Point(350, 293);
this.batchTimeout.Name = "batchTimeout";
this.batchTimeout.Minimum = 1;
this.batchTimeout.Maximum = 60;
this.batchTimeout.Value = 30;
this.Controls.Add(this.experimentsCheckBox);
this.Controls.Add(this.clearInputCheckBox);
this.Controls.Add(this.batchCheckBox);
this.Controls.Add(this.batchSizeLabel);
this.Controls.Add(this.batchSize);
this.Controls.Add(this.newSecondCheckBox);
this.Controls.Add(this.batchTimeoutLabel);
this.Controls.Add(this.batchTimeout);
this.infoLabel.Location = new System.Drawing.Point(20, 335);
this.infoLabel.Name = "infoLabel";
this.infoLabel.Size = new System.Drawing.Size(560, 60);
this.infoLabel.Text = "Duration is based on meter telegram timestamps.";
this.Controls.Add(this.infoLabel);
this.enableEarlyCaptureCheckBox.Location = new System.Drawing.Point(20, 25);
this.enableEarlyCaptureCheckBox.Name = "enableEarlyCaptureCheckBox";
this.enableEarlyCaptureCheckBox.Size = new System.Drawing.Size(560, 32);
this.enableEarlyCaptureCheckBox.Text = "Enable forward optical measurement (Flying)";
this.enableEarlyCaptureCheckBox.Enabled = false;
this.earlyCaptureLabel.Location = new System.Drawing.Point(20, 85);
this.earlyCaptureLabel.Size = new System.Drawing.Size(280, 25);
this.earlyCaptureLabel.Text = "Minimum lead time [s]:";
this.earlyCaptureSeconds.Location = new System.Drawing.Point(320, 83);
this.earlyCaptureSeconds.Name = "earlyCaptureSeconds";
this.earlyCaptureSeconds.Minimum = 1;
this.earlyCaptureSeconds.Maximum = 60;
this.earlyCaptureSeconds.Value = 10;
this.earlyCaptureSeconds.Enabled = false;
this.Controls.Add(this.enableEarlyCaptureCheckBox);
this.Controls.Add(this.earlyCaptureLabel);
this.Controls.Add(this.earlyCaptureSeconds);
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Name = "AsicReaderMeasurementCfgCtrl";
this.Size = new System.Drawing.Size(603, 403);
this.ResumeLayout(false);
}
}
}
@@ -0,0 +1,96 @@
using System;
using System.Globalization;
using System.Windows.Forms;
using Common;
namespace TBF.Rig.RegisterReaders.AsicReader
{
public partial class AsicReaderMeasurementCfgCtrl : UserControl
{
private AsicReaderCfg config;
private bool unlocked;
public AsicReaderMeasurementCfgCtrl()
{
InitializeComponent();
string culture = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
bool sk = culture == "sk", cs = culture == "cs";
experimentsCheckBox.Text = sk ? "Experimentálne spracovanie optických dát" : cs ? "Experimentální zpracování optických dat" : "Experimental optical processing";
clearInputCheckBox.Text = sk ? "Vyčistiť vstupný COM buffer pri otvorení" : cs ? "Vyčistit vstupní COM buffer při otevření" : "Clear COM input buffer on open";
batchCheckBox.Text = sk ? "Z dávky ponechať prvý a posledný validný telegram" : cs ? "Z dávky ponechat první a poslední validní telegram" : "Keep first and last valid telegram of each batch";
batchSizeLabel.Text = sk ? "Počet prijatých telegramov:" : cs ? "Počet přijatých telegramů:" : "Received telegrams per batch:";
newSecondCheckBox.Text = sk ? "Iba prvý validný telegram novej sekundy meradla" : cs ? "Jen první validní telegram nové sekundy měřidla" : "Only first valid telegram of a new meter second";
batchTimeoutLabel.Text = sk ? "Limit neúplnej dávky [s]:" : cs ? "Limit neúplné dávky [s]:" : "Partial batch timeout [s]:";
experimentsCheckBox.CheckedChanged += (s, e) => UpdateEnabledState();
batchCheckBox.CheckedChanged += (s, e) => UpdateEnabledState();
enableEarlyCaptureCheckBox.Text = sk ? "Použiť dopredné optické meranie (Flying)" : cs ? "Použít dopředné optické měření (Flying)" : "Enable forward optical measurement (Flying)";
earlyCaptureLabel.Text = sk ? "Minimálny predstih [s]:" : cs ? "Minimální předstih [s]:" : "Minimum lead time [s]:";
enableEarlyCaptureCheckBox.CheckedChanged += (s, e) => UpdateEnabledState();
infoLabel.Text = sk ? "Dopredné meranie pri stabilnom prietoku. Predstih zahŕňa čas AJ objem.\\r\\nReferenčný objem sa koriguje pomerom času meradla a stanice.\\r\\nMeranie končí so skúškou, bez dočítania po skúške.\\r\\nPríprava používa čas PC a reaguje na Stop. Zmena vyžaduje reštart."
: cs ? "Dopředné měření při stabilním průtoku. Předstih zahrnuje čas I objem.\\r\\nReferenční objem se koriguje poměrem času měřidla a stanice.\\r\\nMěření končí se zkouškou, bez dočtení po zkoušce.\\r\\nPříprava používá čas PC a reaguje na Stop. Změna vyžaduje restart."
: "Forward measurement at stable flow. Lead includes BOTH time and volume.\\r\\nReference volume is corrected by the meter/reference time ratio.\\r\\nMeasurement ends with the test; no post-test wait.\\r\\nPreparation uses PC time and responds to Stop. Changes require restart.";
infoLabel.Text = sk ? "Experiment: bez priemerovania END z 3 vzoriek. RAW zachovať v Logs.\r\nVypnuté experimenty zachovajú pôvodné spracovanie. Vyžaduje reštart." : cs ? "Experiment: bez průměrování END ze 3 vzorků. RAW zachovat v Logs.\r\nVypnuté experimenty zachovají původní zpracování. Vyžaduje restart." : "Experiment: no 3-sample END averaging. Preserve RAW in Logs.\r\nExperiments OFF retain legacy processing. Restart required.";
UpdateEnabledState();
}
public AsicReaderCfg Config
{
get { return config; }
set
{
config = value;
experimentsCheckBox.Checked = config != null && config.EnableOpticalExperiments;
clearInputCheckBox.Checked = config == null || config.ClearOpticalInputOnOpen;
batchCheckBox.Checked = config != null && config.EnableOpticalBatchSelection;
batchSize.Value = config == null ? 5 : Math.Max(2, Math.Min(1000, config.OpticalBatchSize));
batchTimeout.Value = config == null ? 30 : Math.Max(1, Math.Min(60, config.OpticalBatchTimeoutSeconds));
newSecondCheckBox.Checked = config != null && config.OnlyNewOpticalMeterSecond;
enableEarlyCaptureCheckBox.Checked = config != null && config.EnableFlyingEarlyCapture;
earlyCaptureSeconds.Value = config == null ? 10 : Math.Max(1, Math.Min(60, config.FlyingEarlyCaptureSeconds));
UpdateEnabledState();
}
}
public void Unlock() { unlocked = true; UpdateEnabledState(); }
private void UpdateEnabledState()
{
enableEarlyCaptureCheckBox.Enabled = unlocked;
earlyCaptureSeconds.Enabled = unlocked && enableEarlyCaptureCheckBox.Checked;
experimentsCheckBox.Enabled = unlocked;
clearInputCheckBox.Enabled = batchCheckBox.Enabled = newSecondCheckBox.Enabled = unlocked && experimentsCheckBox.Checked;
batchSize.Enabled = batchTimeout.Enabled = unlocked && experimentsCheckBox.Checked && batchCheckBox.Checked;
}
public CfgUpdateFlags VerifyCfg(ref string message)
{
if (experimentsCheckBox.Checked && config != null &&
(!config.EnableDetailedLogPerWM || string.Equals(config.RawOpticalDataFormat, "Disabled", StringComparison.OrdinalIgnoreCase)))
{
message += Environment.NewLine + "Optical experiments require per-WM logging and RAW enabled in Logs.";
return CfgUpdateFlags.Error;
}
if (config == null)
{
message += Environment.NewLine + "Missing ASIC measurement configuration.";
return CfgUpdateFlags.Error;
}
return CfgUpdateFlags.None;
}
public CfgUpdateFlags UpdateCfg()
{
if (config == null) return CfgUpdateFlags.Error;
bool earlyChanged = config.EnableFlyingEarlyCapture != enableEarlyCaptureCheckBox.Checked || config.FlyingEarlyCaptureSeconds != (int)earlyCaptureSeconds.Value;
config.EnableFlyingEarlyCapture = enableEarlyCaptureCheckBox.Checked;
config.FlyingEarlyCaptureSeconds = (int)earlyCaptureSeconds.Value;
bool changed = config.EnableFlyingDrain;
changed |= config.EnableOpticalExperiments != experimentsCheckBox.Checked ||
config.ClearOpticalInputOnOpen != clearInputCheckBox.Checked || config.EnableOpticalBatchSelection != batchCheckBox.Checked ||
config.OpticalBatchSize != (int)batchSize.Value || config.OpticalBatchTimeoutSeconds != (int)batchTimeout.Value ||
config.OnlyNewOpticalMeterSecond != newSecondCheckBox.Checked;
config.EnableOpticalExperiments = experimentsCheckBox.Checked;
config.ClearOpticalInputOnOpen = clearInputCheckBox.Checked;
config.EnableOpticalBatchSelection = batchCheckBox.Checked;
config.OpticalBatchSize = (int)batchSize.Value;
config.OpticalBatchTimeoutSeconds = (int)batchTimeout.Value;
config.OnlyNewOpticalMeterSecond = newSecondCheckBox.Checked;
config.EnableFlyingDrain = false; // Retire post-test waiting when saving the Measurement tab.
return changed || earlyChanged ? CfgUpdateFlags.RestartRqrd : CfgUpdateFlags.None;
}
}
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
</root>
@@ -7,6 +7,7 @@ using log4net;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml.Serialization;
@@ -552,6 +553,33 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
Bridge.OnActivity(this, Strings.Measuring_the_weight);
//----------------------------------------------------
// Forward measurement: preserve BOTH the early volume and timestamp endpoints.
var earlyReaders = readDatastreamOps.OfType<TBF.Rig.RegisterReaders.AsicReader.AsicReader>()
.Where(reader => reader.FlyingEarlyCaptureEnabled).ToList();
IList<IOperation> earlyCaptureOps = earlyReaders.Cast<IOperation>().ToList();
if (earlyReaders.Count > 0)
{
foreach (var reader in earlyReaders) reader.PrepareFlyingEarlyCapture();
int leadSeconds = earlyReaders.Max(reader => reader.FlyingEarlyCaptureSeconds);
log.InfoFormat("ASIC_EARLY_CAPTURE_BEGIN: readers={0}; leadSeconds={1}; PC deadline; flow unchanged", earlyReaders.Count, leadSeconds);
Bridge.OnActivity(this, "Preparing optical measurement");
State.Create(string.Format("{0}({1}) : Early optical capture", test.Method, test.Name))
.AddOperation(checkUiOp)
.AddOperations(readTempPressOps)
.AddOperations(earlyCaptureOps)
.EnterState();
var earlyClock = System.Diagnostics.Stopwatch.StartNew();
do
{
e = StateMachine.WaitRunDevsRunOps();
if (e.Contains(Event.Error)) { retVal = Event.Error; goto stopTest; }
if (TestAndLogUiCmdStop(test, e)) { retVal = Event.UiCmdStop; goto stopTest; }
}
while (earlyClock.Elapsed.TotalSeconds < leadSeconds);
log.InfoFormat("ASIC_EARLY_CAPTURE_READY: elapsedPC={0:F3}; no meter-time or volume scaling changed", earlyClock.Elapsed.TotalSeconds);
Bridge.OnActivity(this, Strings.Measuring_the_weight);
}
LogProcessDataTestInfo(processDataLogger, test.Procedure.Name, test.Name);
if (heatMetersPath == null) LogProcessDataHeader(processDataLogger, "Start mass");
@@ -559,6 +587,7 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
State.Create(string.Format("{0}({1}) : Measuring the start mass", test.Method, test.Name))
.AddOperation(checkUiOp)
.AddOperations(earlyCaptureOps)
.AddOperations(readTempPressOps)
.AddOperations(cameraMeasurementOps)
.AddOperation(scale.ReadStableMassOp(ref StartMass, test.TimeFlow2Mass, test.MassMethod, test.MassRepeats, test.MassSpread))
@@ -600,6 +629,7 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
int finalPulsesCount = 0;
/// Measurement loop
foreach (var reader in earlyReaders) reader.BeginFlyingReferenceWindow();
State.Create(string.Format("{0}({1}) : FlyingStartStopTestWithDivOp is running", test.Method, test.Name))
.AddOperation(checkUiOp)
.AddOperations(readTempPressOps)
@@ -654,6 +684,63 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
}
while (!e.Contains(Event.TestCompleted) && !e.Contains(Event.Next)); /// 'Next' button is enabled in Debug version only
// Retain the same operations: the state transition must not Stop/Start (clear) readers.
var drainReaders = readDatastreamOps.OfType<TBF.Rig.RegisterReaders.AsicReader.AsicReader>()
.Where(reader => reader.FlyingDrainEnabled).ToList();
if (drainReaders.Count > 0)
{
try
{
foreach (var reader in readDatastreamOps.OfType<TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlAsicHead>())
reader.HoldFlyingEndpoint();
foreach (var reader in drainReaders) reader.BeginFlyingDrain(cBrd.TestTime);
StopRecordingStatistics();
State.Create("FlyingStartMassCollection : Processing measurement")
.AddOperation(checkUiOp).AddOperations(readDatastreamOps
.Where(operation => operation is TBF.Rig.RegisterReaders.AsicReader.AsicReader).ToList())
.AddOperations(readTempPressOps).EnterState();
var deadline = System.Diagnostics.Stopwatch.StartNew();
double nextDrainProgressLog = 0;
while (drainReaders.Any(reader => !reader.FlyingDrainCompleted))
{
if (deadline.Elapsed.TotalSeconds >= nextDrainProgressLog)
{
log.WarnFormat("ASIC_DRAIN_PROGRESS: elapsedHostS={0:F3};limitHostS=60;pendingReaders={1};referenceTestTime={2};UI polling next",
deadline.Elapsed.TotalSeconds, drainReaders.Count(reader => !reader.FlyingDrainCompleted), cBrd.TestTime);
nextDrainProgressLog = deadline.Elapsed.TotalSeconds + 5;
}
string culture = System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
Bridge.OnActivity(this, culture == "sk" ? "Prebieha spracovanie merania." :
culture == "cs" ? "Probíhá zpracování měření." : "Processing measurement.");
e = StateMachine.WaitRunDevsRunOps();
if (e.Contains(Event.UiCmdStop) || e.Contains(Event.UiCmdShutdown))
log.Warn("ASIC_DRAIN_STOP_EVENT: central UI stop/shutdown received.");
if (TestAndLogUiCmdStop(test, e)) { retVal = Event.UiCmdStop; goto stopTest; }
if (e.Contains(Event.Error) || e.Contains(Event.OpArgumentError)) { retVal = Event.Error; goto stopTest; }
if (deadline.Elapsed.TotalSeconds >= 60 && drainReaders.Any(reader => !reader.FlyingDrainCompleted))
{
log.Error("ASIC_DRAIN_TIMEOUT: meter-time target not reached within 60 seconds.");
retVal = Event.Error;
goto stopTest;
}
}
}
catch (Exception error)
{
log.Error("ASIC_DRAIN_FAILED", error);
retVal = Event.Error;
goto stopTest;
}
finally
{
foreach (var reader in drainReaders)
reader.FinishFlyingDrain(reader.FlyingDrainCompleted ? "completed" : "cancelled-or-timeout");
if (retVal != Event.Done)
foreach (var reader in readDatastreamOps.OfType<TBF.Rig.RegisterReaders.AsicReader.AsicReader>())
reader.StopDataStreamProcessing();
}
}
//------------------------------------------------
@@ -454,6 +454,30 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
int currentTelegramIx;
bool startSampleAcquired;
private bool holdFlyingEndpoint;
internal void HoldFlyingEndpoint() { lock (this) holdFlyingEndpoint = true; }
internal void SelectFlyingEndpoint(int index) { lock (this) TestEndTelegramIx = index; }
internal bool IsValidFlyingSample(int index)
{
lock (this)
return index >= 0 && optoData[BufferIdx(index)].Flags == OptoTelegramFlags.OK;
}
internal bool HasFlyingEndpointWindow(int index)
{
lock (this)
{
if (index - StartEndFilterSamplesCount2 < 0 || index + StartEndFilterSamplesCount2 >= optoDataCount) return false;
for (int i = index - StartEndFilterSamplesCount2; i <= index + StartEndFilterSamplesCount2; i++)
if (!IsValidFlyingSample(i)) return false;
return true;
}
}
internal void ReplayFlyingSamplesAfter(int index, Action<int, double> observe)
{
lock (this)
for (int i = Math.Max(index + 1, optoDataCount - EndOptoDataCount); i < optoDataCount; i++)
if (IsValidFlyingSample(i)) observe(i, optoData[BufferIdx(i)].TimestampExt);
}
///
/// Timestamp from the opto telegram
@@ -468,13 +492,13 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public double TimestampSecStart
{
//4 * pokus (hodnoty su 4 krat mensie)
get { return MultiplyFactor * TimeFromSamples(optoData, optoDataCount, TestStartTelegramIx, StartEndFilterSamplesCount2); }
get { return MultiplyFactor * TimeFromSamples(optoData, optoDataCount, TestStartTelegramIx, EndpointFilterRadius); }
}
/// Test end time for metrology in seconds
public double TimestampSecEnd
{
//4 * pokus (hodnoty su 4 krat mensie)
get { return MultiplyFactor * TimeFromSamples(optoData, optoDataCount, TestEndTelegramIx, StartEndFilterSamplesCount2); }
get { return MultiplyFactor * TimeFromSamples(optoData, optoDataCount, TestEndTelegramIx, EndpointFilterRadius); }
}
///
public bool NoSamples
@@ -499,7 +523,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public double VolumeLtrEnd
{
//4 * pokus (hodnoty su 4 krat mensie)
get { return NoSamples ? 0 : MultiplyFactor * VolumeFromSamples(optoData, optoDataCount, TestEndTelegramIx, ScalingFactor(), StartEndFilterSamplesCount2); }
get { return NoSamples ? 0 : MultiplyFactor * VolumeFromSamples(optoData, optoDataCount, TestEndTelegramIx, ScalingFactor(), EndpointFilterRadius); }
}
@@ -782,6 +806,19 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
}
private bool flyingCaptureOnly;
internal void PrepareFlyingEarlyCapture()
{
lock (this) flyingCaptureOnly = true;
}
internal void BeginFlyingReferenceWindow()
{
lock (this)
{
flyingCaptureOnly = false;
log.InfoFormat("ASIC_EARLY_CAPTURE_REFERENCE_BEGIN: reader={0}; buffered={1}; forward start preserved; startIndex={2}; acquired={3}", Name, optoDataCount, TestStartTelegramIx, startSampleAcquired);
}
}
int timeFromStart; /// [s] Time from test start to determine when the test start sample should be taken
/// <summary>
@@ -791,7 +828,9 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
{
lock (this)
{
FinishExperimentalOpticalInput();
Clear();
holdFlyingEndpoint = false;
ReadPulses();
StartDataStreamProcessing();
NotifyOpticalTestStarted();
@@ -809,10 +848,16 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
timeFromStart += StateMachine.Period;
ReadPulses();
if (!startSampleAcquired && (timeFromStart >= StartSampleDelaySec) && (currentTelegramIx >= 0))
if (ExperimentalSelectionEnabled) return Event.ReadRegisterDone;
if (holdFlyingEndpoint) return Event.ReadRegisterDone;
if (!startSampleAcquired && (flyingCaptureOnly || timeFromStart >= StartSampleDelaySec) &&
(currentTelegramIx >= (flyingCaptureOnly ? 1 : 0)))
{
startSampleAcquired = true;
TestStartTelegramIx = currentTelegramIx;
if (flyingCaptureOnly)
log.InfoFormat("ASIC_FORWARD_START_SELECTED: reader={0}; index={1}; time={2:R}; final paired volume recorded at operation stop",
Name, TestStartTelegramIx, TimestampSecStart);
}
else if (startSampleAcquired)
{
@@ -835,6 +880,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
/// </summary>
public void Stop()
{
lock (this) flyingCaptureOnly = false;
ReleaseCommandPort();
log.DebugFormat("Flow filtering end, feature vector calculation start: {0:HH:mm:ss.fff}", DateTime.Now);
@@ -910,7 +956,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
if (!OpticalTestDiagnosticsEnabled) return;
try
{
int radius = StartEndFilterSamplesCount2;
int radius = EndpointFilterRadius;
string selection = String.Format(System.Globalization.CultureInfo.InvariantCulture,
"indexBase=0;startVolumeIndices={0};endVolumeIndices={1}..{2};startTimeIndex={0};endTimeIndex={3};noSamples={4};startAcquired={5};telegramCount={6};multiplyFactor={7}",
TestStartTelegramIx, TestEndTelegramIx - radius, TestEndTelegramIx + radius,
@@ -1162,6 +1208,32 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
}
protected virtual bool IsOptoPortOpen => optoSerialPort != null && optoSerialPort.IsOpen;
protected int OpticalBytesAvailable => IsOptoPortOpen ? optoSerialPort.BytesToRead : 0;
protected virtual bool ClearOpticalOnOpen => true;
protected virtual bool ExperimentalSelectionEnabled => false;
protected virtual bool ReadExperimentalOpticalInput() { return false; }
protected virtual void FinishExperimentalOpticalInput() { }
private int EndpointFilterRadius => ExperimentalSelectionEnabled ? 0 : StartEndFilterSamplesCount2;
protected void CommitExperimentalOpticalSample(DiagnosticLedState7Data data, double refFlow, double extendedVolume, double extendedTime)
{
// Called only while holding the reader lock. The worker never touches reader state.
int index = optoDataCount;
int bufferIx = BufferIdx(index);
optoData[bufferIx].UpdateFromSmart(data, index, (float)refFlow,
ref volumeRawExtLast, ref timestampExtLast);
optoData[bufferIx].VolumeRawExt = volumeRawExtLast = extendedVolume;
optoData[bufferIx].TimestampExt = timestampExtLast = extendedTime;
flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast);
OptoTelegramReceived(index, true, volumeRawExtLast, timestampExtLast);
optoDataCount++;
if (!startSampleAcquired && (flyingCaptureOnly || timeFromStart >= StartSampleDelaySec))
{
startSampleAcquired = true;
TestStartTelegramIx = index;
}
TestEndTelegramIx = index;
}
protected virtual void DiscardOptoInputBuffer()
{
@@ -1206,7 +1278,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
volumeRawExtLast = double.NaN;
timestampExtLast = double.NaN;
DiscardOptoInputBuffer();
if (ClearOpticalOnOpen) DiscardOptoInputBuffer();
if (flowDirectionDetection != null) flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection
@@ -1281,8 +1353,15 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
/// </summary>
public void StopDataStreamProcessing()
{
dataStreamState = DataStreamState.Flush;
CloseOptoSerialPort();
lock (this)
{
try { FinishExperimentalOpticalInput(); }
finally
{
dataStreamState = DataStreamState.Flush;
CloseOptoSerialPort();
}
}
}
///
@@ -1311,6 +1390,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
lock (this)
{
if (optoState == DataStreamState.ProcessAndSave && ReadExperimentalOpticalInput()) return;
try
{
int nrBytes = optoSerialPort.BytesToRead;
+10
View File
@@ -1715,6 +1715,13 @@
<Compile Include="Rig\RegisterReaders\AsicReader\AsicReaderLogsCfgCtrl.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Rig\RegisterReaders\AsicReader\AsicFlyingDrain.cs" />
<Compile Include="Rig\RegisterReaders\AsicReader\AsicReaderMeasurementCfgCtrl.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Rig\RegisterReaders\AsicReader\AsicReaderMeasurementCfgCtrl.Designer.cs">
<DependentUpon>AsicReaderMeasurementCfgCtrl.cs</DependentUpon>
</Compile>
<Compile Include="Rig\RegisterReaders\AsicReader\AsicReaderLogsCfgCtrl.Designer.cs">
<DependentUpon>AsicReaderLogsCfgCtrl.cs</DependentUpon>
</Compile>
@@ -3992,6 +3999,9 @@
<EmbeddedResource Include="Rig\RegisterReaders\AsicReader\AsicReaderLogsCfgCtrl.resx">
<DependentUpon>AsicReaderLogsCfgCtrl.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Rig\RegisterReaders\AsicReader\AsicReaderMeasurementCfgCtrl.resx">
<DependentUpon>AsicReaderMeasurementCfgCtrl.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Rig\RegisterReaders\iPerlReaderUNI\IPerlUniCfgCtrl.resx">
<DependentUpon>IPerlUniCfgCtrl.cs</DependentUpon>
</EmbeddedResource>
@@ -0,0 +1,368 @@
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.AsicReader;
namespace TBFTests.Rig.RegisterReaders.AsicReader
{
[TestClass]
[DoNotParallelize]
public class AsicFlyingDrainTests
{
private static string ExperimentalLine(uint ticks, uint volume)
{
string body = "FFCC09\t2020\tFFFA\t" + volume.ToString("X6") +
"\t0054\t0B01\t" + ticks.ToString("X8") + "\tA7\t00001234\t00F0\t00F1\t0100\t0200\t03\tFFEC\t0010\t" +
"02\t02\t00000099\t01\tFFAA10\t000123\tFFEE\t0011\t03\t";
byte checksum = 0;
foreach (char c in body) checksum = unchecked((byte)(checksum + (byte)c));
return body + checksum.ToString("X2");
}
[TestMethod]
public void ExperimentalPartialBatchDeadlineUsesHostClockWithoutFurtherInput()
{
using (var worker = new AsicOpticalBatchWorker(20, false, 1))
{
worker.Enqueue(ExperimentalLine(819200, 1000), new byte[0], DateTime.Now, 1);
AsicOpticalBatchWorker.Frame result = null;
Assert.IsTrue(SpinWait.SpinUntil(() => worker.TryRead(out result), 3000), "No incoming data must not prevent the PC deadline from flushing the batch.");
Assert.IsTrue(result.Keep);
worker.Complete();
}
}
[TestMethod]
public void ExperimentalSelectedEndpointUsesItsOwnVolumeNotSparseNeighbourAverage()
{
var reader = SeedReader(false, false);
var config = (AsicReaderCfg)reader.GetType().GetField("asicCfg", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(reader);
config.EnableOpticalExperiments = config.EnableOpticalBatchSelection = true;
var samples = (TBF.Rig.TestMethods.iPerlCommunication.common.OptoTelegramRaw[])
typeof(TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlAsicHead).GetField("optoData", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(reader);
Assert.AreEqual(samples[reader.TestEndTelegramIx].VolumeRawExt, reader.VolumeLtrEnd, 1e-10);
Assert.AreEqual(samples[reader.TestEndTelegramIx].TimestampExt, reader.TimestampSecEnd, 1e-10);
}
[DataTestMethod]
[DataRow(5)] [DataRow(20)]
public void ExperimentalBatchRetainsExactlyFirstAndLastAndLogsEveryInput(int count)
{
using (var worker = new AsicOpticalBatchWorker(count, false, 30))
{
DateTime pc = new DateTime(2026, 10, 6, 12, 0, 0);
for (int i = 0; i < count; i++) worker.Enqueue(ExperimentalLine((uint)(8192 * (100 + i)), (uint)(1000 + i)), new byte[] { 1 }, pc.AddSeconds(i), 1);
worker.Complete();
var result = new System.Collections.Generic.List<AsicOpticalBatchWorker.Frame>();
AsicOpticalBatchWorker.Frame frame;
while (worker.TryRead(out frame)) result.Add(frame);
Assert.AreEqual(count, result.Count);
Assert.IsTrue(result[0].Keep && result[count - 1].Keep);
Assert.AreEqual(2, System.Linq.Enumerable.Count(result, item => item.Keep));
Assert.AreEqual(pc, result[0].PcTimestamp);
Assert.AreEqual(count - 1, result[count - 1].ReceiveIndex);
}
}
[TestMethod]
public void ExperimentalNewSecondDropsDuplicatesAndInvalidLinesWithoutLosingTail()
{
using (var worker = new AsicOpticalBatchWorker(20, true, 30))
{
worker.Enqueue("invalid", new byte[0], DateTime.Now, 1);
worker.Enqueue(ExperimentalLine(819200, 1000), new byte[0], DateTime.Now, 1);
worker.Enqueue(ExperimentalLine(819201, 1001), new byte[0], DateTime.Now, 1);
worker.Enqueue(ExperimentalLine(827392, 1002), new byte[0], DateTime.Now, 1);
worker.Complete(); // Partial batch smaller than configured 20 is emitted on Stop.
var result = new System.Collections.Generic.List<AsicOpticalBatchWorker.Frame>();
AsicOpticalBatchWorker.Frame frame;
while (worker.TryRead(out frame)) result.Add(frame);
Assert.AreEqual(4, result.Count);
Assert.IsFalse(result[0].Keep);
Assert.IsTrue(result[1].Keep && result[3].Keep);
Assert.AreEqual("DISCARDED_SECOND", result[2].Reason);
}
}
[TestMethod]
public void ExperimentalDefaultsOffAndRoundTripPreservesOptions()
{
var cfg = new AsicReaderCfg();
Assert.IsFalse(cfg.EnableOpticalExperiments);
Assert.IsTrue(cfg.ClearOpticalInputOnOpen);
Assert.IsFalse(cfg.EnableOpticalBatchSelection || cfg.OnlyNewOpticalMeterSecond);
cfg.EnableOpticalExperiments = cfg.EnableOpticalBatchSelection = cfg.OnlyNewOpticalMeterSecond = true;
cfg.OpticalBatchSize = 20;
var output = new StringWriter();
AsicReaderCfg.Serializer.Serialize(output, cfg);
var restored = (AsicReaderCfg)AsicReaderCfg.Serializer.Deserialize(new StringReader(output.ToString()));
Assert.IsTrue(restored.EnableOpticalExperiments && restored.EnableOpticalBatchSelection && restored.OnlyNewOpticalMeterSecond);
Assert.AreEqual(20, restored.OpticalBatchSize);
}
[TestMethod]
public void EarlyCaptureDefaultsOffAndRoundTripsPerReader()
{
var cfg = new AsicReaderCfg();
Assert.IsFalse(cfg.EnableFlyingEarlyCapture);
Assert.AreEqual(10, cfg.FlyingEarlyCaptureSeconds);
cfg.EnableFlyingEarlyCapture = true;
cfg.FlyingEarlyCaptureSeconds = 20;
cfg.EnableFlyingDrain = true;
var output = new StringWriter();
AsicReaderCfg.Serializer.Serialize(output, cfg);
var restored = (AsicReaderCfg)AsicReaderCfg.Serializer.Deserialize(new StringReader(output.ToString()));
var reader = new TBF.Rig.RegisterReaders.AsicReader.AsicReader(restored);
Assert.IsTrue(reader.FlyingEarlyCaptureEnabled);
Assert.AreEqual(20, reader.FlyingEarlyCaptureSeconds);
Assert.IsFalse(reader.FlyingDrainEnabled, "Forward measurement must finish with the reference test, not enter post-test drain.");
Assert.IsFalse(new AsicReaderCfg().EnableFlyingEarlyCapture);
}
[TestMethod]
public void ReferenceBoundaryPreservesForwardStartAndTimer()
{
var reader = new TBF.Rig.RegisterReaders.AsicReader.AsicReader(new AsicReaderCfg());
var type = typeof(TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlAsicHead);
var acquired = type.GetField("startSampleAcquired", BindingFlags.Instance | BindingFlags.NonPublic);
var timer = type.GetField("timeFromStart", BindingFlags.Instance | BindingFlags.NonPublic);
reader.PrepareFlyingEarlyCapture();
acquired.SetValue(reader, true);
timer.SetValue(reader, 12);
reader.BeginFlyingReferenceWindow();
Assert.IsTrue((bool)acquired.GetValue(reader));
Assert.AreEqual(12, timer.GetValue(reader));
}
[TestMethod]
public void LegacyXmlAndDefaultConfigLeaveFeatureDisabled()
{
var defaults = new AsicReaderCfg();
Assert.IsFalse(defaults.EnableFlyingDrain);
using (var input = new StringReader("<AsicReaderCfg><Name>ASIC</Name></AsicReaderCfg>"))
{
var cfg = (AsicReaderCfg)AsicReaderCfg.Serializer.Deserialize(input);
Assert.IsFalse(cfg.EnableFlyingDrain);
Assert.AreEqual(0, cfg.FlyingDrainSeconds);
Assert.IsFalse(cfg.FlyingDrainAllowBeyondTestTime);
Assert.IsFalse(new TBF.Rig.RegisterReaders.AsicReader.AsicReader(cfg).FlyingDrainEnabled);
var reader = new TBF.Rig.RegisterReaders.AsicReader.AsicReader(cfg);
reader.BeginFlyingDrain(0); // Disabled path must not inspect samples or require valid times.
Assert.IsTrue(reader.FlyingDrainCompleted);
}
}
[TestMethod]
public void XmlRoundTripPreservesReaderSpecificSettings()
{
var cfg = new AsicReaderCfg { EnableFlyingDrain = true, FlyingDrainSeconds = 50, FlyingDrainAllowBeyondTestTime = true };
var writer = new StringWriter();
AsicReaderCfg.Serializer.Serialize(writer, cfg);
var restored = (AsicReaderCfg)AsicReaderCfg.Serializer.Deserialize(new StringReader(writer.ToString()));
Assert.IsTrue(restored.EnableFlyingDrain);
Assert.AreEqual(50, restored.FlyingDrainSeconds);
Assert.IsTrue(restored.FlyingDrainAllowBeyondTestTime);
Assert.IsFalse(new AsicReaderCfg().EnableFlyingDrain, "Settings must not leak to another reader.");
}
[TestMethod]
public void MeasurementTimeSelectsLastSampleWithinReferenceWindow()
{
var policy = new AsicFlyingDrain(100, 78, 137, 4, 0, false);
policy.Observe(5, 175);
policy.Observe(6, 177);
policy.Observe(7, 179);
Assert.IsTrue(policy.Complete);
Assert.AreEqual(6, policy.SelectedIndex);
Assert.AreEqual(178.0, policy.Target);
}
[TestMethod]
public void ExactTargetWaitsForRightNeighbourRequiredByLegacyVolumeFilter()
{
var policy = new AsicFlyingDrain(100, 78, 137, 4, 0, false);
policy.Observe(5, 178);
Assert.IsFalse(policy.Complete);
policy.Observe(6, 179);
Assert.IsTrue(policy.Complete);
Assert.AreEqual(5, policy.SelectedIndex);
}
[DataTestMethod]
[DataRow(10)] [DataRow(20)] [DataRow(30)] [DataRow(40)] [DataRow(50)]
public void FixedDurationStartsAtSelectedDivEndpointAndCanBeCapped(int seconds)
{
var unlimited = new AsicFlyingDrain(100, 78, 175, 4, seconds, true);
Assert.AreEqual(175.0 + seconds, unlimited.Target);
unlimited.Observe(5, unlimited.Target + 0.5);
Assert.IsFalse(unlimited.Complete);
unlimited.Observe(6, unlimited.Target + 1.5);
Assert.IsTrue(unlimited.Complete);
Assert.AreEqual(5, unlimited.SelectedIndex);
var capped = new AsicFlyingDrain(100, 78, 175, 4, seconds, false);
Assert.AreEqual(178.0, capped.Target);
capped.Observe(5, 177);
capped.Observe(6, 179);
Assert.IsTrue(capped.Complete);
Assert.AreEqual(5, capped.SelectedIndex);
}
[TestMethod]
public void MeterTimeModeNeverExtrapolatesEvenWhenAllowBeyondChecked()
{
var policy = new AsicFlyingDrain(100, 78, 137, 4, 0, true);
policy.Observe(5, 177);
policy.Observe(6, 179);
Assert.AreEqual(5, policy.SelectedIndex);
Assert.IsTrue(policy.Complete);
}
[TestMethod]
public void InvalidOrBackwardsTimesDoNotCompleteAndRolloverExtendedTimeIsAccepted()
{
var policy = new AsicFlyingDrain(65530, 20, 65535, 4, 0, false);
policy.Observe(5, double.NaN);
policy.Observe(6, double.PositiveInfinity);
policy.Observe(7, 1);
Assert.IsFalse(policy.Complete);
Assert.AreEqual(4, policy.SelectedIndex);
policy.Observe(8, 65549);
policy.Observe(9, 65551);
Assert.IsTrue(policy.Complete);
Assert.AreEqual(8, policy.SelectedIndex);
}
[TestMethod]
public void PolicyRejectsInvalidInitialSamplesAndInvalidConfig()
{
Assert.ThrowsException<InvalidOperationException>(() => new AsicFlyingDrain(0, 78, 37, 4, 0, false));
Assert.ThrowsException<InvalidOperationException>(() => new AsicFlyingDrain(100, 0, 137, 4, 0, false));
Assert.ThrowsException<InvalidOperationException>(() => new AsicFlyingDrain(100, 78, 137, 4, 9, false));
}
[TestMethod]
public void ReaderReplaysBufferedTailAndPublishesSelectedFilteredVolume()
{
var reader = SeedReader(true, false);
reader.BeginFlyingDrain(78);
Assert.IsTrue(reader.FlyingDrainCompleted);
reader.FinishFlyingDrain("completed");
Assert.AreEqual(5, reader.TestEndTelegramIx);
Assert.AreEqual(100.0, reader.TimestampSecStart);
Assert.AreEqual(178.0, reader.TimestampSecEnd);
Assert.AreEqual(15.0, reader.VolumeLtrEnd, 1e-9, "Legacy end filter must average indices 4, 5, 6.");
}
[TestMethod]
public void ReaderDisabledRetainsExistingEndpointAndInvalidFilterWindowCannotComplete()
{
var disabled = SeedReader(false, false);
disabled.BeginFlyingDrain(78);
Assert.AreEqual(3, disabled.TestEndTelegramIx);
Assert.AreEqual(137.0, disabled.TimestampSecEnd);
var invalid = SeedReader(true, true);
invalid.BeginFlyingDrain(78);
Assert.IsFalse(invalid.FlyingDrainCompleted, "Invalid neighbour must not produce a zero-volume successful result.");
invalid.FinishFlyingDrain("timeout");
}
private static TBF.Rig.RegisterReaders.AsicReader.AsicReader SeedReader(bool enabled, bool invalidNeighbour)
{
var reader = new TBF.Rig.RegisterReaders.AsicReader.AsicReader(new AsicReaderCfg
{ Name = "ASIC_TEST", EnableFlyingDrain = enabled });
var samples = new TBF.Rig.TestMethods.iPerlCommunication.common.OptoTelegramRaw[8];
double[] times = { 99, 100, 101, 137, 177, 178, 179, 180 };
for (int i = 0; i < samples.Length; i++)
samples[i] = new TBF.Rig.TestMethods.iPerlCommunication.common.OptoTelegramRaw
{
Counter = i, TimestampExt = times[i], VolumeRawExt = 10 + i,
Flags = invalidNeighbour && i == 6 ? TBF.Rig.TestMethods.iPerlCommunication.common.OptoTelegramFlags.InvalidTelegram
: TBF.Rig.TestMethods.iPerlCommunication.common.OptoTelegramFlags.OK
};
var type = typeof(TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlAsicHead);
const BindingFlags fields = BindingFlags.Instance | BindingFlags.NonPublic;
type.GetField("optoData", fields).SetValue(reader, samples);
type.GetField("optoDataCount", fields).SetValue(reader, samples.Length);
reader.TestStartTelegramIx = 1;
reader.TestEndTelegramIx = 3;
return reader;
}
[TestMethod]
public void DrainStateRetainsReaderOperationWithoutRestartingOrClearingIt()
{
var current = typeof(TBF.Rig.State).GetField("currentState", BindingFlags.Static | BindingFlags.NonPublic);
object previous = current.GetValue(null);
int starts = 0, stops = 0;
var reader = new Moq.Mock<TBF.Rig.IOperation>();
reader.Setup(op => op.Start()).Callback(() => starts++);
reader.Setup(op => op.Stop()).Callback(() => stops++);
try
{
current.SetValue(null, TBF.Rig.State.Create("idle"));
TBF.Rig.State.Create("measurement").AddOperation(reader.Object).EnterState();
TBF.Rig.State.Create("drain").AddOperation(reader.Object).EnterState();
Assert.AreEqual(1, starts);
Assert.AreEqual(0, stops);
TBF.Rig.State.Create("calculation").EnterState();
Assert.AreEqual(1, stops);
}
finally { current.SetValue(null, previous); }
}
[TestMethod]
public void GuiUnlockAndCheckboxGateOptionsAndRequestRestartOnChange()
{
Exception failure = null;
var thread = new Thread(() =>
{
try
{
using (var ui = new AsicReaderMeasurementCfgCtrl())
{
var cfg = new AsicReaderCfg();
ui.Config = cfg;
const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
var enable = (CheckBox)ui.GetType().GetField("enableEarlyCaptureCheckBox", flags).GetValue(ui);
var mode = (NumericUpDown)ui.GetType().GetField("earlyCaptureSeconds", flags).GetValue(ui);
Assert.IsFalse(enable.Enabled || mode.Enabled);
ui.Unlock();
Assert.IsTrue(enable.Enabled);
Assert.IsFalse(mode.Enabled);
enable.Checked = true;
Assert.IsTrue(mode.Enabled);
mode.Value = 30;
cfg.EnableFlyingDrain = true;
string message = "";
Assert.AreEqual(Common.CfgUpdateFlags.None, ui.VerifyCfg(ref message));
Assert.AreEqual(Common.CfgUpdateFlags.RestartRqrd, ui.UpdateCfg());
Assert.AreEqual(30, cfg.FlyingEarlyCaptureSeconds);
Assert.IsTrue(cfg.EnableFlyingEarlyCapture);
Assert.IsFalse(cfg.EnableFlyingDrain);
Assert.AreEqual(Common.CfgUpdateFlags.None, ui.UpdateCfg());
var experiments = (CheckBox)ui.GetType().GetField("experimentsCheckBox", flags).GetValue(ui);
var batching = (CheckBox)ui.GetType().GetField("batchCheckBox", flags).GetValue(ui);
var size = (NumericUpDown)ui.GetType().GetField("batchSize", flags).GetValue(ui);
Assert.IsTrue(experiments.Enabled);
Assert.IsFalse(batching.Enabled || size.Enabled);
experiments.Checked = true;
Assert.IsTrue(batching.Enabled);
Assert.IsFalse(size.Enabled);
batching.Checked = true;
Assert.IsTrue(size.Enabled);
size.Value = 20;
Assert.AreEqual(Common.CfgUpdateFlags.Error, ui.VerifyCfg(ref message));
cfg.EnableDetailedLogPerWM = true;
Assert.AreEqual(Common.CfgUpdateFlags.None, ui.VerifyCfg(ref message));
Assert.AreEqual(Common.CfgUpdateFlags.RestartRqrd, ui.UpdateCfg());
Assert.IsTrue(cfg.EnableOpticalExperiments && cfg.EnableOpticalBatchSelection);
Assert.AreEqual(20, cfg.OpticalBatchSize);
}
}
catch (Exception error) { failure = error; }
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Assert.IsTrue(thread.Join(10000));
if (failure != null) throw failure;
}
}
}
@@ -380,6 +380,58 @@ namespace TBFTests.Rig.RegisterReaders.AsicReader
Assert.IsFalse(reader.Open);
}
[TestMethod]
public void FlyingLegacy_EndSelectionKeepsExistingRunDelay_NotNewestTelegram()
{
var reader = Create(1, 1, 1);
reader.ReadDatastreamOp().Start();
var type = typeof(IperlAsicHead);
const BindingFlags fields = BindingFlags.Instance | BindingFlags.NonPublic;
type.GetField("startSampleAcquired", fields).SetValue(reader, true);
var history = (int[])type.GetField("endTelegramIdx", fields).GetValue(reader);
for (int i = 0; i < history.Length; i++) history[i] = 100 + i;
type.GetField("currentTelegramIx", fields).SetValue(reader, 999);
int expectedEnd = history[history.Length - 1];
Assert.AreEqual(Event.ReadRegisterDone, reader.ReadDatastreamOp().Run());
Assert.AreEqual(expectedEnd, reader.TestEndTelegramIx,
"Disabled drain must preserve the delayed endpoint, not replace it with the newest telegram.");
Assert.AreEqual(999, history[0]);
Assert.IsTrue(reader.Open);
}
[TestMethod]
public void FlyingLegacy_NoSamplesRetainsZeroVolumeAndMissingSampleFlag()
{
var reader = Create(1, 1, 1);
reader.ReadDatastreamOp().Start();
Assert.IsTrue(reader.NoSamples);
Assert.AreEqual(0.0, reader.VolumeLtrStart);
Assert.AreEqual(0.0, reader.VolumeLtrEnd);
Assert.AreEqual(0.0, reader.TimestampSecStart);
Assert.AreEqual(0.0, reader.TimestampSecEnd);
}
[DataTestMethod]
[DataRow(1, 1)]
[DataRow(1, 2)]
[DataRow(2, 1)]
[DataRow(2, 2)]
public void FlyingLegacy_StopClosesPortWithoutReadingAdditionalTelegrams(int group, int mux)
{
var reader = Create(1, group, mux);
var operation = reader.ReadDatastreamOp();
operation.Start();
int reads = reader.TotalReads;
operation.Stop();
Assert.IsFalse(reader.Open);
Assert.AreEqual(1, reader.Closes);
Assert.AreEqual(reads, reader.TotalReads,
"Legacy stop must not consume an additional optical telegram.");
Assert.AreEqual(0, reader.ActiveReads);
reader.EndSession();
Assert.AreEqual(1, reader.Closes, "Session cleanup must remain idempotent.");
}
private SimulatedAsic Create(int position, int group, int mux)
{
var cfg = (AsicReaderCfg)new Factory().DefaultConfig();
@@ -433,7 +485,7 @@ namespace TBFTests.Rig.RegisterReaders.AsicReader
{
private readonly ConcurrentDictionary<int, SimulatedAsic> ports;
private volatile bool open;
public int Opens, Closes, ActiveReads, ClosedDuringRead, CommandDisposals;
public int Opens, Closes, ActiveReads, ClosedDuringRead, CommandDisposals, TotalReads;
public uint RawVolume = 40000;
public bool NoData;
public readonly ManualResetEventSlim ReadEntered = new ManualResetEventSlim();
@@ -471,6 +523,7 @@ namespace TBFTests.Rig.RegisterReaders.AsicReader
protected override string ReadOptoLine(out byte[] bytes)
{
Interlocked.Increment(ref TotalReads);
Interlocked.Increment(ref ActiveReads);
try
{
+1
View File
@@ -166,6 +166,7 @@
<Compile Include="Rig\RegisterReaders\AsicReader\AsicCorrectionsTest.cs" />
<Compile Include="Rig\RegisterReaders\AsicReader\ManualReaderPortReleaseTests.cs" />
<Compile Include="Rig\RegisterReaders\AsicReader\AsicGroupPortLifecycleTests.cs" />
<Compile Include="Rig\RegisterReaders\AsicReader\AsicFlyingDrainTests.cs" />
<None Include="Rig\RegisterReaders\AsicReader\AsicGroupPortLifecycleTests.txt" />
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalSampleTest.cs" />
<Compile Include="Rig\RegisterReaders\AllyReader\Optical\AllyOpticalProtocolTests.cs" />