diff --git a/Results/Entities/helpers/DatabaseMigrationHelper.cs b/Results/Entities/helpers/DatabaseMigrationHelper.cs
index 6481ed596..8f301141e 100644
--- a/Results/Entities/helpers/DatabaseMigrationHelper.cs
+++ b/Results/Entities/helpers/DatabaseMigrationHelper.cs
@@ -1,6 +1,7 @@
using System;
using MySql.Data.MySqlClient;
using Common;
+using log4net;
namespace Results.Entities.helpers
{
@@ -10,6 +11,8 @@ namespace Results.Entities.helpers
///
public static class DatabaseMigrationHelper
{
+ private static readonly ILog log = LogManager.GetLogger(typeof(DatabaseMigrationHelper));
+
public static void EnsureSchema(DBType dbType, string connectionString)
{
switch (dbType)
@@ -36,9 +39,31 @@ namespace Results.Entities.helpers
// EnsureColumnMySql(conn, "WaterMeter", "Q3Channel", "INT NOT NULL DEFAULT 0");
// EnsureColumnMySql(conn, "MeterTestRslt", "Q3Channel", "INT NOT NULL DEFAULT 0");
- EnsureColumnMySql(conn, "MeterTestRslt", "FlipMode", "INT NULL");
- }
- }
+ EnsureColumnMySql(conn, "MeterTestRslt", "PulsesPerKilogram", "DOUBLE NOT NULL DEFAULT 0");
+ EnsureMeterTestResultMassColumnsMySql(conn);
+ EnsureColumnMySql(conn, "MeterTestRslt", "FlipMode", "INT NULL");
+ EnsureColumnMySql(conn, "MeterTestRslt", "ExtraDataPath", "VARCHAR(255) NULL");
+ EnsureMeterTestResultExtraColumnsMySql(conn);
+ }
+ }
+
+ private static void EnsureMeterTestResultExtraColumnsMySql(MySqlConnection conn)
+ {
+ for (int index = 1; index <= 9; index++)
+ EnsureColumnMySql(conn, "MeterTestRslt", "X" + index, "FLOAT NOT NULL DEFAULT 0");
+ }
+
+ // These properties were added to the MeterTestRslt NHibernate mapping
+ // after older customer databases had already been created. Keep them in
+ // one migration group so a batch insert does not fail one column at a time.
+ private static void EnsureMeterTestResultMassColumnsMySql(MySqlConnection conn)
+ {
+ EnsureColumnMySql(conn, "MeterTestRslt", "MassMeter", "DOUBLE NOT NULL DEFAULT 0");
+ EnsureColumnMySql(conn, "MeterTestRslt", "MassRef", "DOUBLE NOT NULL DEFAULT 0");
+ EnsureColumnMySql(conn, "MeterTestRslt", "ErrorMass", "DOUBLE NOT NULL DEFAULT 0");
+ EnsureColumnMySql(conn, "MeterTestRslt", "PassedMass", "TINYINT(1) NOT NULL DEFAULT 0");
+ EnsureColumnMySql(conn, "MeterTestRslt", "QuantityUnits", "VARCHAR(32) NULL");
+ }
private static void EnsureColumnMySql(
MySqlConnection conn,
@@ -74,7 +99,9 @@ namespace Results.Entities.helpers
alter.Transaction = transaction;
alter.CommandText = "ALTER TABLE `" + tableName + "` ADD COLUMN `" +
columnName + "` " + columnDefinition;
- alter.ExecuteNonQuery();
+ alter.ExecuteNonQuery();
+ log.WarnFormat("Results DB migration: added {0}.{1} ({2}).",
+ tableName, columnName, columnDefinition);
}
}
@@ -100,9 +127,28 @@ namespace Results.Entities.helpers
// EnsureColumnSQLite(conn, "WaterMeter", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
// EnsureColumnSQLite(conn, "MeterTestRslt", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
- EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL");
- }
- }
+ EnsureColumnSQLite(conn, "MeterTestRslt", "PulsesPerKilogram", "REAL NOT NULL DEFAULT 0");
+ EnsureMeterTestResultMassColumnsSQLite(conn);
+ EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL");
+ EnsureColumnSQLite(conn, "MeterTestRslt", "ExtraDataPath", "TEXT NULL");
+ EnsureMeterTestResultExtraColumnsSQLite(conn);
+ }
+ }
+
+ private static void EnsureMeterTestResultExtraColumnsSQLite(System.Data.SQLite.SQLiteConnection conn)
+ {
+ for (int index = 1; index <= 9; index++)
+ EnsureColumnSQLite(conn, "MeterTestRslt", "X" + index, "REAL NOT NULL DEFAULT 0");
+ }
+
+ private static void EnsureMeterTestResultMassColumnsSQLite(System.Data.SQLite.SQLiteConnection conn)
+ {
+ EnsureColumnSQLite(conn, "MeterTestRslt", "MassMeter", "REAL NOT NULL DEFAULT 0");
+ EnsureColumnSQLite(conn, "MeterTestRslt", "MassRef", "REAL NOT NULL DEFAULT 0");
+ EnsureColumnSQLite(conn, "MeterTestRslt", "ErrorMass", "REAL NOT NULL DEFAULT 0");
+ EnsureColumnSQLite(conn, "MeterTestRslt", "PassedMass", "INTEGER NOT NULL DEFAULT 0");
+ EnsureColumnSQLite(conn, "MeterTestRslt", "QuantityUnits", "TEXT NULL");
+ }
private static void EnsureColumnSQLite(
System.Data.SQLite.SQLiteConnection conn,
@@ -136,7 +182,9 @@ namespace Results.Entities.helpers
{
alter.CommandText = "ALTER TABLE " + tableName + " ADD COLUMN " +
columnName + " " + columnDefinition;
- alter.ExecuteNonQuery();
+ alter.ExecuteNonQuery();
+ log.WarnFormat("Results DB migration: added {0}.{1} ({2}).",
+ tableName, columnName, columnDefinition);
}
}
}
diff --git a/TBF/ReadMe_BugFix.md b/TBF/ReadMe_BugFix.md
index eb9891461..bb265abb5 100644
--- a/TBF/ReadMe_BugFix.md
+++ b/TBF/ReadMe_BugFix.md
@@ -3,3 +3,14 @@
| Version | Source of change | Target Environment | Title | Description |
|------------|----------------------|--------------------------------------------------------------|-----------------------------------------------------|------------------------------------|
| 3.9.2149.0 | Michal databse error | 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 | Main TBF | General release | Version iteration | Assembly and file version increment. |
+| 3.9.2149.2 | Main TBF | Poseidon register reader | Poseidon pulse and timing handling | Added reference-pulse reading in `Run()` and improved Poseidon timing/task tracking. |
+| 3.9.2149.4 | Main TBF | 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 | Main TBF | Test infrastructure | TBF test assembly access | Added `InternalsVisibleTo` support for `TBFTests`. |
+| 3.9.2201.1 | Main TBF | Poseidon CLI | Poseidon CLI configuration | Added macro descriptions, refined serial-port/CLI argument configuration and extended CLI test coverage. |
+| 3.9.2202.1 | Main TBF | Poseidon CLI | CLI release iteration | Assembly and file version increment for the Poseidon CLI workstream. |
+| 3.9.2203.1 | Main TBF | 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 | 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 | 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 coverage. Results DB now creates missing compatibility columns (`FlipMode`, `ExtraDataPath`, `X1`-`X9`) automatically for MySQL and SQLite. |
+| 3.9.2206.0 | Morrisville / Main TBF | Poseidon CLI / Results DB | Poseidon start/end rearm and simulation isolation | Re-armed a completed START reader once for END, preventing reused START values or skipped END CLI calls. Added CLI response, dialog prefill and confirmed-value diagnostics. Simulation always runs `C:\TBF\Cli\cmdSleepTest.exe` instead of the configured physical Hat CLI. Added customer-response, decimal separator, non-zero, dialog-transfer, reader-cycle and simulation-selection regression tests. Added compatibility migration for `PulsesPerKilogram`, `MassMeter`, `MassRef`, `ErrorMass`, `PassedMass` and `QuantityUnits`. |
+| 3.9.3143.0 | Ally port | Ally / Poseidon CLI / Results DB | Morrisville Poseidon fixes transferred | Ported the applicable Morrisville Poseidon read, simulation, logging, regression-test and results-schema migration fixes to the Ally source branch while retaining its compatible legacy reader configuration and non-blocking UI flow. |
diff --git a/TBF/Rig/DataEntry/PoseidonCmd/EntryForm.cs b/TBF/Rig/DataEntry/PoseidonCmd/EntryForm.cs
index 6f4d39027..50e35f622 100644
--- a/TBF/Rig/DataEntry/PoseidonCmd/EntryForm.cs
+++ b/TBF/Rig/DataEntry/PoseidonCmd/EntryForm.cs
@@ -368,10 +368,16 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
{
dlg.WMStartState[item] = poseidonReader.BeginWMState;
dlg.WMStartStateStr[item] = poseidonReader.BeginWMState.ToString();
+ log.InfoFormat("Poseidon dialog prefill: phase=Start, WM{0}, reader={1}, value={2}",
+ item + 1, poseidonReader.Name, dlg.WMStartState[item]);
}
if (currentOp == CurrentOp.ReadDatastream_EndStates)
+ {
dlg.WMEndState[item] = poseidonReader.EndWMState;
+ log.InfoFormat("Poseidon dialog prefill: phase=End, WM{0}, reader={1}, value={2}",
+ item + 1, poseidonReader.Name, dlg.WMEndState[item]);
+ }
}
}
@@ -426,31 +432,10 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
}
}
}
- else if (modelessDlg is TestStartEndForm && currentOp == CurrentOp.ReadDatastream_StartStates)
+ else if (modelessDlg is TestStartEndForm)
{
- /// Fixed start test - start
- TestStartEndForm dlg = (modelessDlg as TestStartEndForm);
- if (dlg != null)
- {
- for (int i = 0; i < dlg.WaterMetersCount; i++)
- {
- wmStartState[i] = dlg.WMStartState[i];
- wmStartStateStr[i] = dlg.WMStartStateStr[i];
- }
- }
- }
- else if (modelessDlg is TestStartEndForm && currentOp == CurrentOp.ReadDatastream_EndStates)
- {
- /// Fixed start test - end
- TestStartEndForm dlg = (modelessDlg as TestStartEndForm);
- if (dlg != null)
- {
- for (int i = 0; i < dlg.WaterMetersCount; i++)
- {
- wmEndState[i] = dlg.WMEndState[i];
- }
- }
- }
+ StoreAcceptedDialogValues((TestStartEndForm)modelessDlg);
+ }
resultSaved = true;
modelessDlg = null;
modelessDialogOpening = false;
@@ -459,6 +444,32 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
return Event.ModelessFormClosed; /// Form closed
}
+ private void StoreAcceptedDialogValues(TestStartEndForm dlg)
+ {
+ if (dlg == null)
+ return;
+
+ if (currentOp == CurrentOp.ReadDatastream_StartStates)
+ {
+ for (int i = 0; i < dlg.WaterMetersCount; i++)
+ {
+ wmStartState[i] = dlg.WMStartState[i];
+ wmStartStateStr[i] = dlg.WMStartStateStr[i];
+ log.InfoFormat("Poseidon dialog accepted: phase=Start, WM{0}, text='{1}', value={2}",
+ i + 1, wmStartStateStr[i], wmStartState[i]);
+ }
+ }
+ else if (currentOp == CurrentOp.ReadDatastream_EndStates)
+ {
+ for (int i = 0; i < dlg.WaterMetersCount; i++)
+ {
+ wmEndState[i] = dlg.WMEndState[i];
+ log.InfoFormat("Poseidon dialog accepted: phase=End, WM{0}, value={1}",
+ i + 1, wmEndState[i]);
+ }
+ }
+ }
+
/// Stop this operation
public void Stop()
{
diff --git a/TBF/Rig/DataEntry/PoseidonCmd/TestStartEndForm.cs b/TBF/Rig/DataEntry/PoseidonCmd/TestStartEndForm.cs
index 835cb7537..fbe10e4c6 100644
--- a/TBF/Rig/DataEntry/PoseidonCmd/TestStartEndForm.cs
+++ b/TBF/Rig/DataEntry/PoseidonCmd/TestStartEndForm.cs
@@ -147,6 +147,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
private void SetUiBusy(bool busy, long deltaTime = -1)
{
+ log.DebugFormat("Poseidon UI: SetUiBusy busy={0}, deltaTime={1}, enabledTextBoxes={2}",
+ busy, deltaTime, enabledTextBoxes.Count);
// show wait cursor for form and children
this.UseWaitCursor = busy;
@@ -296,6 +298,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
BeginInvoke(new Action(() => UpdateValues(stratValue, enableEdit)));
return;
}
+ log.DebugFormat("Poseidon UI: UpdateValues on UI thread startValue={0}, enableEdit={1}, deltaTime={2}",
+ stratValue, enableEdit, deltaTime);
for (int i = 0; i < TextBoxesCount; i++)
{
@@ -320,6 +324,7 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
if (enableEdit)
{
SetUiBusy(false, deltaTime);
+ log.Debug("Poseidon UI: values applied and dialog released from Loading...");
}
// if you also need to enable/disable editing, do it here,
// it's now safely on the UI thread.
@@ -348,6 +353,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
if (endTextBoxes[i].Visible && endTextBoxes[i].Enabled)
{
WMEndState[i] = Utils.ParseUDouble(endTextBoxes[i].Text);
+ log.InfoFormat("Poseidon dialog OK: phase=End, WM{0}, enteredText='{1}', parsedValue={2}",
+ i + 1, endTextBoxes[i].Text, WMEndState[i]);
}
}
}
@@ -359,6 +366,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
{
WMStartStateStr[i] = startTextBoxes[i].Text;
WMStartState[i] = Utils.ParseUDouble(startTextBoxes[i].Text);
+ log.InfoFormat("Poseidon dialog OK: phase=Start, WM{0}, enteredText='{1}', parsedValue={2}",
+ i + 1, startTextBoxes[i].Text, WMStartState[i]);
}
}
}
diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs
index 0e3e34a84..d889ee351 100644
--- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs
+++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs
@@ -25,6 +25,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
public class PoseidonReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, ICommonRegReader
{
private static readonly ILog log = LogManager.GetLogger(typeof(PoseidonReader));
+ // Simulation must never call the CLI configured for the physical Hat.
+ internal const string SimulatedCliFileName = "cmdSleepTest.exe";
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
readonly PoseidonCfg registerReaderCfg;
@@ -302,9 +304,17 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
registerReaderCfg.MeterType,
SerialPortData.DefaultPoseidonMeterType);
}
- serialPort = new SerialPortData(string.Format("COM{0}", registerReaderCfg.ComPortNr),
- registerReaderCfg.CliFileName,
- registerReaderCfg.MeterType);
+ string cliFileName = GetCliFileNameForMode(DebugLevel, registerReaderCfg.CliFileName);
+ serialPort = new SerialPortData(string.Format("COM{0}", registerReaderCfg.ComPortNr),
+ cliFileName,
+ registerReaderCfg.MeterType);
+
+ if (DebugLevel == DebugMode.Simulate)
+ {
+ log.WarnFormat(
+ "{0}: simulation mode enabled; overriding configured CLI '{1}' with '{2}'.",
+ Name, registerReaderCfg.CliFileName, serialPort.SerialPortCmdClientPath);
+ }
//TODO BUMI prepare serial port - for us do nothing
@@ -327,6 +337,13 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
}
}
+ internal static string GetCliFileNameForMode(DebugMode debugMode, string configuredCliFileName)
+ {
+ return debugMode == DebugMode.Simulate
+ ? SimulatedCliFileName
+ : configuredCliFileName;
+ }
+
public void Clear()
{
log.DebugFormat("{0}:Clear()", Name);
@@ -518,12 +535,14 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
}
}
- double volume;
- if (TryParseCliReading(data.Reading, out volume))
+ double volumeLi;
+ string dialogValueFailureReason;
+ if (TryGetDialogValue(data, out volumeLi, out dialogValueFailureReason))
{
lastCliReadingParsed = true;
lastCliReadSucceeded = true;
- double volumeLi = Units.ConvertFrom(Unit.USgal, volume);
+ double volume;
+ TryParseCliReading(data.Reading, out volume);
if (_isReadingStart)
beginWMState = volumeLi;
@@ -533,7 +552,11 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
Name, _isReadingStart ? "Begin" : "End", data.DeviceId, data.Reading, volume, volumeLi, beginWMState, endWMState);
}
else
- lastCliReadFailureReason = "Reading could not be parsed: '" + data.Reading + "'.";
+ {
+ lastCliReadFailureReason = dialogValueFailureReason;
+ log.ErrorFormat("{0}: cannot parse CLI reading '{1}' using invariant or current culture.",
+ Name, data.Reading);
+ }
}
}
@@ -554,6 +577,29 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
CultureInfo.InvariantCulture, out value);
}
+ ///
+ /// Validates a CLI response and converts its US-gallon reading to the
+ /// litre value assigned to the START/END dialog.
+ ///
+ public static bool TryGetDialogValue(JsonDataFromPoseidon data, out double valueLitres,
+ out string failureReason)
+ {
+ valueLitres = 0;
+ if (!TryValidateCliReadResponse(data, out failureReason))
+ return false;
+
+ double valueGallons;
+ if (!TryParseCliReading(data.Reading, out valueGallons))
+ {
+ failureReason = "Reading could not be parsed: '" + data.Reading + "'.";
+ return false;
+ }
+
+ valueLitres = Units.ConvertFrom(Unit.USgal, valueGallons);
+ failureReason = null;
+ return true;
+ }
+
public static bool TryValidateCliReadResponse(JsonDataFromPoseidon data, out string failureReason)
{
if (data == null) { failureReason = "CLI returned no JSON data."; return false; }
diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonCustomerCliResponseTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonCustomerCliResponseTest.cs
new file mode 100644
index 000000000..71f5c6e30
--- /dev/null
+++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonCustomerCliResponseTest.cs
@@ -0,0 +1,54 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
+using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
+
+namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
+{
+ ///
+ /// Regression fixtures extracted from PT50 customer CliRunner responses.
+ ///
+ [TestClass]
+ public class PoseidonCustomerCliResponseTest
+ {
+ // Independently calculated fixtures can differ by insignificant
+ // IEEE-754 rounding during gallons-to-litres conversion.
+ private const double DialogValueToleranceLitres = 0.0001;
+
+ private const string Com43InitialResponse =
+ "{\"Reading\":\"002780.99\",\"DeviceId\":\"1000000219\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
+ private const string Com45InitialResponse =
+ "{\"Reading\":\"002316.63\",\"DeviceId\":\"1000000279\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
+ private const string Com43LaterResponse =
+ "{\"Reading\":\"002898.60\",\"DeviceId\":\"1000000219\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
+ private const string Com45LaterResponse =
+ "{\"Reading\":\"002433.50\",\"DeviceId\":\"1000000279\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
+
+ [TestMethod]
+ public void CustomerCliResponses_AreDeserializedAndConvertedToDialogValues()
+ {
+ AssertDialogValue(Com43InitialResponse, "1000000219", "002780.99", 10527.1923171862);
+ AssertDialogValue(Com45InitialResponse, "1000000279", "002316.63", 8769.39850116792);
+ AssertDialogValue(Com43LaterResponse, "1000000219", "002898.60", 10972.3945971024);
+ AssertDialogValue(Com45LaterResponse, "1000000279", "002433.50", 9211.799576364);
+ }
+
+ private static void AssertDialogValue(string json, string expectedDeviceId,
+ string expectedReading, double expectedLitres)
+ {
+ var cliRunner = new CliRunner(false);
+ JsonDataFromPoseidon response;
+
+ Assert.IsTrue(cliRunner.TryJsonStringDeserialize(json, out response));
+ Assert.IsNotNull(response);
+ Assert.AreEqual(expectedDeviceId, response.DeviceId);
+ Assert.AreEqual(expectedReading, response.Reading);
+
+ double dialogValue;
+ string failureReason;
+ Assert.IsTrue(CmdPoseidonReader.TryGetDialogValue(response, out dialogValue, out failureReason), failureReason);
+ Assert.AreEqual(expectedLitres, dialogValue, DialogValueToleranceLitres,
+ "The value assigned to the START/END dialog must come from the CLI response.");
+ Assert.AreNotEqual(0d, dialogValue);
+ }
+ }
+}
diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonDialogTransferTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonDialogTransferTest.cs
new file mode 100644
index 000000000..93ef2760e
--- /dev/null
+++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonDialogTransferTest.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Windows.Forms;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TBF.Rig.DataEntry.PoseidonCmd;
+using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
+using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
+
+namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
+{
+ [TestClass]
+ public class PoseidonDialogTransferTest
+ {
+ private const string CustomerCliResponse =
+ "{\"Reading\":\"002433.50\",\"DeviceId\":\"1000000279\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
+
+ [TestMethod]
+ public void CustomerCliValue_IsPrefilledConfirmedAndTransferred_ForStartAndEnd()
+ {
+ Exception failure = null;
+ var staThread = new Thread(() =>
+ {
+ try { RunDialogTransferScenario(); }
+ catch (Exception exception) { failure = exception; }
+ });
+ staThread.SetApartmentState(ApartmentState.STA);
+ staThread.Start();
+ staThread.Join(TimeSpan.FromSeconds(15));
+
+ Assert.IsFalse(staThread.IsAlive, "The Poseidon dialog test did not finish.");
+ if (failure != null) throw failure;
+ }
+
+ private static void RunDialogTransferScenario()
+ {
+ var cliRunner = new CliRunner(false);
+ JsonDataFromPoseidon response;
+ Assert.IsTrue(cliRunner.TryJsonStringDeserialize(CustomerCliResponse, out response));
+
+ double cliValueLitres;
+ string failureReason;
+ Assert.IsTrue(CmdPoseidonReader.TryGetDialogValue(response, out cliValueLitres, out failureReason), failureReason);
+ VerifyStartDialogTransfer(cliValueLitres);
+ VerifyEndDialogTransfer(cliValueLitres);
+ }
+
+ private static void VerifyStartDialogTransfer(double expectedValue)
+ {
+ using (var dialog = new TestStartEndForm(1, null, new bool[1]))
+ {
+ dialog.CreateControl();
+ dialog.WMStartState[0] = expectedValue;
+ dialog.WMStartStateStr[0] = expectedValue.ToString();
+ dialog.UpdateValues(true, true);
+ Assert.AreEqual(dialog.WMStartStateStr[0], FindTextBox(dialog, "startTextBox1").Text);
+
+ ConfirmDialog(dialog);
+ Assert.IsTrue(dialog.Completed);
+ Assert.AreEqual(expectedValue, dialog.WMStartState[0], 0.0001);
+
+ var entryForm = CreateEntryFormForTransfer(EntryForm.CurrentOp.ReadDatastream_StartStates);
+ TransferAcceptedDialogValues(entryForm, dialog);
+ Assert.AreEqual(expectedValue, entryForm.WMStartState(0), 0.0001);
+ }
+ }
+
+ private static void VerifyEndDialogTransfer(double expectedValue)
+ {
+ using (var dialog = new TestStartEndForm(1, null, new[] { expectedValue.ToString() }, new bool[1], 0, 0, 0))
+ {
+ dialog.CreateControl();
+ dialog.WMEndState[0] = expectedValue;
+ dialog.UpdateValues(false, true);
+ Assert.AreEqual(expectedValue.ToString(), FindTextBox(dialog, "endTextBox1").Text);
+
+ ConfirmDialog(dialog);
+ Assert.IsTrue(dialog.Completed);
+ Assert.AreEqual(expectedValue, dialog.WMEndState[0], 0.0001);
+
+ var entryForm = CreateEntryFormForTransfer(EntryForm.CurrentOp.ReadDatastream_EndStates);
+ TransferAcceptedDialogValues(entryForm, dialog);
+ Assert.AreEqual(expectedValue, entryForm.WMEndState(0), 0.0001);
+ }
+ }
+
+ private static TextBox FindTextBox(Control dialog, string name)
+ {
+ TextBox textBox = dialog.Controls.Find(name, true).OfType().FirstOrDefault();
+ Assert.IsNotNull(textBox, "Expected dialog control was not found: " + name);
+ return textBox;
+ }
+
+ private static void ConfirmDialog(TestStartEndForm dialog)
+ {
+ MethodInfo okHandler = typeof(TestStartEndForm).GetMethod("okButton_Click",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.IsNotNull(okHandler);
+ okHandler.Invoke(dialog, new object[] { dialog, EventArgs.Empty });
+ }
+
+ private static EntryForm CreateEntryFormForTransfer(EntryForm.CurrentOp currentOp)
+ {
+ var entryForm = new EntryForm();
+ SetPrivateField(entryForm, "currentOp", currentOp);
+ SetPrivateField(entryForm, "wmStartState", new double[1]);
+ SetPrivateField(entryForm, "wmStartStateStr", new string[1]);
+ SetPrivateField(entryForm, "wmEndState", new double[1]);
+ return entryForm;
+ }
+
+ private static void TransferAcceptedDialogValues(EntryForm entryForm, TestStartEndForm dialog)
+ {
+ MethodInfo method = typeof(EntryForm).GetMethod("StoreAcceptedDialogValues",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.IsNotNull(method);
+ method.Invoke(entryForm, new object[] { dialog });
+ }
+
+ private static void SetPrivateField(object instance, string name, object value)
+ {
+ FieldInfo field = instance.GetType().GetField(name,
+ BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.IsNotNull(field, "Expected field was not found: " + name);
+ field.SetValue(instance, value);
+ }
+ }
+}
diff --git a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycleTest.cs b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycleTest.cs
index c228200ee..89567b91d 100644
--- a/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycleTest.cs
+++ b/TBFTests/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReadCycleTest.cs
@@ -82,6 +82,26 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
}
}
+ [TestMethod]
+ public void InputJson_NonZeroReading_WithDecimalCommaOrDot_IsParsedCorrectly()
+ {
+ foreach (string reading in new[] { "002433,50", "002433.50", "002898,60", "002898.60" })
+ {
+ double value;
+ Assert.IsTrue(CmdPoseidonReader.TryParseCliReading(reading, out value));
+ Assert.IsTrue(value > 0, "The non-zero CLI reading must remain non-zero: " + reading);
+ }
+ }
+
+ [TestMethod]
+ public void SimulationMode_AlwaysUsesCmdSleepTestInsteadOfConfiguredHatCli()
+ {
+ Assert.AreEqual("cmdSleepTest.exe", CmdPoseidonReader.GetCliFileNameForMode(
+ Common.DebugMode.Simulate, "HatCliDemo.exe"));
+ Assert.AreEqual("HatCliDemo.exe", CmdPoseidonReader.GetCliFileNameForMode(
+ Common.DebugMode.Normal, "HatCliDemo.exe"));
+ }
+
[TestMethod]
public void CliReadResponse_RequiresNfcAndCompletedReading()
{
diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj
index 8c2f17db1..7dd3ba129 100644
--- a/TBFTests/TBFTests.csproj
+++ b/TBFTests/TBFTests.csproj
@@ -136,6 +136,8 @@
+
+