diff --git a/Results/DB.cs b/Results/DB.cs
index 1bc7a5d0f..23f06a0a2 100644
--- a/Results/DB.cs
+++ b/Results/DB.cs
@@ -11,6 +11,7 @@ using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using Common;
using Results.Entities;
+using Results.Entities.helpers;
namespace Results
{
@@ -107,7 +108,11 @@ namespace Results
throw new Exception("Connection string was not specified");
}
- if (SessionFactory == null) SessionFactory = CreateSessionFactory();
+ if (SessionFactory == null)
+ {
+ DatabaseMigrationHelper.EnsureSchema(dbType, connectionString);
+ SessionFactory = CreateSessionFactory();
+ }
return SessionFactory.OpenSession();
}
diff --git a/Results/Results.csproj b/Results/Results.csproj
index 06cf05a71..484ea4d1f 100644
--- a/Results/Results.csproj
+++ b/Results/Results.csproj
@@ -53,6 +53,9 @@
..\packages\NHibernate.4.0.4.4000\lib\net40\NHibernate.dll
+
+ ..\packages\System.Data.SQLite.2.0.3\lib\net471\System.Data.SQLite.dll
+
@@ -69,6 +72,7 @@
+
@@ -265,4 +269,4 @@
-->
-
\ No newline at end of file
+
diff --git a/TBF/Properties/AssemblyInfo.cs b/TBF/Properties/AssemblyInfo.cs
index be61ee32a..4405d1a97 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.2204.1")]
-[assembly: AssemblyFileVersion("3.9.2204.1")]
+[assembly: AssemblyVersion("3.9.2205.1")]
+[assembly: AssemblyFileVersion("3.9.2205.1")]
diff --git a/TBF/ReadMe_BugFix.md b/TBF/ReadMe_BugFix.md
index 7a8010bc9..40648787a 100644
--- a/TBF/ReadMe_BugFix.md
+++ b/TBF/ReadMe_BugFix.md
@@ -3,3 +3,12 @@
| Version | Target Environment | Title | Description |
|------------|--------------------------------------------------------------|-----------------------------------------------------|------------------------------------|
| 3.9.2149.0 | HeatMeters, Heat meter sensors, Procedure Dilog, Tab Process | Excanged columns value 'Sensor' and 'Heat meter sensor' | Fix in code ProcedureDlg, row 1851 |
+| 3.9.2149.1 | General release | Version iteration | Assembly and file version increment. |
+| 3.9.2149.2 | Poseidon register reader | Poseidon pulse and timing handling | Added reference-pulse reading in `Run()` and improved Poseidon timing/task tracking. |
+| 3.9.2149.4 | Mass collection / Poseidon start-stop | Mass collection update | Refactored standing-start mass collection, extended Poseidon start/end data-entry configuration and improved dialog/task handling. |
+| 3.9.2200.1 | Test infrastructure | TBF test assembly access | Added `InternalsVisibleTo` support for `TBFTests`. |
+| 3.9.2201.1 | Poseidon CLI | Poseidon CLI configuration | Added macro descriptions, refined serial-port/CLI argument configuration and extended CLI test coverage. |
+| 3.9.2202.1 | Poseidon CLI | CLI release iteration | Assembly and file version increment for the Poseidon CLI workstream. |
+| 3.9.2203.1 | Poseidon CLI / smart-meter sequence | CLI test and configuration update | Updated CLI executable test setup, serial-port default responses and smart-meter component-name handling. |
+| 3.9.2204.1 | Morrisville / Poseidon CLI | Morrisville CLI diagnostics | Restored lost 2204 versioning, added detailed CLI command/response logging and used a fixed CLI directory for deployment. |
+| 3.9.2205.1 | Morrisville / Poseidon CLI / Results DB | Poseidon read diagnostics and results DB migration | Improved Poseidon CLI execution and diagnostics (fixed CLI working directory, exit code/stdout/stderr capture, JSON/NFC/reading validation and culture-independent decimal parsing). Added reader-cycle and fake-CLI test coverage. Prevented a device/CLI error while changing the legacy optical-head mode from crashing the UI. Results DB now creates missing `MeterTestRslt` compatibility columns (`FlipMode`, `ExtraDataPath`, `X1`-`X9`) automatically for MySQL and SQLite before results are saved. |
diff --git a/TBF/Rig/DataEntry/PoseidonCmd/EntryForm.cs b/TBF/Rig/DataEntry/PoseidonCmd/EntryForm.cs
index 95901aeb2..9dc95d57d 100644
--- a/TBF/Rig/DataEntry/PoseidonCmd/EntryForm.cs
+++ b/TBF/Rig/DataEntry/PoseidonCmd/EntryForm.cs
@@ -433,42 +433,42 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|| (currentOp == CurrentOp.ReadDatastream_EndStates))
{
bool finishedReading = regReaders == null; // we can work only with register readers
+ List poseidonReaders = new List();
+ if (regReaders != null)
+ {
+ foreach (var iRegReader in regReaders)
+ {
+ PoseidonReader poseidonReader = iRegReader as PoseidonReader;
+ if (poseidonReader != null)
+ {
+ poseidonReader.SetCliLogging(CliLogging);
+ poseidonReaders.Add(new PoseidonReaderOperation(poseidonReader));
+ }
+ }
+ }
+
while (!finishedReading) //TODO BUMI lock - fuck ?
{
- bool bAllReadersFinished = true;
- foreach (var iRegReader in regReaders )
+ Dictionary operationsBefore =
+ new Dictionary();
+ foreach (IPoseidonReadOperation poseidonReader in poseidonReaders)
{
- if (iRegReader is PoseidonReader)
+ operationsBefore.Add(poseidonReader, poseidonReader.IsFinished);
+ if (poseidonReader.IsNotStarted)
+ log.DebugFormat("Poseidon read: starting {0} for {1}", currentOp, poseidonReader.Name);
+ }
+
+ bool bAllReadersFinished = PoseidonReadCycle.RunIteration(
+ poseidonReaders,
+ currentOp == CurrentOp.ReadDatastream_StartStates);
+ foreach (IPoseidonReadOperation poseidonReader in poseidonReaders)
+ {
+ if (poseidonReader.IsFinished && !operationsBefore[poseidonReader])
{
- PoseidonReader poseidonReader = (iRegReader as PoseidonReader);
- if(poseidonReader == null)
- continue;
- poseidonReader.SetCliLogging(CliLogging);
- // Start a read only once. The previous condition restarted a
- // reader after it reached Done/Error, which kept the dialog in
- // Loading... and repeatedly launched the CLI for every meter.
- if (poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.None)
- {
- log.DebugFormat("Poseidon read: starting {0} for {1}", currentOp, poseidonReader.Name);
- poseidonReader.SetCurrentOp((currentOp == CurrentOp.ReadDatastream_StartStates)?
- PoseidonReader.CurrentPoseidonOp.ReadDataStream_Start :
- PoseidonReader.CurrentPoseidonOp.ReadDataStream_End);
- }
- /// Send start data stream
- poseidonReader.Run();
- if (poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error)
- {
+ if (poseidonReader.HasError)
log.ErrorFormat("Poseidon read: {0} completed with Error during {1}", poseidonReader.Name, currentOp);
- }
- else if (poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Done)
- {
+ else
log.DebugFormat("Poseidon read: {0} completed during {1}", poseidonReader.Name, currentOp);
- }
- if (!(poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Done
- || poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error))
- {
- bAllReadersFinished = false;
- }
}
}
if (bAllReadersFinished)
diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs
index 31473d2ba..6511300e9 100644
--- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs
+++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs
@@ -259,9 +259,18 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
- string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
+ info.ExitCode = process.ExitCode;
+ info.StandardOutput = stdOutTask.Result ?? "";
+ info.StandardError = stdErrTask.Result ?? "";
+ string allOutput = info.StandardOutput + info.StandardError;
log?.Debug(allOutput);
+ if (info.ExitCode != 0)
+ {
+ info.FailureReason = $"CLI exited with code {info.ExitCode}.";
+ log?.Error($"{info.Name}: {info.FailureReason} stderr='{info.StandardError}'");
+ }
+
info.State = CliTaskState.Completed;
return allOutput;
}
@@ -358,9 +367,20 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
- string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
+ info.ExitCode = process.ExitCode;
+ info.StandardOutput = stdoutTask.Result ?? "";
+ info.StandardError = stderrTask.Result ?? "";
+ string allOutput = info.StandardOutput + info.StandardError;
log?.Debug(allOutput);
+ if (info.ExitCode != 0)
+ {
+ info.FailureReason = $"CLI exited with code {info.ExitCode}.";
+ log?.Error($"{info.Name}: {info.FailureReason} stderr='{info.StandardError}'");
+ info.State = CliTaskState.Completed;
+ return default(T);
+ }
+
string json = ExtractJson(allOutput);
T result;
if (TryJsonStringDeserialize(json, out result))
@@ -369,6 +389,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
return result;
}
+ info.FailureReason = "CLI completed without a valid JSON response.";
+ log?.Error($"{info.Name}: {info.FailureReason} stdout='{info.StandardOutput}' stderr='{info.StandardError}'");
info.State = CliTaskState.Completed;
return default(T);
}
@@ -391,18 +413,17 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
public bool TryJsonStringDeserialize(string json, out T value) where T : new()
{
- if (json != null)
+ if (!string.IsNullOrWhiteSpace(json))
{
try
{
value = JsonConvert.DeserializeObject(json);
- return true;
+ return value != null;
}
catch (Exception ex)
{
log?.Debug(ex.Message);
- value = TryConvert(json);
- return true;
+ return TryConvert(json, out value);
}
}
@@ -410,7 +431,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
return false;
}
- private static T TryConvert(string json) where T : new()
+ private static bool TryConvert(string json, out T value) where T : new()
{
T obj = new T();
@@ -428,21 +449,24 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
{
try
{
- object value = token.ToObject(prop.PropertyType);
- prop.SetValue(obj, value);
+ object propertyValue = token.ToObject(prop.PropertyType);
+ prop.SetValue(obj, propertyValue);
}
catch
{
}
}
}
+
+ value = obj;
+ return true;
}
catch (Exception ex)
{
Console.WriteLine($"TryConvert failed: {ex.Message}");
+ value = default(T);
+ return false;
}
-
- return obj;
}
public string ExtractJson(string text)
diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs
index aabb6ec79..aeac9961e 100644
--- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs
+++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliTaskInfo.cs
@@ -11,6 +11,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
public Process Process { get; set; }
public CliTaskState State { get; set; } = CliTaskState.Running;
public string Name { get; set; }
+ public int? ExitCode { get; set; }
+ public string StandardOutput { get; set; }
+ public string StandardError { get; set; }
+ public string FailureReason { get; set; }
public bool UseResult
{
@@ -22,4 +26,4 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
}
}
}
-}
\ No newline at end of file
+}
diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycle.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycle.cs
new file mode 100644
index 000000000..0c07a1fe6
--- /dev/null
+++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycle.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using TBF.Rig;
+
+namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
+{
+ ///
+ /// A single polling step for the Poseidon start/end read dialog.
+ ///
+ /// A reader is armed only from None. In particular, a terminal reader
+ /// must not be armed again: doing so starts a new CLI process on every
+ /// polling iteration and leaves the dialog in Loading.
+ ///
+ public interface IPoseidonReadOperation
+ {
+ string Name { get; }
+ bool IsNotStarted { get; }
+ bool IsFinished { get; }
+ bool HasError { get; }
+ void Start(bool readStart);
+ Event Run();
+ }
+
+ public sealed class PoseidonReaderOperation : IPoseidonReadOperation
+ {
+ private readonly PoseidonReader reader;
+
+ public PoseidonReaderOperation(PoseidonReader reader)
+ {
+ if (reader == null) throw new ArgumentNullException(nameof(reader));
+ this.reader = reader;
+ }
+
+ public string Name { get { return reader.Name; } }
+ public bool IsNotStarted { get { return reader.CurrentOp == PoseidonReader.CurrentPoseidonOp.None; } }
+ public bool IsFinished
+ {
+ get
+ {
+ return reader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Done ||
+ reader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error;
+ }
+ }
+ public bool HasError { get { return reader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error; } }
+ public void Start(bool readStart)
+ {
+ reader.SetCurrentOp(readStart
+ ? PoseidonReader.CurrentPoseidonOp.ReadDataStream_Start
+ : PoseidonReader.CurrentPoseidonOp.ReadDataStream_End);
+ }
+ public Event Run() { return reader.Run(); }
+ }
+
+ public static class PoseidonReadCycle
+ {
+ public static bool RunIteration(IEnumerable readers, bool readStart)
+ {
+ if (readers == null)
+ return true;
+
+ bool allReadersFinished = true;
+ foreach (IPoseidonReadOperation reader in readers)
+ {
+ if (reader == null)
+ continue;
+
+ // None means that this dialog operation has not been started yet.
+ // Done/Error are terminal and deliberately remain terminal.
+ if (reader.IsNotStarted)
+ reader.Start(readStart);
+
+ reader.Run();
+
+ if (!reader.IsFinished)
+ allReadersFinished = false;
+ }
+
+ return allReadersFinished;
+ }
+ }
+}
diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs
index 90318b0cb..99aab7f55 100644
--- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs
+++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs
@@ -3,6 +3,7 @@
///
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.IO.Ports;
using System.Text.RegularExpressions;
@@ -338,11 +339,17 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
private static long SafetyTimeOut = 30 * 1000;
private long incommingTime = -1;
private bool _lastOpTimedOut;
+ private bool lastCliReadingParsed;
+ private string lastCliReadFailureReason;
+ private bool lastCliReadSucceeded;
public long DeltaTime { get{return fullTimeInMilis;}}
public long IncommingTime { get{return incommingTime;}}
+ public bool LastCliReadingParsed { get { return lastCliReadingParsed; } }
+ public string LastCliReadFailureReason { get { return lastCliReadFailureReason; } }
+ public bool LastCliReadSucceeded { get { return lastCliReadSucceeded; } }
/// Run this operation
/// eventDone
@@ -405,6 +412,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
{
+ lastCliReadingParsed = false;
+ lastCliReadFailureReason = null;
+ lastCliReadSucceeded = false;
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
incommingTime = -1;
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
@@ -454,16 +464,30 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
{
var task = (Task)firstTaskInfo.Task;
data = task.Result;
+ if (data == null)
+ {
+ lastCliReadFailureReason = firstTaskInfo.FailureReason ?? "CLI returned no Poseidon JSON data.";
+ log.ErrorFormat("{0}: Poseidon {1} read failed. reason='{2}', exitCode={3}, stderr='{4}'",
+ Name,
+ _isReadingStart ? "Begin" : "End",
+ lastCliReadFailureReason,
+ firstTaskInfo.ExitCode,
+ firstTaskInfo.StandardError);
+ }
}
else
{
+ var lastTaskInfo = CliRunner.TaskPool.FindLast(t => t.Task is Task);
+ lastCliReadFailureReason = lastTaskInfo == null
+ ? "No CLI task was created."
+ : lastTaskInfo.FailureReason ?? "CLI task was not usable, state=" + lastTaskInfo.State + ".";
if (_lastOpTimedOut)
{
- log.Warn($"PoseidonReader {Name}: ReadDatastream timed out, no completed result available.");
+ log.Warn($"PoseidonReader {Name}: ReadDatastream timed out, no completed result available. {lastCliReadFailureReason}");
}
else
{
- log.Warn("No completed JsonDataFromPoseidon task available.");
+ log.Error($"PoseidonReader {Name}: no usable JsonDataFromPoseidon task. {lastCliReadFailureReason}");
}
}
@@ -471,6 +495,15 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
{
log.DebugFormat("{0}: CLI read result deviceId={1}, reading={2}, readingComplete={3}, nfcTagDetected={4}",
Name, data.DeviceId, data.Reading, data.ReadingComplete, data.NfcTagDetected);
+ string validationError;
+ if (!TryValidateCliReadResponse(data, out validationError))
+ {
+ lastCliReadFailureReason = validationError;
+ log.ErrorFormat("{0}: Poseidon {1} read rejected. {2}. Begin={3}, End={4}",
+ Name, _isReadingStart ? "Begin" : "End", validationError, beginWMState, endWMState);
+ }
+ else
+ {
if (string.IsNullOrEmpty(wmSerialNr))
{
try
@@ -484,14 +517,26 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
}
double volume;
- if (Double.TryParse(data.Reading, out volume))
+ if (TryParseCliReading(data.Reading, out volume))
{
+ lastCliReadingParsed = true;
+ lastCliReadSucceeded = true;
double volumeLi = Units.ConvertFrom(Unit.USgal, volume);
if (_isReadingStart)
beginWMState = volumeLi;
else
endWMState = volumeLi;
+
+ log.InfoFormat("{0}: Poseidon {1} value stored. deviceId={2}, rawReading='{3}', gallons={4}, litres={5}, Begin={6}, End={7}",
+ Name, _isReadingStart ? "Begin" : "End", data.DeviceId, data.Reading, volume, volumeLi, beginWMState, endWMState);
+ }
+ else
+ {
+ lastCliReadFailureReason = "Reading could not be parsed: '" + data.Reading + "'.";
+ log.ErrorFormat("{0}: cannot parse CLI reading '{1}' using invariant or current culture.",
+ Name, data.Reading);
+ }
}
}
@@ -504,6 +549,50 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
}
+ ///
+ /// CLI values must not depend on the Windows UI culture. Both dot and comma
+ /// are accepted as a decimal separator and normalized before parsing.
+ ///
+ public static bool TryParseCliReading(string reading, out double value)
+ {
+ value = 0;
+ if (String.IsNullOrWhiteSpace(reading))
+ return false;
+
+ string normalizedReading = reading.Trim().Replace(',', '.');
+ return Double.TryParse(normalizedReading, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
+ }
+
+ public static bool TryValidateCliReadResponse(JsonDataFromPoseidon data, out string failureReason)
+ {
+ if (data == null)
+ {
+ failureReason = "CLI returned no JSON data.";
+ return false;
+ }
+
+ if (!data.NfcTagDetected)
+ {
+ failureReason = "NfcTagDetected=false.";
+ return false;
+ }
+
+ if (!data.ReadingComplete)
+ {
+ failureReason = "ReadingComplete=false.";
+ return false;
+ }
+
+ if (String.IsNullOrWhiteSpace(data.Reading))
+ {
+ failureReason = "Reading is empty.";
+ return false;
+ }
+
+ failureReason = null;
+ return true;
+ }
+
/// Stop this operation
public void Stop()
diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/UniHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/PoseidonReader/UniHeadTestCtrl.cs
index 3c8d3d5b6..7ee475cd3 100644
--- a/TBF/Rig/RegisterReaders/PoseidonReader/UniHeadTestCtrl.cs
+++ b/TBF/Rig/RegisterReaders/PoseidonReader/UniHeadTestCtrl.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Windows.Forms;
+using log4net;
using TBF.Rig.RegisterReaders.CommonRR.IPerl.communication;
using TBF.Rig.RegisterReaders.iPerlReaderUNI.common;
using TBF.Rig.RegisterReaders.PoseidonReader.implementations;
@@ -11,6 +12,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader
{
public partial class UniHeadTestCtrl : UserControl
{
+ private static readonly ILog log = LogManager.GetLogger(typeof(UniHeadTestCtrl));
private IUniHeadTestCtrl _ctrl;
private IUniHeadTestCtrl Ctrl { get => _ctrl; }
@@ -77,6 +79,12 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader
RfidOutputListBox = rfidOutputListBox
});
}
+ catch (Exception exception)
+ {
+ // A device/CLI communication failure must not terminate Application.Run.
+ log.Error("Poseidon head test command failed.", exception);
+ rfidOutputListBox.Items.Add("Error: " + exception.Message);
+ }
finally
{
commandTestButton.Enabled = true;
diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/communication/OpticalHeadTest.cs b/TBF/Rig/RegisterReaders/PoseidonReader/communication/OpticalHeadTest.cs
index 35f5e234a..700687d27 100644
--- a/TBF/Rig/RegisterReaders/PoseidonReader/communication/OpticalHeadTest.cs
+++ b/TBF/Rig/RegisterReaders/PoseidonReader/communication/OpticalHeadTest.cs
@@ -77,21 +77,26 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader.communication
internal static string SetActiveMode(PoseidonCfg iHeadCfg)
{
- SerialPortData serialPortData = new SerialPortData(
- $"COM{iHeadCfg.RfidComPortNr}",
- iHeadCfg.CliProgramName,
- iHeadCfg.CliMeterType);
- NfcHeadServiceOld headService = new NfcHeadServiceOld(serialPortData);
- // Wait synchronously
- OptoHeadStatus optoHeadStatus = headService.SetTestingMode(OptoHeadStatus.OptoHeadDisabled);
- if (optoHeadStatus == OptoHeadStatus.OptoHeadDisabled)
+ try
{
- _lastOptoHeadStatus = optoHeadStatus;
- return "OK";
+ SerialPortData serialPortData = new SerialPortData(
+ $"COM{iHeadCfg.RfidComPortNr}",
+ iHeadCfg.CliProgramName,
+ iHeadCfg.CliMeterType);
+ NfcHeadServiceOld headService = new NfcHeadServiceOld(serialPortData);
+ OptoHeadStatus optoHeadStatus = headService.SetTestingMode(OptoHeadStatus.OptoHeadDisabled);
+ if (optoHeadStatus == OptoHeadStatus.OptoHeadDisabled)
+ {
+ _lastOptoHeadStatus = optoHeadStatus;
+ return "OK";
+ }
+
+ return "Error Set Active Mode";
}
- else
+ catch (Exception exception)
{
- return "Error Set Test Mode";
+ rfidDataLogger.Error("Poseidon Set Active Mode failed.", exception);
+ return "Error Set Active Mode: " + exception.Message;
}
}
@@ -100,23 +105,28 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader.communication
internal static string SetTestMode(PoseidonCfg iHeadCfg)
{
- SerialPortData serialPortData = new SerialPortData(
- $"COM{iHeadCfg.RfidComPortNr}",
- iHeadCfg.CliProgramName,
- iHeadCfg.CliMeterType);
- NfcHeadServiceOld headService = new NfcHeadServiceOld(serialPortData);
- // Wait synchronously
- OptoHeadStatus optoHeadStatus = headService.SetTestingMode(OptoHeadStatus.OptoHeadC7);
- if (optoHeadStatus == OptoHeadStatus.OptoHeadC7)
- {
- _lastOptoHeadStatus = optoHeadStatus;
- _lastIHeadCfg = iHeadCfg;
- return "OK";
- }
- else
+ try
{
+ SerialPortData serialPortData = new SerialPortData(
+ $"COM{iHeadCfg.RfidComPortNr}",
+ iHeadCfg.CliProgramName,
+ iHeadCfg.CliMeterType);
+ NfcHeadServiceOld headService = new NfcHeadServiceOld(serialPortData);
+ OptoHeadStatus optoHeadStatus = headService.SetTestingMode(OptoHeadStatus.OptoHeadC7);
+ if (optoHeadStatus == OptoHeadStatus.OptoHeadC7)
+ {
+ _lastOptoHeadStatus = optoHeadStatus;
+ _lastIHeadCfg = iHeadCfg;
+ return "OK";
+ }
+
return "Error Set Test Mode";
}
+ catch (Exception exception)
+ {
+ rfidDataLogger.Error("Poseidon Set Test Mode failed.", exception);
+ return "Error Set Test Mode: " + exception.Message;
+ }
}
private static OptoHeadService optoHeadService;
diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/implementations/PoseidonImplHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/PoseidonReader/implementations/PoseidonImplHeadTestCtrl.cs
index 65761e1b2..b6c95eb12 100644
--- a/TBF/Rig/RegisterReaders/PoseidonReader/implementations/PoseidonImplHeadTestCtrl.cs
+++ b/TBF/Rig/RegisterReaders/PoseidonReader/implementations/PoseidonImplHeadTestCtrl.cs
@@ -85,18 +85,20 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader.implementations
rfidListItem.Text = $"ReadSerialNo: {OpticalHeadTest.ReadRequest_SerialNo(poseidonCfg)}";
break;
case Operations.SetTestModeOn:
- rfidListItem.Text = $"SetTestMode: {OpticalHeadTest.SetTestMode(poseidonCfg)}";
- //Do start thread
-
- a.OptoListBox.Items.Clear();
- stopWorkerThread = false;
- optoThread = new Thread(OptoWorker);
- if (!optoThread.IsAlive)
+ string setTestModeResult = OpticalHeadTest.SetTestMode(poseidonCfg);
+ rfidListItem.Text = $"SetTestMode: {setTestModeResult}";
+ // Start the opto worker only after a successful mode change.
+ if (setTestModeResult == "OK")
{
- OpticalHeadTest.StartOptotestInputLoop(poseidonCfg,OptoReceivedHandler); // open opto port
- optoThread.Start();
+ a.OptoListBox.Items.Clear();
+ stopWorkerThread = false;
+ optoThread = new Thread(OptoWorker);
+ if (!optoThread.IsAlive)
+ {
+ OpticalHeadTest.StartOptotestInputLoop(poseidonCfg, OptoReceivedHandler);
+ optoThread.Start();
+ }
}
-
break;
case Operations.SetTestModeOff:
//Do stop thread
@@ -133,4 +135,4 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader.implementations
this.stopWorkerThread = true;
}
}
-}
\ No newline at end of file
+}
diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj
index b8a4bffa5..b8afb07f5 100644
--- a/TBF/TBF.csproj
+++ b/TBF/TBF.csproj
@@ -1331,6 +1331,7 @@
+
diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutTest.cs
index c144e22d2..4d312fb37 100644
--- a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutTest.cs
+++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunnerTimeoutTest.cs
@@ -285,14 +285,25 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
Assert.IsNotNull(task);
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
- // Depending on TryJsonStringDeserialize fallback,
- // result may be null or an empty/default object.
- if (task.Result != null)
- {
- Assert.IsTrue(
- string.IsNullOrEmpty(task.Result.DeviceId),
- "Invalid JSON should not produce a valid DeviceId.");
- }
+ Assert.IsNull(task.Result, "Invalid JSON must not create a default reading object.");
+ }
+
+ [TestMethod]
+ public async Task CliProcess_ExitErrorWithoutJson_RecordsDiagnosticsAndReturnsNoReading()
+ {
+ string cmdExe = System.Environment.GetEnvironmentVariable("ComSpec") ?? @"C:\\Windows\\System32\\cmd.exe";
+ var cliRunner = new CliRunner(false);
+ var info = new CliTaskInfo { Name = "missing-dependency-simulation" };
+
+ JsonDataFromPoseidon result = await cliRunner.RunAndCaptureJsonAsync(
+ cmdExe,
+ "/d /c \"echo Could not load file or assembly 'log4net' 1>&2 & exit /b 17\"",
+ info);
+
+ Assert.IsNull(result, "A failed CLI process must not create a default zero reading.");
+ Assert.AreEqual(17, info.ExitCode);
+ StringAssert.Contains(info.FailureReason, "exit code 17");
+ StringAssert.Contains(info.StandardError, "log4net");
}
}
-}
\ No newline at end of file
+}
diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycleTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycleTest.cs
new file mode 100644
index 000000000..9aa1ae19b
--- /dev/null
+++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycleTest.cs
@@ -0,0 +1,213 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using JetBrains.Annotations;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TBF.Rig;
+using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
+using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
+
+namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
+{
+ [TestClass]
+ [TestSubject(typeof(PoseidonReadCycle))]
+ public class PoseidonReadCycleTest
+ {
+ private sealed class FakePoseidonReader : IPoseidonReadOperation
+ {
+ public string Name { get; private set; }
+ public CmdPoseidonReader.CurrentPoseidonOp CurrentOp { get; private set; }
+ public int StartCount { get; private set; }
+ public int RunCount { get; private set; }
+ private readonly int runningIterationsBeforeDone;
+ private int runningIterations;
+
+ public FakePoseidonReader(string name, int runningIterationsBeforeDone = 1)
+ {
+ Name = name;
+ this.runningIterationsBeforeDone = runningIterationsBeforeDone;
+ }
+
+ public bool IsNotStarted { get { return CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.None; } }
+ public bool IsFinished
+ {
+ get
+ {
+ return CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.Done ||
+ CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.Error;
+ }
+ }
+ public bool HasError { get { return CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.Error; } }
+
+ public void Start(bool readStart)
+ {
+ StartCount++;
+ CurrentOp = readStart
+ ? CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_Start
+ : CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_End;
+ }
+
+ public Event Run()
+ {
+ RunCount++;
+ if (CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_Start ||
+ CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_End)
+ {
+ CurrentOp = CmdPoseidonReader.CurrentPoseidonOp.ReadDatastream_Running;
+ return Event.Busy;
+ }
+
+ if (CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.ReadDatastream_Running)
+ {
+ runningIterations++;
+ if (runningIterations >= runningIterationsBeforeDone)
+ {
+ CurrentOp = CmdPoseidonReader.CurrentPoseidonOp.Done;
+ return Event.Done;
+ }
+
+ return Event.Busy;
+ }
+
+ return Event.None;
+ }
+ }
+
+ [TestMethod]
+ public void RunIteration_FullBench_DoesNotRestartCompletedReaders()
+ {
+ var readers = new List();
+ for (int i = 1; i <= 48; i++)
+ readers.Add(new FakePoseidonReader("PoseidonPos" + i, 1 + (i % 3)));
+
+ Assert.IsFalse(PoseidonReadCycle.RunIteration(readers, true));
+ Assert.IsFalse(PoseidonReadCycle.RunIteration(readers, true));
+ Assert.IsFalse(PoseidonReadCycle.RunIteration(readers, true));
+ Assert.IsTrue(PoseidonReadCycle.RunIteration(readers, true));
+
+ foreach (FakePoseidonReader reader in readers)
+ {
+ Assert.AreEqual(1, reader.StartCount, reader.Name + " must be armed exactly once.");
+ Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
+ }
+ }
+
+ [TestMethod]
+ public void RunIteration_ReducedBench_DoesNotRestartCompletedReaders()
+ {
+ var readers = new List
+ {
+ new FakePoseidonReader("PoseidonPos1", 1),
+ new FakePoseidonReader("PoseidonPos2", 3)
+ };
+
+ Assert.IsFalse(PoseidonReadCycle.RunIteration(readers, false));
+ // Pos1 is now Done while Pos2 is still running. This is the state
+ // which used to relaunch Pos1's CLI process over and over.
+ Assert.IsFalse(PoseidonReadCycle.RunIteration(readers, false));
+ Assert.AreEqual(1, ((FakePoseidonReader)readers[0]).StartCount);
+ Assert.IsFalse(PoseidonReadCycle.RunIteration(readers, false));
+ Assert.IsTrue(PoseidonReadCycle.RunIteration(readers, false));
+
+ foreach (FakePoseidonReader reader in readers)
+ {
+ Assert.AreEqual(1, reader.StartCount, reader.Name + " must be armed exactly once.");
+ }
+ }
+
+ [TestMethod]
+ public void InputJson_ZeroReading_WithDecimalCommaOrDot_IsDeserializedAndParsedCorrectly()
+ {
+ AssertInputJsonReading("00000,0", 0.0);
+ AssertInputJsonReading("00000.0", 0.0);
+ }
+
+ [TestMethod]
+ public void CliReadResponse_ZeroReading_IsAcceptedOnlyWhenNfcAndReadingAreComplete()
+ {
+ var validZero = new JsonDataFromPoseidon
+ {
+ NfcTagDetected = true,
+ ReadingComplete = true,
+ Reading = "00000.0"
+ };
+ string failureReason;
+
+ Assert.IsTrue(CmdPoseidonReader.TryValidateCliReadResponse(validZero, out failureReason));
+ Assert.IsNull(failureReason);
+
+ validZero.ReadingComplete = false;
+ Assert.IsFalse(CmdPoseidonReader.TryValidateCliReadResponse(validZero, out failureReason));
+ Assert.AreEqual("ReadingComplete=false.", failureReason);
+
+ validZero.ReadingComplete = true;
+ validZero.NfcTagDetected = false;
+ Assert.IsFalse(CmdPoseidonReader.TryValidateCliReadResponse(validZero, out failureReason));
+ Assert.AreEqual("NfcTagDetected=false.", failureReason);
+ }
+
+ private static void AssertInputJsonReading(string reading, double expectedValue)
+ {
+ string json = "{\"NfcTagDetected\":true,\"ReadingComplete\":true," +
+ "\"DeviceId\":\"1000000322\",\"Reading\":\"" + reading + "\"}";
+ var cliRunner = new CliRunner(false);
+ JsonDataFromPoseidon data;
+
+ bool deserialized = cliRunner.TryJsonStringDeserialize(json, out data);
+ Assert.IsTrue(deserialized, "CLI JSON must be deserialized for Reading='" + reading + "'.");
+ Assert.IsNotNull(data);
+ Assert.IsTrue(data.NfcTagDetected);
+ Assert.IsTrue(data.ReadingComplete);
+ Assert.AreEqual("1000000322", data.DeviceId);
+ Assert.AreEqual(reading, data.Reading);
+
+ double parsedReading;
+ bool parsed = CmdPoseidonReader.TryParseCliReading(data.Reading, out parsedReading);
+ Assert.IsTrue(parsed, "CLI Reading must be parsed for '" + reading + "'.");
+ Assert.AreEqual(expectedValue, parsedReading, 0.000001);
+ }
+
+ [TestMethod]
+ [TestCategory("Integration")]
+ public void CmdSleepTest_OneReader_CompletesAndStoresStartValue()
+ {
+ const string cliFile = "cmdSleepTest.exe";
+ string cliPath = Path.Combine(SerialPortData.CliDirectory, cliFile);
+ if (!File.Exists(cliPath))
+ Assert.Inconclusive("cmdSleepTest.exe is not installed in " + SerialPortData.CliDirectory);
+
+ CmdPoseidonReader reader = CreateReader("PoseidonPos1", 3, cliFile);
+ reader.SetCurrentOp(CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_Start);
+
+ for (int iteration = 0; iteration < 300 && !IsTerminal(reader); iteration++)
+ {
+ reader.Run();
+ Thread.Sleep(10);
+ }
+
+ Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
+ Assert.IsFalse(string.IsNullOrEmpty(reader.SerialNr));
+ Assert.IsTrue(reader.BeginWMState > 0, "The JSON reading from cmdSleepTest must be transferred to BeginWMState.");
+ }
+
+ internal static CmdPoseidonReader CreateReader(string name, int comPort, string cliFile)
+ {
+ var cfg = new PoseidonCfg(name, new Factory())
+ {
+ ComPortNr = comPort,
+ CliFileName = cliFile,
+ TimeOut = 10
+ };
+ var reader = new CmdPoseidonReader(cfg, null);
+ reader.DebugLevel = Common.DebugMode.Normal;
+ reader.Initialize();
+ return reader;
+ }
+
+ internal static bool IsTerminal(CmdPoseidonReader reader)
+ {
+ return reader.CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.Done ||
+ reader.CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.Error;
+ }
+ }
+}
diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReaderTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReaderTest.cs
index 26abb99fa..7d2f2e48b 100644
--- a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReaderTest.cs
+++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReaderTest.cs
@@ -1,5 +1,9 @@
using JetBrains.Annotations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Globalization;
+using System.Threading;
+using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
+using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
{
@@ -37,5 +41,30 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
Assert.IsTrue(tryGetDeviceId);
Assert.AreEqual("1000000267", strOut);
}
+
+ [TestMethod]
+ public void TryParseCliReading_DecimalDot_IsIndependentOfWindowsCulture()
+ {
+ CultureInfo originalCulture = Thread.CurrentThread.CurrentCulture;
+ try
+ {
+ Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("sk-SK");
+ double value;
+
+ bool parsed = CmdPoseidonReader.TryParseCliReading("0014383.3", out value);
+
+ Assert.IsTrue(parsed);
+ Assert.AreEqual(14383.3, value, 0.000001);
+
+ parsed = CmdPoseidonReader.TryParseCliReading("0014383,3", out value);
+
+ Assert.IsTrue(parsed);
+ Assert.AreEqual(14383.3, value, 0.000001);
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentCulture = originalCulture;
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonSingleMeterIntegrationTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonSingleMeterIntegrationTest.cs
new file mode 100644
index 000000000..994bfbaa1
--- /dev/null
+++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonSingleMeterIntegrationTest.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Linq;
+using System.Threading;
+using System.Windows.Forms;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TBF.Rig.DataEntry.PoseidonCmd;
+using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
+
+namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
+{
+ ///
+ /// Manual, opt-in test for a real Poseidon meter/test-hat. It is deliberately
+ /// inconclusive unless TBF_POSEIDON_COM_PORT_X or _Y is configured. Do not
+ /// use it while an ASIC meter is connected to the selected test hat.
+ ///
+ [TestClass]
+ [TestCategory("Integration")]
+ public class PoseidonSingleMeterIntegrationTest
+ {
+ // Set exactly one of these to the real COM number after a Poseidon meter
+ // is connected. Keep both values 0 while an ASIC meter is connected.
+ private const int RealPoseidonComPortHat = 3;
+ private const int RealPoseidonComPortOptho = 4;
+ private const string RealPoseidonCliFile = "HatCliDemo.exe";
+
+ [TestMethod]
+ [TestCategory("Manual")]
+ public void RealMeter_ReadAndFillStartDialog()
+ {
+ int comPort = RealPoseidonComPortHat > 0
+ ? RealPoseidonComPortHat
+ : RealPoseidonComPortOptho;
+ if (comPort <= 0)
+ {
+ Assert.Inconclusive(
+ "Set RealPoseidonComPortHat or RealPoseidonComPortOptho in this test class before running the manual integration test.");
+ }
+
+ Exception failure = null;
+ Thread testThread = new Thread(() =>
+ {
+ try
+ {
+ RunRealMeterScenario(comPort, RealPoseidonCliFile);
+ }
+ catch (Exception exception)
+ {
+ failure = exception;
+ }
+ });
+ testThread.SetApartmentState(ApartmentState.STA);
+ testThread.IsBackground = true;
+ testThread.Start();
+ testThread.Join(TimeSpan.FromSeconds(75));
+
+ Assert.IsFalse(testThread.IsAlive, "The real-meter integration test did not finish within 75 seconds.");
+ if (failure != null) throw failure;
+ }
+
+ private static void RunRealMeterScenario(int comPort, string cliFile)
+ {
+ var reader = PoseidonReadCycleTest.CreateReader("PoseidonPos1", comPort, cliFile);
+ reader.SetCurrentOp(CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_Start);
+
+ for (int iteration = 0; iteration < 3500 && !PoseidonReadCycleTest.IsTerminal(reader); iteration++)
+ {
+ reader.Run();
+ Thread.Sleep(10);
+ }
+
+ Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
+ Assert.IsTrue(reader.LastCliReadingParsed,
+ "CLI returned no parseable Reading. Check the Poseidon reader and CLI logs for the raw JSON response.");
+
+ TBF.Data.SetData(1, 1, 1);
+ using (var dialog = new TestStartEndForm(1, new TBF.Rig.GenericDevices.IRegReader[] { reader }, new bool[1]))
+ {
+ dialog.CreateControl();
+ dialog.WMStartState[0] = reader.BeginWMState;
+ dialog.WMStartStateStr[0] = reader.BeginWMState.ToString();
+ dialog.UpdateValues(true, true, reader.DeltaTime);
+
+ TextBox startTextBox = dialog.Controls.Find("startTextBox1", true).OfType().FirstOrDefault();
+ Assert.IsNotNull(startTextBox, "The start-state text box must exist.");
+ Assert.AreEqual(dialog.WMStartStateStr[0], startTextBox.Text);
+ }
+ }
+ }
+}
diff --git a/TBFTests/Rig/TestMethods/iPerlCommunication/IPerlCommunicationFormIntegrationTest.cs b/TBFTests/Rig/TestMethods/iPerlCommunication/IPerlCommunicationFormIntegrationTest.cs
new file mode 100644
index 000000000..8b4533d49
--- /dev/null
+++ b/TBFTests/Rig/TestMethods/iPerlCommunication/IPerlCommunicationFormIntegrationTest.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Threading;
+using Common;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
+using IPerlCommunicationForm = TBF.Rig.TestMethods.iPerlCommunication.iPerlCommunicationFormTestMethod;
+using IPerlConfigStruct = TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.ConfigStruct;
+using IPerlHead = TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHead;
+using IPerlHeadCfg = TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfg;
+using IPerlHeadFactory = TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.Factory;
+using static Sensus.iPerl.NfcHandler.MCI_Protocol;
+
+namespace TBFTests.Rig.TestMethods.iPerlCommunication
+{
+ ///
+ /// Manual, read-only integration test through the legacy iPerl communication form.
+ /// It reads Configuration only; it never writes to the connected meter.
+ ///
+ [TestClass]
+ [TestCategory("Integration")]
+ public class IPerlCommunicationFormIntegrationTest
+ {
+ // Set the COM port of the currently connected iPerl/ASIC test head.
+ // Keep 0 to disable this manual test.
+ private const int RealRfidComPort = 3;
+ private const int RealOptoComPort = 0;
+ private const CommunicationInterface RealCommunicationInterface = CommunicationInterface.RFID;
+
+ [TestMethod]
+ [TestCategory("Manual")]
+ public void ReadConfiguration_ThroughIPerlCommunicationForm()
+ {
+ if (RealRfidComPort <= 0)
+ {
+ Assert.Inconclusive(
+ "Set RealRfidComPort in IPerlCommunicationFormIntegrationTest before running this manual test.");
+ }
+
+ Exception failure = null;
+ Thread testThread = new Thread(() =>
+ {
+ try
+ {
+ ReadConfiguration();
+ }
+ catch (Exception exception)
+ {
+ failure = exception;
+ }
+ });
+ testThread.SetApartmentState(ApartmentState.STA);
+ testThread.IsBackground = true;
+ testThread.Start();
+ testThread.Join(TimeSpan.FromSeconds(30));
+
+ Assert.IsFalse(testThread.IsAlive, "iPerl configuration read did not finish within 30 seconds.");
+ if (failure != null) throw failure;
+ }
+
+ private static void ReadConfiguration()
+ {
+ var headCfg = new IPerlHeadCfg(new IPerlHeadFactory())
+ {
+ Name = "iPerl1",
+ RfidComPortNr = RealRfidComPort,
+ OptoComPortNr = RealOptoComPort,
+ CommunicationInterface = RealCommunicationInterface
+ };
+ var head = new IPerlHead(headCfg)
+ {
+ DebugLevel = DebugMode.Normal
+ };
+ head.StartSession();
+
+ // The form initializes the same CfgIPerl and request pipeline that
+ // production iPerlCommunication uses. No form is shown to the user.
+ using (var form = new IPerlCommunicationForm(true))
+ {
+ byte[] buffer;
+ int result = IPerlCommunicationForm.ReadRequestPort(
+ head,
+ MessageID.Configuration,
+ StructName.Configuration,
+ 0,
+ IPerlConfigStruct.Length,
+ out buffer);
+
+ Assert.AreEqual(0, result, "iPerlCommunicationForm could not read the meter Configuration.");
+ Assert.IsNotNull(buffer);
+ Assert.AreEqual(IPerlConfigStruct.Length, buffer.Length);
+
+ IPerlConfigStruct configuration = IPerlConfigStruct.FromByteArray(buffer);
+ Assert.IsNotNull(configuration);
+ Assert.IsFalse(string.IsNullOrWhiteSpace(configuration.GetPcbNrString()));
+ }
+ }
+ }
+}
diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj
index 2c59a2925..b2d12071d 100644
--- a/TBFTests/TBFTests.csproj
+++ b/TBFTests/TBFTests.csproj
@@ -107,6 +107,9 @@
+
+
+
@@ -115,6 +118,10 @@
+
+ {5954d496-caab-4f7a-bde2-bdc8f47dab19}
+ NfcS5_DLL
+
{c8939821-ba5c-4988-a3d0-bf53b74865c7}
Common
@@ -171,4 +178,4 @@
-->
-
\ No newline at end of file
+