fix(morrisville): release 3.9.2205.1 Poseidon CLI diagnostics and results DB migration
- improve Poseidon CLI diagnostics with working-directory, exit-code, stdout/stderr, JSON and NFC/reading validation - accept decimal dot and comma readings, including valid zero readings - add reader-cycle, fake CLI, timeout and Poseidon parsing test coverage - prevent legacy optical-head mode errors from crashing the UI - add automatic MySQL/SQLite Results DB migrations for MeterTestRslt: FlipMode, ExtraDataPath and X1–X9 - add SQLite reference required by the migration helper - update ReadMe_BugFix version history for releases 3.9.2149–3.9.2205
This commit is contained in:
parent
2fd2697e3b
commit
59cd5f9dde
@ -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();
|
||||
}
|
||||
|
||||
@ -53,6 +53,9 @@
|
||||
<Reference Include="NHibernate">
|
||||
<HintPath>..\packages\NHibernate.4.0.4.4000\lib\net40\NHibernate.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SQLite, Version=2.0.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Data.SQLite.2.0.3\lib\net471\System.Data.SQLite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
@ -69,6 +72,7 @@
|
||||
<Compile Include="DBase.cs" />
|
||||
<Compile Include="Entities\Batch.cs" />
|
||||
<Compile Include="Entities\Components.cs" />
|
||||
<Compile Include="Entities\helpers\DatabaseMigrationHelper.cs" />
|
||||
<Compile Include="Entities\MeterTestRslt.cs" />
|
||||
<Compile Include="Entities\PurchaseBox.cs" />
|
||||
<Compile Include="Entities\PurchaseOrder.cs" />
|
||||
@ -265,4 +269,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@ -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")]
|
||||
|
||||
@ -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. |
|
||||
|
||||
@ -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<IPoseidonReadOperation> poseidonReaders = new List<IPoseidonReadOperation>();
|
||||
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<IPoseidonReadOperation, bool> operationsBefore =
|
||||
new Dictionary<IPoseidonReadOperation, bool>();
|
||||
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)
|
||||
|
||||
@ -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<T>(string json, out T value) where T : new()
|
||||
{
|
||||
if (json != null)
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
try
|
||||
{
|
||||
value = JsonConvert.DeserializeObject<T>(json);
|
||||
return true;
|
||||
return value != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Debug(ex.Message);
|
||||
value = TryConvert<T>(json);
|
||||
return true;
|
||||
return TryConvert(json, out value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,7 +431,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
return false;
|
||||
}
|
||||
|
||||
private static T TryConvert<T>(string json) where T : new()
|
||||
private static bool TryConvert<T>(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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<IPoseidonReadOperation> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; } }
|
||||
|
||||
/// <summary>Run this operation</summary>
|
||||
/// <returns>eventDone</returns>
|
||||
@ -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<JsonDataFromPoseidon>)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<JsonDataFromPoseidon>);
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CLI values must not depend on the Windows UI culture. Both dot and comma
|
||||
/// are accepted as a decimal separator and normalized before parsing.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Stop this operation</summary>
|
||||
public void Stop()
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1331,6 +1331,7 @@
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonCfg.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonProcParams.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReader.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReadCycle.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\ProcessExtensions.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\ScopedLoggerFactory.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\SerialPortData.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<JsonDataFromPoseidon>(
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<IPoseidonReadOperation>();
|
||||
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<IPoseidonReadOperation>
|
||||
{
|
||||
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<JsonDataFromPoseidon>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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<TextBox>().FirstOrDefault();
|
||||
Assert.IsNotNull(startTextBox, "The start-state text box must exist.");
|
||||
Assert.AreEqual(dialog.WMStartStateStr[0], startTextBox.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Manual, read-only integration test through the legacy iPerl communication form.
|
||||
/// It reads Configuration only; it never writes to the connected meter.
|
||||
/// </summary>
|
||||
[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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,6 +107,9 @@
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutUnitTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReaderTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReadCycleTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonSingleMeterIntegrationTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\IPerlCommunicationFormIntegrationTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonReader\UniHeadTestCtrlTest.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\ReadStableMassOpTest.cs" />
|
||||
</ItemGroup>
|
||||
@ -115,6 +118,10 @@
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\NfcS5_DLL\NfcS5_DLL.csproj">
|
||||
<Project>{5954d496-caab-4f7a-bde2-bdc8f47dab19}</Project>
|
||||
<Name>NfcS5_DLL</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Common\Common.csproj">
|
||||
<Project>{c8939821-ba5c-4988-a3d0-bf53b74865c7}</Project>
|
||||
<Name>Common</Name>
|
||||
@ -171,4 +178,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user