Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
302a7a682c | ||
|
|
e4b42b5b51 | ||
|
|
234bd39ed5 | ||
|
|
59cd5f9dde | ||
|
|
2fd2697e3b | ||
|
|
f51fd4709e | ||
|
|
d5c50189ba | ||
|
|
5a1db14e2a | ||
|
|
5ea88265b9 | ||
|
|
2b9a207342 | ||
|
|
c4b61d885b |
@@ -6,6 +6,10 @@
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<WarningLevel>0</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
|
||||
+6
-1
@@ -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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using Common;
|
||||
using log4net;
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace Results.Entities.helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies explicit, backwards-compatible results DB migrations before the
|
||||
/// first NHibernate session factory is created.
|
||||
/// </summary>
|
||||
public static class DatabaseMigrationHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(DatabaseMigrationHelper));
|
||||
|
||||
public static void EnsureSchema(DBType dbType, string connectionString)
|
||||
{
|
||||
switch (dbType)
|
||||
{
|
||||
case DBType.MySql:
|
||||
EnsureMySqlSchema(connectionString);
|
||||
break;
|
||||
case DBType.SQLite:
|
||||
EnsureSQLiteSchema(connectionString);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureMySqlSchema(string connectionString)
|
||||
{
|
||||
using (var conn = new MySqlConnection(connectionString))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
// Added by newer MeterTestRslt mapping; older customer DBs do
|
||||
// not contain it and otherwise reject the entire batch insert.
|
||||
EnsureColumnMySql(conn, "MeterTestRslt", "PulsesPerKilogram", "DOUBLE NOT NULL DEFAULT 0");
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureColumnMySql(
|
||||
MySqlConnection conn,
|
||||
string tableName,
|
||||
string columnName,
|
||||
string columnDefinition)
|
||||
{
|
||||
using (var transaction = conn.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
bool exists;
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = transaction;
|
||||
cmd.CommandText = @"
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = @tableName
|
||||
AND COLUMN_NAME = @columnName";
|
||||
cmd.Parameters.AddWithValue("@tableName", tableName);
|
||||
cmd.Parameters.AddWithValue("@columnName", columnName);
|
||||
exists = Convert.ToInt32(cmd.ExecuteScalar()) > 0;
|
||||
}
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
using (var alter = conn.CreateCommand())
|
||||
{
|
||||
alter.Transaction = transaction;
|
||||
alter.CommandText = "ALTER TABLE `" + tableName + "` ADD COLUMN `" +
|
||||
columnName + "` " + columnDefinition;
|
||||
alter.ExecuteNonQuery();
|
||||
log.WarnFormat("Results DB migration: added {0}.{1} ({2}).",
|
||||
tableName, columnName, columnDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureSQLiteSchema(string databaseFile)
|
||||
{
|
||||
using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=" + databaseFile))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
EnsureColumnSQLite(conn, "MeterTestRslt", "PulsesPerKilogram", "REAL NOT NULL DEFAULT 0");
|
||||
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 EnsureColumnSQLite(
|
||||
System.Data.SQLite.SQLiteConnection conn,
|
||||
string tableName,
|
||||
string columnName,
|
||||
string columnDefinition)
|
||||
{
|
||||
bool exists = false;
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "PRAGMA table_info(" + tableName + ")";
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
if (string.Equals(reader["name"].ToString(), columnName,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
using (var alter = conn.CreateCommand())
|
||||
{
|
||||
alter.CommandText = "ALTER TABLE " + tableName + " ADD COLUMN " +
|
||||
columnName + " " + columnDefinition;
|
||||
alter.ExecuteNonQuery();
|
||||
log.WarnFormat("Results DB migration: added {0}.{1} ({2}).",
|
||||
tableName, columnName, columnDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -106,13 +106,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LabelPrinting", "LabelPrint
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TBFTests", "TBFTests\TBFTests.csproj", "{77EB589F-C670-4489-AAD6-2A3C02061FD1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppDiagnostic", "AppDiagnostic\AppDiagnostic.csproj", "{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3} = {8F942729-F454-4C99-BA6C-746962065AE3}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedComponents", "SharedComponents\SharedComponents.csproj", "{8F942729-F454-4C99-BA6C-746962065AE3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sensus.iPerl.RfidCom", "..\iPerlHead\Sensus.iPerl.RfidCom\Sensus.iPerl.RfidCom.csproj", "{A3E14180-7F00-42E5-94A9-8DB62EAD6EE8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sensus.iPerl.TestConsole", "..\iPerlHead\Sensus.iPerl.TestConsole\Sensus.iPerl.TestConsole.csproj", "{5025ED8B-A94F-4E58-8BE0-68481B061609}"
|
||||
@@ -121,8 +114,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NfcS5_DLL", "..\NfcS5_DLL\N
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NfcC7_DLL", "NfcC7_DLL\NfcC7_DLL.csproj", "{53E75979-B530-4805-8FC9-B314F14A62BE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NfcC7_DLL.Tests", "NfcC7_DLL.Tests\NfcC7_DLL.Tests.csproj", "{715605C5-568B-48D6-9C09-2DC5F2E81F66}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -483,30 +474,6 @@ Global
|
||||
{77EB589F-C670-4489-AAD6-2A3C02061FD1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{77EB589F-C670-4489-AAD6-2A3C02061FD1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{77EB589F-C670-4489-AAD6-2A3C02061FD1}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8F942729-F454-4C99-BA6C-746962065AE3}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A3E14180-7F00-42E5-94A9-8DB62EAD6EE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A3E14180-7F00-42E5-94A9-8DB62EAD6EE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A3E14180-7F00-42E5-94A9-8DB62EAD6EE8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
@@ -555,18 +522,6 @@ Global
|
||||
{53E75979-B530-4805-8FC9-B314F14A62BE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{53E75979-B530-4805-8FC9-B314F14A62BE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{53E75979-B530-4805-8FC9-B314F14A62BE}.Release|x86.Build.0 = Release|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{715605C5-568B-48D6-9C09-2DC5F2E81F66}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -19,6 +19,9 @@ using System.Runtime.InteropServices;
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
//Visibility to TBFTests internal classes
|
||||
[assembly: InternalsVisibleTo("TBFTests")]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("2c13538e-15ee-48b2-af0f-2c95afb67186")]
|
||||
|
||||
@@ -29,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion("3.9.2149.4")]
|
||||
[assembly: AssemblyFileVersion("3.9.2149.4")]
|
||||
[assembly: AssemblyVersion("3.9.2206.0")]
|
||||
[assembly: AssemblyFileVersion("3.9.2206.0")]
|
||||
|
||||
@@ -3,3 +3,13 @@
|
||||
| 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. |
|
||||
| 3.9.2206.0 | Morrisville / Poseidon CLI / Results DB | Poseidon start/end rearm and simulation isolation | Fixed the Poseidon start/end read cycle so a completed START reader is armed again once for END, preventing reused START values or skipped END CLI calls. Added trace logging for CLI response, dialog prefill and confirmed dialog values. In `Simulate` mode, `PoseidonCmdStartStop` now always runs the fixed `C:\TBF\Cli\cmdSleepTest.exe` instead of the configured physical Hat CLI. Added regression tests for customer CLI JSON responses, decimal comma/dot and non-zero readings, start/end dialog transfer, full/reduced reader cycles and simulation CLI selection. Extended results-schema migration with `MeterTestRslt.PulsesPerKilogram`. |
|
||||
|
||||
@@ -17,8 +17,7 @@ using TBF.Boxes;
|
||||
using TBF.Rig.GenericDevices;
|
||||
using TBF.Rig.Sequences;
|
||||
using TBF.UiBridge;
|
||||
using AppDiagnostic;
|
||||
using SharedComponents;
|
||||
|
||||
|
||||
namespace TBF.Rig.ControlBoard.Uni
|
||||
{
|
||||
|
||||
@@ -268,10 +268,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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,31 +332,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;
|
||||
}
|
||||
@@ -358,6 +343,36 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
return Event.ModelessFormClosed; /// Form closed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies values confirmed by the Poseidon start/end dialog into the
|
||||
/// data-entry state used later by the result calculation.
|
||||
/// </summary>
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Stop this operation</summary>
|
||||
public void Stop()
|
||||
{
|
||||
@@ -433,35 +448,45 @@ 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var phaseRunner = new PoseidonReadPhaseRunner(
|
||||
currentOp == CurrentOp.ReadDatastream_StartStates);
|
||||
while (!finishedReading) //TODO BUMI lock - fuck ?
|
||||
{
|
||||
bool bAllReadersFinished = true;
|
||||
foreach (var iRegReader in regReaders )
|
||||
foreach (IPoseidonReadOperation poseidonReader in poseidonReaders)
|
||||
{
|
||||
if (iRegReader is PoseidonReader)
|
||||
if (poseidonReader.IsNotStarted || poseidonReader.IsFinished)
|
||||
log.DebugFormat("Poseidon read: arming {0} for {1}, previous state finished={2}",
|
||||
currentOp, poseidonReader.Name, poseidonReader.IsFinished);
|
||||
}
|
||||
|
||||
bool bAllReadersFinished = phaseRunner.RunIteration(poseidonReaders);
|
||||
foreach (IPoseidonReadOperation poseidonReader in poseidonReaders)
|
||||
{
|
||||
if (poseidonReader.IsFinished)
|
||||
{
|
||||
PoseidonReader poseidonReader = (iRegReader as PoseidonReader);
|
||||
if(poseidonReader == null)
|
||||
continue;
|
||||
poseidonReader.SetCliLogging(CliLogging);
|
||||
if (!(poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.ReadDatastream_Done
|
||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.ReadDatastream_Running))
|
||||
{
|
||||
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.Done
|
||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error))
|
||||
{
|
||||
bAllReadersFinished = false;
|
||||
}
|
||||
if (poseidonReader.HasError)
|
||||
log.ErrorFormat("Poseidon read: {0} completed with Error during {1}", poseidonReader.Name, currentOp);
|
||||
else
|
||||
log.DebugFormat("Poseidon read: {0} completed during {1}", poseidonReader.Name, currentOp);
|
||||
}
|
||||
}
|
||||
if (bAllReadersFinished)
|
||||
{
|
||||
log.DebugFormat("Poseidon read: all readers finished for {0}", currentOp);
|
||||
finishedReading = true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -144,6 +144,9 @@ 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;
|
||||
|
||||
@@ -290,10 +293,15 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
// Ensure we are on the UI thread
|
||||
if (InvokeRequired)
|
||||
{
|
||||
log.DebugFormat("Poseidon UI: queue UpdateValues startValue={0}, enableEdit={1}, deltaTime={2}",
|
||||
stratValue, enableEdit, deltaTime);
|
||||
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++)
|
||||
{
|
||||
if (stratValue)
|
||||
@@ -317,6 +325,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.
|
||||
@@ -345,6 +354,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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,6 +367,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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,46 +13,29 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public class CliRunner
|
||||
{
|
||||
//static readonly ILog log = LogManager.GetLogger(typeof(CliRunner));
|
||||
private readonly ILog log;
|
||||
private List<Task> taskPool = new List<Task>();
|
||||
private long startTime;
|
||||
private long incommingTime;
|
||||
|
||||
public List<Task> TaskPool { get { return taskPool; } }
|
||||
public void AddTask(Task task) { taskPool.Add(task); }
|
||||
public void WaitAll() { Task.WaitAll(taskPool.ToArray()); }
|
||||
public void Clear() { taskPool.Clear(); }
|
||||
public void CancelAll() { Task.WhenAll(taskPool).ContinueWith(t => { }); }
|
||||
private readonly List<CliTaskInfo> taskPool = new List<CliTaskInfo>();
|
||||
private long startTimeMs;
|
||||
private long incommingTimeMs;
|
||||
|
||||
public void StartAll()
|
||||
public List<CliTaskInfo> TaskPool
|
||||
{
|
||||
taskPool.ForEach(t => t.Start());
|
||||
}
|
||||
|
||||
public bool AreTasksDone()
|
||||
{
|
||||
return taskPool.All(task => task.IsCompleted);
|
||||
get { return taskPool; }
|
||||
}
|
||||
|
||||
public long StartTime
|
||||
{
|
||||
get => startTime;
|
||||
get { return startTimeMs; }
|
||||
}
|
||||
|
||||
public long IncommingTime
|
||||
{
|
||||
get => incommingTime;
|
||||
get { return incommingTimeMs; }
|
||||
}
|
||||
|
||||
public bool TimeOutReceived(long timeout)
|
||||
{
|
||||
return (DateTime.Now.Ticks - startTime) > timeout;
|
||||
}
|
||||
|
||||
public CliRunner(bool isCliLogging)
|
||||
{
|
||||
startTime = DateTime.Now.Ticks;
|
||||
ResetStartTime();
|
||||
|
||||
if (isCliLogging)
|
||||
{
|
||||
@@ -60,173 +43,432 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
log = new ScopedLoggerFactory().CreateLogger<CliRunner>(
|
||||
@"C:\TBF\Logs\CliRunner.txt",
|
||||
10, // maxFileSizeMB
|
||||
7, // maxBackups
|
||||
10,
|
||||
7,
|
||||
log4net.Core.Level.Debug,
|
||||
true, // zipRolledFiles
|
||||
true, // singleZipPerDay
|
||||
TimeSpan.FromMinutes(2) // zipScanInterval
|
||||
true,
|
||||
true,
|
||||
TimeSpan.FromMinutes(2)
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Fallback to console or handle gracefully
|
||||
Console.WriteLine($"Failed to initialize logger: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public void AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg)
|
||||
public void ResetStartTime()
|
||||
{
|
||||
var task = SendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
|
||||
taskPool.Add(task);
|
||||
}
|
||||
|
||||
public void AddSendAsync(string fileName, string args)
|
||||
{
|
||||
var task = SendAsync(fileName, args);
|
||||
taskPool.Add(task);
|
||||
}
|
||||
|
||||
|
||||
public async Task<string> SendAsync(string fileName, string args, CancellationToken ct = default)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
|
||||
process.Start();
|
||||
|
||||
Task<string> stdOutTask = process.StandardOutput.ReadToEndAsync();
|
||||
Task<string> stdErrTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(stdOutTask, stdErrTask, process.WaitForExitAsync(ct));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
//comming answer from serial port - good place for time stamp
|
||||
incommingTime = DateTime.Now.Ticks;
|
||||
string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
|
||||
log?.Debug(allOutput);
|
||||
return allOutput;
|
||||
startTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
|
||||
public void AddRunAndCaptureJsonAsync<T>(SerialPortData data, SerialPortData.EMeterArg eMeterArg) where T : new()
|
||||
public void Clear()
|
||||
{
|
||||
var task = RunAndCaptureJsonAsync<T>(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
|
||||
taskPool.Add(task);
|
||||
}
|
||||
|
||||
public async Task<T> RunAndCaptureJsonAsync<T>(string fileName, string args, CancellationToken ct = default) where T : new()
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
|
||||
process.Start();
|
||||
|
||||
var stdoutTask = process.StandardOutput.ReadToEndAsync();
|
||||
var stderrTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
await Task.WhenAll(stdoutTask, stderrTask, process.WaitForExitAsync(ct));
|
||||
|
||||
string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
|
||||
log?.Debug(allOutput);
|
||||
|
||||
string json = ExtractJson(allOutput);
|
||||
if (TryJsonStringDeserialize(json, out T result)) return result;
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool TryJsonStringDeserialize<T>(string json, out T runAndCaptureJsonAsync) where T : new()
|
||||
{
|
||||
if (json != null)
|
||||
foreach (var item in taskPool)
|
||||
{
|
||||
try
|
||||
{
|
||||
runAndCaptureJsonAsync = JsonConvert.DeserializeObject<T>(json);
|
||||
return true;
|
||||
item?.Cts?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (item?.Process != null)
|
||||
{
|
||||
item.Process.Dispose();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
taskPool.Clear();
|
||||
}
|
||||
|
||||
public void WaitAll()
|
||||
{
|
||||
var tasks = taskPool
|
||||
.Where(t => t != null && t.Task != null)
|
||||
.Select(t => t.Task)
|
||||
.ToArray();
|
||||
|
||||
if (tasks.Length == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
catch (AggregateException)
|
||||
{
|
||||
RefreshTaskStates();
|
||||
}
|
||||
}
|
||||
|
||||
public bool AreTasksDone()
|
||||
{
|
||||
RefreshTaskStates();
|
||||
return taskPool.Count > 0 && taskPool.All(t => t.State != CliTaskState.Running);
|
||||
}
|
||||
|
||||
public bool TimeOutReceived(long timeoutMs)
|
||||
{
|
||||
long nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
return (nowMs - startTimeMs) > timeoutMs;
|
||||
}
|
||||
|
||||
public void RefreshTaskStates()
|
||||
{
|
||||
foreach (var item in taskPool)
|
||||
{
|
||||
if (item == null || item.Task == null)
|
||||
continue;
|
||||
|
||||
if (item.State == CliTaskState.TimedOut)
|
||||
continue;
|
||||
|
||||
if (!item.Task.IsCompleted)
|
||||
{
|
||||
item.State = CliTaskState.Running;
|
||||
}
|
||||
else if (item.Task.IsCanceled)
|
||||
{
|
||||
item.State = CliTaskState.Canceled;
|
||||
}
|
||||
else if (item.Task.IsFaulted)
|
||||
{
|
||||
item.State = CliTaskState.Faulted;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.State = CliTaskState.Completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelUndoneTasksAsTimedOut()
|
||||
{
|
||||
foreach (var item in taskPool)
|
||||
{
|
||||
if (item == null || item.Task == null)
|
||||
continue;
|
||||
|
||||
if (item.Task.IsCompleted)
|
||||
{
|
||||
if (item.Task.IsCanceled)
|
||||
item.State = CliTaskState.Canceled;
|
||||
else if (item.Task.IsFaulted)
|
||||
item.State = CliTaskState.Faulted;
|
||||
else
|
||||
item.State = CliTaskState.Completed;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
item.State = CliTaskState.TimedOut;
|
||||
|
||||
try
|
||||
{
|
||||
item.Cts?.Cancel();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Debug(ex.Message);
|
||||
runAndCaptureJsonAsync = TryConvert<T>(json);
|
||||
return true;
|
||||
log?.Warn("Cancel token failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
runAndCaptureJsonAsync = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static T TryConvert<T>(string json) where T : new()
|
||||
{
|
||||
|
||||
T obj = new T();
|
||||
|
||||
try
|
||||
{
|
||||
JObject jObject = JObject.Parse(json);
|
||||
|
||||
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
if (item.Process != null && !item.Process.HasExited)
|
||||
{
|
||||
if (!prop.CanWrite) continue;
|
||||
|
||||
JToken token;
|
||||
if (jObject.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out token))
|
||||
{
|
||||
try
|
||||
{
|
||||
object value = token.ToObject(prop.PropertyType);
|
||||
prop.SetValue(obj, value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// leave default if conversion fails
|
||||
}
|
||||
}
|
||||
// else → keep default value
|
||||
item.Process.Kill();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"TryConvert failed: {ex.Message}");
|
||||
log?.Warn("Kill process failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg)
|
||||
{
|
||||
AddSendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettings(eMeterArg));
|
||||
}
|
||||
|
||||
public void AddSendAsync(string fileName, string args)
|
||||
{
|
||||
ResetStartTime();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var info = new CliTaskInfo
|
||||
{
|
||||
Cts = cts,
|
||||
Name = "SendAsync"
|
||||
};
|
||||
|
||||
var task = SendAsync(fileName, args, info, cts.Token);
|
||||
info.Task = task;
|
||||
taskPool.Add(info);
|
||||
}
|
||||
|
||||
public async Task<string> SendAsync(string fileName, string args, CliTaskInfo info, CancellationToken ct = default)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
WorkingDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName)),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true })
|
||||
{
|
||||
info.Process = process;
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
|
||||
Task<string> stdOutTask = process.StandardOutput.ReadToEndAsync();
|
||||
Task<string> stdErrTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(stdOutTask, stdErrTask, process.WaitForExitAsync(ct));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (info.State != CliTaskState.TimedOut)
|
||||
info.State = CliTaskState.Canceled;
|
||||
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
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 exit code {info.ExitCode}.";
|
||||
log?.Error($"{info.Name}: {info.FailureReason} stderr='{info.StandardError}'");
|
||||
}
|
||||
|
||||
info.State = CliTaskState.Completed;
|
||||
return allOutput;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
info.State = CliTaskState.Faulted;
|
||||
log?.Error("SendAsync failed", ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
info.Process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRunAndCaptureJsonAsync<T>(SerialPortData data, SerialPortData.EMeterArg eMeterArg) where T : new()
|
||||
{
|
||||
AddRunAndCaptureJsonAsync<T>(
|
||||
data.SerialPortCmdClientPath,
|
||||
data.DefaultArgSettings(eMeterArg));
|
||||
}
|
||||
|
||||
public void AddRunAndCaptureJsonAsync<T>(string fileName, string args) where T : new()
|
||||
{
|
||||
// Tests deliberately create CliRunner without a logger. Starting a CLI task
|
||||
// must not depend on diagnostics being configured.
|
||||
log?.Debug($"CLI: {fileName}");
|
||||
log?.Debug($"ARGS: {args}");
|
||||
|
||||
ResetStartTime();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var info = new CliTaskInfo
|
||||
{
|
||||
Cts = cts,
|
||||
Name = $"RunAndCaptureJsonAsync<{typeof(T).Name}>"
|
||||
};
|
||||
|
||||
var task = RunAndCaptureJsonAsync<T>(fileName, args, info, cts.Token);
|
||||
|
||||
info.Task = task;
|
||||
taskPool.Add(info);
|
||||
}
|
||||
|
||||
public async Task<T> RunAndCaptureJsonAsync<T>(string fileName, string args, CliTaskInfo info, CancellationToken ct = default) where T : new()
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
WorkingDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName)),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true })
|
||||
{
|
||||
info.Process = process;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
log?.Debug($"FileName = '{psi.FileName}'");
|
||||
log?.Debug($"Arguments = '{psi.Arguments}'");
|
||||
log?.Debug($"WorkingDirectory = '{psi.WorkingDirectory}'");
|
||||
log?.Debug($"Exists = {System.IO.File.Exists(psi.FileName)}");
|
||||
|
||||
process.Start();
|
||||
|
||||
var stdoutTask = process.StandardOutput.ReadToEndAsync();
|
||||
var stderrTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(stdoutTask, stderrTask, process.WaitForExitAsync(ct));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (info.State != CliTaskState.TimedOut)
|
||||
info.State = CliTaskState.Canceled;
|
||||
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
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 exit 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))
|
||||
{
|
||||
info.State = CliTaskState.Completed;
|
||||
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);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
info.State = CliTaskState.Faulted;
|
||||
log?.Error("RunAndCaptureJsonAsync failed", ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
info.Process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryJsonStringDeserialize<T>(string json, out T value) where T : new()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
try
|
||||
{
|
||||
value = JsonConvert.DeserializeObject<T>(json);
|
||||
return value != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Debug(ex.Message);
|
||||
return TryConvert(json, out value);
|
||||
}
|
||||
}
|
||||
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryConvert<T>(string json, out T value) where T : new()
|
||||
{
|
||||
T obj = new T();
|
||||
|
||||
try
|
||||
{
|
||||
JObject jObject = JObject.Parse(json);
|
||||
|
||||
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (!prop.CanWrite)
|
||||
continue;
|
||||
|
||||
JToken token;
|
||||
if (jObject.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out token))
|
||||
{
|
||||
try
|
||||
{
|
||||
object propertyValue = token.ToObject(prop.PropertyType);
|
||||
prop.SetValue(obj, propertyValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
value = obj;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"TryConvert failed: {ex.Message}");
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string ExtractJson(string text)
|
||||
@@ -241,6 +483,20 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void AddTaskForTest(Task task, string name = "TestTask", CancellationTokenSource cts = null)
|
||||
{
|
||||
taskPool.Add(new CliTaskInfo
|
||||
{
|
||||
Task = task,
|
||||
Cts = cts,
|
||||
Name = name,
|
||||
State = task.IsCompleted
|
||||
? (task.IsCanceled ? CliTaskState.Canceled :
|
||||
task.IsFaulted ? CliTaskState.Faulted :
|
||||
CliTaskState.Completed)
|
||||
: CliTaskState.Running
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public class CliTaskInfo
|
||||
{
|
||||
public Task Task { get; set; }
|
||||
public CancellationTokenSource Cts { get; set; }
|
||||
public Process Process { get; set; }
|
||||
public CliTaskState State { get; set; } = CliTaskState.Running;
|
||||
public string Name { get; set; }
|
||||
public int? ExitCode { get; set; }
|
||||
public string StandardOutput { get; set; }
|
||||
public string StandardError { get; set; }
|
||||
public string FailureReason { get; set; }
|
||||
|
||||
public bool UseResult
|
||||
{
|
||||
get
|
||||
{
|
||||
return State == CliTaskState.Completed
|
||||
&& Task != null
|
||||
&& Task.Status == TaskStatus.RanToCompletion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public enum CliTaskState
|
||||
{
|
||||
Running,
|
||||
Completed,
|
||||
TimedOut,
|
||||
Canceled,
|
||||
Faulted
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public static class GetDescription
|
||||
{
|
||||
public static string ToDescription(this Enum en)
|
||||
{
|
||||
Type type = en.GetType();
|
||||
MemberInfo[] memInfo = type.GetMember(en.ToString());
|
||||
|
||||
if (memInfo != null && memInfo.Length > 0)
|
||||
{
|
||||
object[] attrs = memInfo[0].GetCustomAttributes(
|
||||
typeof(DescriptionAttribute),
|
||||
false);
|
||||
|
||||
if (attrs != null && attrs.Length > 0)
|
||||
return ((DescriptionAttribute)attrs[0]).Description;
|
||||
}
|
||||
|
||||
return en.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,53 @@
|
||||
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public class JsonDataFromPoseidon
|
||||
{
|
||||
public bool? NfcTagDetected { get; set; }
|
||||
public int? ProductType { get; set; }
|
||||
public string ProductTypeVersion { get; set; }
|
||||
public bool NfcTagDetected { get; set; }
|
||||
public bool ReadingComplete { get; set; }
|
||||
|
||||
public string DeviceId { get; set; }
|
||||
public string ProductType { get; set; }
|
||||
public string ProductTypeVersion { get; set; }
|
||||
public string CliVersion { get; set; }
|
||||
|
||||
public string Reading { get; set; }
|
||||
public string Reading_Totalizer { get; set; }
|
||||
public string Reading_Digits { get; set; }
|
||||
public string Reading_Shift { get; set; }
|
||||
public string Reading_Resolution { get; set; }
|
||||
|
||||
public object Reading_Totalizer { get; set; }
|
||||
public object Reading_FlowRate { get; set; }
|
||||
public object Reading_FlowDirection { get; set; }
|
||||
public object Reading_Digits { get; set; }
|
||||
public object Reading_Shift { get; set; }
|
||||
public object Reading_Resolution { get; set; }
|
||||
public string Reading_Units { get; set; }
|
||||
public string Reading_FlowDirection { get; set; }
|
||||
public string Reading_FlowRate { get; set; }
|
||||
|
||||
public string MeterState { get; set; }
|
||||
public string CalibrationFactor { get; set; }
|
||||
public bool? ReadingComplete { get; set; }
|
||||
public string OpticalDataMode { get; set; }
|
||||
|
||||
public string BuildInformation { get; set; }
|
||||
public string ManufactureDate { get; set; }
|
||||
public string MeterSize { get; set; }
|
||||
public string FactoryId { get; set; }
|
||||
public string CustomerText { get; set; }
|
||||
|
||||
public string TestTotalizer { get; set; }
|
||||
public string FlowUnits { get; set; }
|
||||
public string ReadingUnits { get; set; }
|
||||
|
||||
public string SpreadSpectrumParameters { get; set; }
|
||||
|
||||
public string SpreadSpect_DisableSpreadSpectrum { get; set; }
|
||||
public string SpreadSpect_AlwaysUseFullRateBags { get; set; }
|
||||
public string SpreadSpect_PowerUp { get; set; }
|
||||
public string SpreadSpect_HighNoise { get; set; }
|
||||
public string SpreadSpect_EmptyPipe { get; set; }
|
||||
public string SpreadSpect_BadAdc { get; set; }
|
||||
public string SpreadSpect_InitLearningCompleteCount { get; set; }
|
||||
public string SpreadSpect_MinOffsetLearningSampleCount { get; set; }
|
||||
public string SpreadSpect_AdcShiftUpdateInterval { get; set; }
|
||||
public string SpreadSpect_MaxPreviousAdcAge { get; set; }
|
||||
public string SpreadSpect_AdcOffsetLearningGuard { get; set; }
|
||||
public string SpreadSpect_DisplacementCorrectionCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
using System.ComponentModel;
|
||||
using System.Xml.Serialization;
|
||||
using Common;
|
||||
using TBF.Rig.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Common;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
@@ -22,7 +24,22 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
///
|
||||
public int ComPortNr;
|
||||
public string CliFileName;
|
||||
public int MeterType;
|
||||
|
||||
[XmlIgnore]
|
||||
public SerialPortData.MeterProduct MeterType = SerialPortData.MeterProduct.Poseidon;
|
||||
|
||||
[XmlElement("MeterProduct")]
|
||||
public string MeterProductXml
|
||||
{
|
||||
get { return SerialPortData.MeterToString(MeterType); }
|
||||
set { MeterType = SerialPortData.StringToMeter(value); }
|
||||
}
|
||||
|
||||
public SerialPortData.HatType HatType = SerialPortData.HatType.Mth;
|
||||
public int TimeOut = 30;
|
||||
public string MainMacro = SerialPortData.MacroTypes.readall.ToDescription();
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary> Procedure parameters </summary>
|
||||
@@ -31,14 +48,68 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
public override IParamsProvider GetRuntimeProcParamsProvider() { return ProcParams; }
|
||||
public override IParamsProvider CreateProcParamsProvider() { return new PoseidonProcParams(true); }
|
||||
|
||||
public static bool TryParseMeter(
|
||||
string description,
|
||||
out SerialPortData.MeterProduct meter)
|
||||
{
|
||||
var found = Enum.GetValues(typeof(SerialPortData.MeterProduct))
|
||||
.Cast<SerialPortData.MeterProduct>()
|
||||
.FirstOrDefault(e =>
|
||||
string.Equals(
|
||||
e.GetType()
|
||||
.GetField(e.ToString())
|
||||
.GetCustomAttribute<DescriptionAttribute>()
|
||||
?.Description,
|
||||
description,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!Equals(found, default(SerialPortData.MeterProduct)) ||
|
||||
string.Equals(description, "Poseidon", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
meter = found;
|
||||
return true;
|
||||
}
|
||||
|
||||
meter = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryParseMacro(
|
||||
string description,
|
||||
out string macro)
|
||||
{
|
||||
var found = Enum.GetValues(typeof(SerialPortData.MacroTypes))
|
||||
.Cast<SerialPortData.MacroTypes>()
|
||||
.FirstOrDefault(e =>
|
||||
string.Equals(
|
||||
e.GetType()
|
||||
.GetField(e.ToString())
|
||||
.GetCustomAttribute<DescriptionAttribute>()
|
||||
?.Description,
|
||||
description,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!Equals(found, default(SerialPortData.MacroTypes)) ||
|
||||
string.Equals(description, "readall", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
macro = found.ToDescription();
|
||||
return true;
|
||||
}
|
||||
|
||||
macro = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] paramNames = new string[]
|
||||
{
|
||||
"Serial port number", /// 0
|
||||
"CLI program name", /// 1
|
||||
"Meter Type", /// 2
|
||||
"Hat Type", /// 3
|
||||
"TimeOut [s]", /// 4
|
||||
"Main Macro" /// 5
|
||||
};
|
||||
|
||||
public string ParamName(int i) { return paramNames[i]; }
|
||||
public int ParamsCount() { return paramNames.Length; }
|
||||
|
||||
@@ -51,8 +122,27 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
case 1:
|
||||
return new string[] { "HatCLI.exe"};
|
||||
case 2:
|
||||
return new string[] { "74" };
|
||||
|
||||
return new string[] {
|
||||
SerialPortData.MeterProduct.Poseidon.ToDescription() ,
|
||||
SerialPortData.MeterProduct.Ally.ToDescription(),
|
||||
SerialPortData.MeterProduct.IperlPlus.ToDescription(),
|
||||
SerialPortData.MeterProduct.IperlLegacy.ToDescription()
|
||||
};
|
||||
case 3:
|
||||
return new string[]
|
||||
{
|
||||
SerialPortData.HatType.Mth.ToDescription(),
|
||||
SerialPortData.HatType.Harry.ToDescription()
|
||||
};
|
||||
case 4:
|
||||
return new string[] { "30", "60", "90"};
|
||||
case 5:
|
||||
string macro0 = SerialPortData.MacroTypes.readall.ToDescription();
|
||||
string macro1 = SerialPortData.MacroTypes.macro1.ToDescription();
|
||||
string macro2 = SerialPortData.MacroTypes.macro2.ToDescription();
|
||||
string macro3 = SerialPortData.MacroTypes.macro3.ToDescription();
|
||||
|
||||
return new string[] { macro0, macro1, macro2, macro3 };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -60,12 +150,15 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities) { return new Configs.ParamsProvider.ComponentCfgCtrl(this, null); }
|
||||
|
||||
|
||||
|
||||
public void InitializeAll()
|
||||
{
|
||||
ComPortNr = 3;
|
||||
CliFileName = "HatCLI.exe";
|
||||
MeterType = 74;
|
||||
CliFileName = "HatCLI.exe";
|
||||
MeterType = SerialPortData.MeterProduct.Poseidon;
|
||||
HatType = SerialPortData.HatType.Mth;
|
||||
TimeOut = 30;
|
||||
MainMacro = SerialPortData.MacroTypes.readall.ToDescription();
|
||||
}
|
||||
|
||||
public bool ValidateParam(int i, string strValue, out string message)
|
||||
@@ -83,8 +176,17 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
if(System.Text.RegularExpressions.Regex.IsMatch(strValue, @"^[a-zA-Z0-9_-]+\.exe$")) return true; //validate exe file name
|
||||
break;
|
||||
case 2:
|
||||
if (TryParseMeter(strValue, out var meter)) return true;
|
||||
break;
|
||||
case 3:
|
||||
if (TryParseHatType(strValue, out var hatType)) return true;
|
||||
break;
|
||||
case 4:
|
||||
if (int.TryParse(strValue, out idummy) && idummy > 0) return true;
|
||||
break;
|
||||
case 5:
|
||||
if (TryParseMacro(strValue, out string macroType)) return true;
|
||||
break;
|
||||
default:
|
||||
message = "Invalid index";
|
||||
return false;
|
||||
@@ -100,11 +202,38 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
case 0: ComPortNr = int.Parse(str); return CfgUpdateFlags.RestartRqrd;
|
||||
case 1: CliFileName = str; return CfgUpdateFlags.RestartRqrd;
|
||||
case 2: MeterType = int.Parse(str); return CfgUpdateFlags.RestartRqrd;
|
||||
case 2: TryParseMeter(str, out MeterType); return CfgUpdateFlags.RestartRqrd;
|
||||
case 3: TryParseHatType(str, out HatType); return CfgUpdateFlags.RestartRqrd;
|
||||
case 4: TimeOut = int.Parse(str); return CfgUpdateFlags.RestartRqrd;
|
||||
case 5: MainMacro = str; return CfgUpdateFlags.RestartRqrd;
|
||||
default: return CfgUpdateFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryParseHatType(string description, out SerialPortData.HatType hatType)
|
||||
{
|
||||
var found = Enum.GetValues(typeof(SerialPortData.HatType))
|
||||
.Cast<SerialPortData.HatType>()
|
||||
.FirstOrDefault(e =>
|
||||
string.Equals(
|
||||
e.GetType()
|
||||
.GetField(e.ToString())
|
||||
.GetCustomAttribute<DescriptionAttribute>()
|
||||
?.Description,
|
||||
description,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!Equals(found, default(SerialPortData.HatType)) ||
|
||||
string.Equals(description, "Mth", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hatType = found;
|
||||
return true;
|
||||
}
|
||||
|
||||
hatType = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool UpdateEmbeddedDbEntity()
|
||||
{
|
||||
return true; /// =OK, do nothing
|
||||
@@ -139,9 +268,12 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
case 0: return ComPortNr.ToString();
|
||||
case 1: return CliFileName;
|
||||
case 2: return MeterType.ToString();
|
||||
case 3: return HatType.ToString();
|
||||
case 4: return TimeOut.ToString();
|
||||
case 5: return MainMacro;
|
||||
default:
|
||||
return string.Format("{0}: Com{1}, CliName =`{2}`, MeterType = {3}",
|
||||
Name, ComPortNr, CliFileName, MeterType);
|
||||
return string.Format("{0}: Com{1}, CliName =`{2}`, MeterType = {3}, HatType = {4}, TimeOut = {5}",
|
||||
Name, ComPortNr, CliFileName, MeterType, HatType, TimeOut);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +282,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
prms.ComPortNr = this.ComPortNr;
|
||||
prms.CliFileName = this.CliFileName;
|
||||
prms.MeterType = this.MeterType;
|
||||
prms.HatType = this.HatType;
|
||||
prms.TimeOut = this.TimeOut;
|
||||
prms.MainMacro = this.MainMacro;
|
||||
prms.ProcParams = this.ProcParams;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Owns one logical dialog phase. A new phase must arm a reader even when
|
||||
/// the preceding phase left it in Done/Error; within this phase it is armed
|
||||
/// only once.
|
||||
/// </summary>
|
||||
public sealed class PoseidonReadPhaseRunner
|
||||
{
|
||||
private readonly bool readStart;
|
||||
private readonly HashSet<IPoseidonReadOperation> startedReaders =
|
||||
new HashSet<IPoseidonReadOperation>();
|
||||
|
||||
public PoseidonReadPhaseRunner(bool readStart)
|
||||
{
|
||||
this.readStart = readStart;
|
||||
}
|
||||
|
||||
public bool RunIteration(IEnumerable<IPoseidonReadOperation> readers)
|
||||
{
|
||||
if (readers == null)
|
||||
return true;
|
||||
|
||||
bool allReadersFinished = true;
|
||||
foreach (IPoseidonReadOperation reader in readers)
|
||||
{
|
||||
if (reader == null)
|
||||
continue;
|
||||
|
||||
if (startedReaders.Add(reader))
|
||||
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;
|
||||
@@ -24,6 +25,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
public class PoseidonReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, ICommonRegReader
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(PoseidonReader));
|
||||
// The simulator must never open the configured physical Hat CLI. The
|
||||
// deterministic test CLI returns a valid Poseidon JSON response instead.
|
||||
internal const string SimulatedCliFileName = "cmdSleepTest.exe";
|
||||
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
||||
|
||||
readonly PoseidonCfg registerReaderCfg;
|
||||
@@ -280,11 +284,23 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
if ((DebugLevel == DebugMode.Normal)||(DebugLevel == DebugMode.Simulate))
|
||||
{
|
||||
/// Prepare serial port
|
||||
string cliFileName = GetCliFileNameForMode(DebugLevel, registerReaderCfg.CliFileName);
|
||||
serialPort = new SerialPortData(string.Format("COM{0}", registerReaderCfg.ComPortNr),
|
||||
registerReaderCfg.CliFileName,
|
||||
registerReaderCfg.MeterType);
|
||||
cliFileName,
|
||||
registerReaderCfg.MeterType, //registerReaderCfg.MeterType, //MeterProduct.Poseidon
|
||||
registerReaderCfg.HatType,//HatType.Mth
|
||||
registerReaderCfg.TimeOut, //timeoutSeconds
|
||||
registerReaderCfg.MainMacro
|
||||
);
|
||||
|
||||
|
||||
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
|
||||
//serialPort.Open();
|
||||
//we can check file program if exists
|
||||
@@ -305,6 +321,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);
|
||||
@@ -330,13 +353,21 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
/// for debug purposes what time will consume answer
|
||||
/// </summary>
|
||||
private long startTimeInMilis = -1, fullTimeInMilis = -1;
|
||||
/// 30 seconds
|
||||
private static long SafetyTimeOut = 30 * 1000;
|
||||
private long incommingTime = -1;
|
||||
private bool _lastOpTimedOut;
|
||||
private bool lastCliReadingParsed;
|
||||
private string lastCliReadFailureReason;
|
||||
private bool lastCliReadSucceeded;
|
||||
|
||||
|
||||
/// 30 seconds
|
||||
|
||||
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>
|
||||
@@ -350,108 +381,262 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
if (_currentOp == CurrentPoseidonOp.SendStartDataStream)
|
||||
{
|
||||
CliRunner.Clear();
|
||||
_lastOpTimedOut = false;
|
||||
CliRunner.AddSendAsync(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
return Event.Busy;
|
||||
_currentOp = CurrentPoseidonOp.SendStartDataStream_Runing;
|
||||
return Event.Busy;
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.SendStartDataStream_Runing)
|
||||
{
|
||||
if (CliRunner.AreTasksDone()
|
||||
|| CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
if (CliRunner.AreTasksDone())
|
||||
{
|
||||
_currentOp = CurrentPoseidonOp.SendStartDataStream_Done;
|
||||
}
|
||||
return Event.Busy;
|
||||
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
{
|
||||
_lastOpTimedOut = true;
|
||||
CliRunner.CancelUndoneTasksAsTimedOut();
|
||||
_currentOp = CurrentPoseidonOp.SendStartDataStream_Done;
|
||||
}
|
||||
|
||||
return Event.Busy;
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.SendStartDataStream_Done)
|
||||
{
|
||||
var firstTask = CliRunner.TaskPool.FindLast(t => t is Task<string>);
|
||||
var firstTaskInfo = CliRunner.TaskPool
|
||||
.FindLast(t => t.Task is Task<string> && t.UseResult);
|
||||
|
||||
|
||||
if (firstTask != null && firstTask is Task<string>)
|
||||
if (firstTaskInfo != null)
|
||||
{
|
||||
string data = null;
|
||||
data = (firstTask as Task<string>).Result;
|
||||
var task = (Task<string>)firstTaskInfo.Task;
|
||||
string data = task.Result;
|
||||
|
||||
if (data != null && TryGetDeviceId(data, out wmSerialNr))
|
||||
if (!string.IsNullOrEmpty(data))
|
||||
{
|
||||
|
||||
TryGetDeviceId(data, out wmSerialNr);
|
||||
}
|
||||
}
|
||||
else if (_lastOpTimedOut)
|
||||
{
|
||||
log.Warn(
|
||||
$"PoseidonReader {Name}: SendStartDataStream timed out, no completed result will be used.");
|
||||
}
|
||||
|
||||
_currentOp = CurrentPoseidonOp.Done;
|
||||
CliRunner.Clear();
|
||||
_currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done;
|
||||
return Event.Done;
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
||||
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
|
||||
{
|
||||
startTimeInMilis = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
|
||||
lastCliReadingParsed = false;
|
||||
lastCliReadFailureReason = null;
|
||||
lastCliReadSucceeded = false;
|
||||
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
incommingTime = -1;
|
||||
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
|
||||
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
CliRunner.Clear();
|
||||
_lastOpTimedOut = false;
|
||||
log.DebugFormat("{0}: starting CLI read, direction={1}, path='{2}', args='{3}'",
|
||||
Name,
|
||||
_isReadingStart ? "start" : "end",
|
||||
serialPort == null ? "<not initialized>" : serialPort.SerialPortCmdClientPath,
|
||||
serialPort == null ? "<not initialized>" : serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams));
|
||||
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Running;
|
||||
return Event.Busy;
|
||||
}else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Running)
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Running)
|
||||
{
|
||||
if (CliRunner.AreTasksDone()
|
||||
|| CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
if (CliRunner.AreTasksDone())
|
||||
{
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
||||
//set time stamp - end of reading
|
||||
incommingTime = CliRunner.IncommingTime;
|
||||
log.DebugFormat("{0}: CLI read task finished, taskCount={1}, incomingTime={2}",
|
||||
Name, CliRunner.TaskPool.Count, incommingTime);
|
||||
}
|
||||
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
{
|
||||
_lastOpTimedOut = true;
|
||||
CliRunner.CancelUndoneTasksAsTimedOut();
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
||||
incommingTime = CliRunner.IncommingTime;
|
||||
}
|
||||
|
||||
return Event.Busy;
|
||||
}
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Done)
|
||||
{
|
||||
fullTimeInMilis = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - startTimeInMilis;
|
||||
log.Debug($"PoseidonReader.Run() Name= {Cfg.Name} fullTimeInMilis = {fullTimeInMilis} ");
|
||||
JsonDataFromPoseidon data = null;
|
||||
var first = CliRunner.TaskPool.FindLast(t => t is Task<JsonDataFromPoseidon>);
|
||||
if (first != null && first is Task<JsonDataFromPoseidon>)
|
||||
{
|
||||
data = (first as Task<JsonDataFromPoseidon>).Result;
|
||||
fullTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTimeInMilis;
|
||||
log.Debug($"PoseidonReader.Run() Name= {Cfg.Name} fullTimeInMilis = {fullTimeInMilis}");
|
||||
|
||||
//GET serial number
|
||||
JsonDataFromPoseidon data = null;
|
||||
|
||||
var firstTaskInfo = CliRunner.TaskPool
|
||||
.FindLast(t => t.Task is Task<JsonDataFromPoseidon> && t.UseResult);
|
||||
|
||||
if (firstTaskInfo != null)
|
||||
{
|
||||
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. {lastCliReadFailureReason}");
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Error($"PoseidonReader {Name}: no usable JsonDataFromPoseidon task. {lastCliReadFailureReason}");
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
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
|
||||
{
|
||||
wmSerialNr = data?.DeviceId ?? wmSerialNr;
|
||||
wmSerialNr = data.DeviceId ?? wmSerialNr;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error("Serial Nr - parse error!");
|
||||
log.Error("Serial Nr - parse error!", e);
|
||||
}
|
||||
}
|
||||
|
||||
//GET volume
|
||||
if (data != null && Double.TryParse(data.Reading, out double Volume))
|
||||
double volumeLi;
|
||||
string dialogValueFailureReason;
|
||||
if (TryGetDialogValue(data, out volumeLi, out dialogValueFailureReason))
|
||||
{
|
||||
double VolumeLi = Units.ConvertFrom(Unit.USgal, Volume);
|
||||
//double VolumeM3 = Units.ConvertTo(Unit.m3, VolumeLi);
|
||||
if (_isReadingStart)
|
||||
{
|
||||
//volume
|
||||
beginWMState = VolumeLi;
|
||||
}
|
||||
else
|
||||
{
|
||||
//volume
|
||||
endWMState = VolumeLi;
|
||||
}
|
||||
}
|
||||
lastCliReadingParsed = true;
|
||||
lastCliReadSucceeded = true;
|
||||
double volume;
|
||||
TryParseCliReading(data.Reading, out 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 = dialogValueFailureReason;
|
||||
log.ErrorFormat("{0}: cannot parse CLI reading '{1}' using invariant or current culture.",
|
||||
Name, data.Reading);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Finish reading and loop
|
||||
_currentOp = CurrentPoseidonOp.Done;
|
||||
|
||||
CliRunner.Clear();
|
||||
_currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done;
|
||||
return Event.Done;
|
||||
}
|
||||
|
||||
return Event.None;
|
||||
|
||||
return Event.None;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a validated CLI response to the value written into the
|
||||
/// start/end dialog. Poseidon reports US gallons; TBF stores litres.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
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,10 +1,15 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public class SerialPortData
|
||||
{
|
||||
public const string CliDirectory = @"C:\TBF\Cli";
|
||||
|
||||
private Boolean? _cliExists;
|
||||
public bool CliExists { get {
|
||||
if (_cliExists == null || !_cliExists.HasValue)
|
||||
@@ -13,39 +18,245 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
}
|
||||
return _cliExists.Value;
|
||||
} }
|
||||
public string SerialPortCmdClientPath {
|
||||
#if DEBUG
|
||||
get { return Path.Combine("C:\\","TBF","Cli", CmdClientName);}
|
||||
#else
|
||||
get { return Path.Combine("..","Cli", CmdClientName);}
|
||||
#endif
|
||||
}
|
||||
public string SerialPortCmdClientPath
|
||||
{
|
||||
get
|
||||
{
|
||||
// The configured value is a file name, not an executable path.
|
||||
// Keeping the CLI directory fixed prevents a bench configuration
|
||||
// from starting an unintended executable or failing due to a
|
||||
// relative path/current-directory change.
|
||||
string cliFileName = Path.GetFileName(CmdClientName);
|
||||
if (string.IsNullOrWhiteSpace(cliFileName))
|
||||
cliFileName = "HalCli.exe";
|
||||
|
||||
return Path.Combine(CliDirectory, cliFileName);
|
||||
}
|
||||
}
|
||||
public string CmdClientName { get; set; } = "HalCli.exe";
|
||||
public string PortName { get; set; }
|
||||
public int MeterType { get; set; }
|
||||
// Backward compatibility: old numeric meter type
|
||||
public int? OldMeterType { get; set; }
|
||||
|
||||
public enum EMeterArg {
|
||||
// New v2.0 parameters
|
||||
public HatType Hat { get; set; } = HatType.Mth;
|
||||
public int TimeoutSeconds { get; set; } = 10;
|
||||
public string Macro { get; set; } = "readall";
|
||||
|
||||
[XmlIgnore]
|
||||
public MeterProduct Meter { get; set; } = MeterProduct.Poseidon;
|
||||
|
||||
[XmlElement("MeterProduct")]
|
||||
public string MeterXml
|
||||
{
|
||||
get { return MeterToString(Meter); }
|
||||
set { Meter = StringToMeter(value); }
|
||||
}
|
||||
|
||||
public static string MeterToString(MeterProduct meter)
|
||||
{
|
||||
switch (meter)
|
||||
{
|
||||
case MeterProduct.Poseidon:
|
||||
return "Poseidon"; //Poseidon
|
||||
case MeterProduct.Ally:
|
||||
return "ally";
|
||||
case MeterProduct.IperlPlus:
|
||||
return "iperlplus";
|
||||
case MeterProduct.IperlLegacy:
|
||||
return "iperllegacy";
|
||||
default:
|
||||
return "74";
|
||||
}
|
||||
}
|
||||
|
||||
public static MeterProduct StringToMeter(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return MeterProduct.Poseidon;
|
||||
|
||||
// old numeric values: 74, 7889, etc.
|
||||
if (int.TryParse(value, out _))
|
||||
return MeterProduct.Poseidon;
|
||||
|
||||
if (string.Equals(value, "cTouchRead", StringComparison.OrdinalIgnoreCase))
|
||||
return MeterProduct.Poseidon;
|
||||
|
||||
if (string.Equals(value, "Poseidon", StringComparison.OrdinalIgnoreCase))
|
||||
return MeterProduct.Poseidon;
|
||||
|
||||
if (string.Equals(value, "ally", StringComparison.OrdinalIgnoreCase))
|
||||
return MeterProduct.Ally;
|
||||
|
||||
if (string.Equals(value, "iperlplus", StringComparison.OrdinalIgnoreCase))
|
||||
return MeterProduct.IperlPlus;
|
||||
|
||||
if (string.Equals(value, "iperllegacy", StringComparison.OrdinalIgnoreCase))
|
||||
return MeterProduct.IperlLegacy;
|
||||
|
||||
return MeterProduct.Poseidon;
|
||||
}
|
||||
|
||||
public static bool TryParseMeter(
|
||||
string description,
|
||||
out SerialPortData.MeterProduct meter)
|
||||
{
|
||||
foreach (SerialPortData.MeterProduct value in
|
||||
Enum.GetValues(typeof(SerialPortData.MeterProduct)))
|
||||
{
|
||||
var attr = value.GetType()
|
||||
.GetField(value.ToString())
|
||||
.GetCustomAttribute<DescriptionAttribute>();
|
||||
|
||||
if (string.Equals(attr?.Description, description, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value.ToString(), description, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
meter = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
meter = SerialPortData.MeterProduct.Poseidon;
|
||||
return false;
|
||||
}
|
||||
|
||||
public enum MacroTypes
|
||||
{
|
||||
[Description("readall")]
|
||||
readall = 0,
|
||||
[Description("readmacro1")]
|
||||
macro1 = 1,
|
||||
[Description("readmacro2")]
|
||||
macro2 = 2,
|
||||
[Description("readmacro3")]
|
||||
macro3 = 3
|
||||
}
|
||||
|
||||
public enum HatType
|
||||
{
|
||||
[Description("Mth")]
|
||||
Mth = 0,
|
||||
[Description("Harry")]
|
||||
Harry = 1
|
||||
}
|
||||
|
||||
public enum MeterProduct
|
||||
{
|
||||
[Description("Poseidon")]
|
||||
[XmlEnum("74")]
|
||||
Poseidon = 74,
|
||||
|
||||
[Description("ally")]
|
||||
[XmlEnum("ally")]
|
||||
Ally = 75,
|
||||
|
||||
[Description("iperlplus")]
|
||||
[XmlEnum("iperlplus")]
|
||||
IperlPlus = 76,
|
||||
|
||||
[Description("iperllegacy")]
|
||||
[XmlEnum("iperllegacy")]
|
||||
IperlLegacy = 77
|
||||
}
|
||||
|
||||
public enum EMeterArg
|
||||
{
|
||||
Calibration = 0,
|
||||
AllParams = 2,
|
||||
DeviceId = 3,
|
||||
}
|
||||
EMeterArg _eMeterArg = EMeterArg.AllParams;
|
||||
|
||||
// New macro operations - just waste
|
||||
ReadMacro1 = 10,
|
||||
ReadMacro2 = 11,
|
||||
ReadMacro3 = 12
|
||||
}
|
||||
|
||||
public string DefaultArgSettings(EMeterArg eMeterArg)
|
||||
public string DefaultArgSettings(EMeterArg eMeterArg)
|
||||
{
|
||||
string commonArgs = BuildCommonArgs();
|
||||
|
||||
switch (eMeterArg)
|
||||
{
|
||||
case EMeterArg.Calibration:
|
||||
return $"-p {PortName} -m {MeterType} --operation write --parameter Calibration --value 1";
|
||||
case EMeterArg.AllParams:
|
||||
return $"-p {PortName} -m {MeterType} --operation readall";
|
||||
return $"{commonArgs} --operation write --parameter Calibration --value 1";
|
||||
|
||||
case EMeterArg.DeviceId:
|
||||
return $"-p {PortName} -m {MeterType} --operation read --parameter DeviceId";
|
||||
return $"{commonArgs} --operation read --parameter DeviceId";
|
||||
|
||||
case EMeterArg.AllParams:
|
||||
case EMeterArg.ReadMacro1:
|
||||
case EMeterArg.ReadMacro2:
|
||||
case EMeterArg.ReadMacro3:
|
||||
return $"{commonArgs} --operation {Macro}";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildCommonArgs()
|
||||
{
|
||||
return $"-p {PortName} " +
|
||||
$"-m {GetMeterArgument()} " +
|
||||
$"--hat {GetHatArgument()} " +
|
||||
$"--timeout {TimeoutSeconds}";
|
||||
}
|
||||
|
||||
private string GetMeterArgument()
|
||||
{
|
||||
// Old compatibility mode
|
||||
if (OldMeterType.HasValue)
|
||||
return OldMeterType.Value.ToString();
|
||||
|
||||
switch (Meter)
|
||||
{
|
||||
case MeterProduct.Poseidon:
|
||||
return "74"; //return "Poseidon";
|
||||
|
||||
case MeterProduct.Ally:
|
||||
return "ally";
|
||||
|
||||
case MeterProduct.IperlPlus:
|
||||
return "iperlplus";
|
||||
|
||||
case MeterProduct.IperlLegacy:
|
||||
return "iperllegacy";
|
||||
|
||||
default:
|
||||
return "74"; //return "Poseidon";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetHatArgument()
|
||||
{
|
||||
switch (Hat)
|
||||
{
|
||||
case HatType.Harry:
|
||||
return "harry";
|
||||
|
||||
case HatType.Mth:
|
||||
default:
|
||||
return "mth";
|
||||
}
|
||||
}
|
||||
|
||||
// New constructor for v2.0
|
||||
public SerialPortData(
|
||||
string portName,
|
||||
string cmdClientName,
|
||||
MeterProduct meter = MeterProduct.Poseidon,
|
||||
HatType hat = HatType.Mth,
|
||||
int timeoutSeconds = 10,
|
||||
string macro = "readall")
|
||||
{
|
||||
PortName = portName;
|
||||
CmdClientName = cmdClientName;
|
||||
Meter = meter;
|
||||
Hat = hat;
|
||||
TimeoutSeconds = timeoutSeconds;
|
||||
Macro = macro;
|
||||
}
|
||||
|
||||
// Old constructor kept for backward compatibility
|
||||
public SerialPortData(
|
||||
string portName,
|
||||
string cmdClientName,
|
||||
@@ -53,7 +264,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
PortName = portName;
|
||||
CmdClientName = cmdClientName;
|
||||
MeterType = meterType;
|
||||
OldMeterType = meterType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+13
-11
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ using TBF.Rig.Network.RestAPI;
|
||||
using TBF.Rig.Network.RestAPI.facade;
|
||||
using TBF.Rig.Output.Printers.GroupPrinting.Single;
|
||||
using TBF.UiBridge;
|
||||
using SharedComponents;
|
||||
using System.Text;
|
||||
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||
@@ -1548,7 +1547,7 @@ namespace TBF.Rig.Sequences
|
||||
}
|
||||
|
||||
// ✍️ Uloženie do vlastného logu
|
||||
LiveLogCache.Instance.AddLog(logBuilder.ToString());
|
||||
//LiveLogCache.Instance.AddLog(logBuilder.ToString());
|
||||
|
||||
Bridge.OnError(this, "Chyba pri aktualizácii výsledkov v MySQL.");
|
||||
goto error;
|
||||
|
||||
@@ -373,7 +373,7 @@ namespace TBF.Rig.TestMethods.SmartMeterFlyingStartMassCollection
|
||||
/// Show the modeless dialog with error indication
|
||||
///
|
||||
string componentName = (method as IComponent)?.Name ?? string.Empty;
|
||||
Program.MainWnd.Invoke(new SmartCommFormDlgt(OpenSmartCommForm), new object[] { this, componentName, test, testParams });
|
||||
Program.MainWnd.Invoke(new SmartCommFormDlgt(OpenSmartCommForm), new object[] { this, method/*componentName*/, test, testParams });
|
||||
|
||||
//------------------------------------------------
|
||||
Bridge.OnActivity(this, Strings.iPerl_Communication_in_progress);
|
||||
|
||||
@@ -10,7 +10,7 @@ using SchematicDrawing;
|
||||
using TBF.Rig.ControlBoard.Uni;
|
||||
using TBF.Boxes;
|
||||
using TBF.Resources;
|
||||
using AppDiagnostic;
|
||||
|
||||
|
||||
namespace TBF.Rig.Uni.FlowMeter
|
||||
{
|
||||
|
||||
@@ -7,7 +7,6 @@ using log4net;
|
||||
using SchematicDrawing;
|
||||
using TBF.Rig.ControlBoard.Uni;
|
||||
using TBF.Boxes;
|
||||
using SharedComponents;
|
||||
|
||||
namespace TBF.Rig.Uni.RegValveLowRegulTimeSaturation
|
||||
{
|
||||
|
||||
+7
-9
@@ -63,6 +63,7 @@
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;CAMERA;LANG_PL;IPERL;</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
@@ -74,6 +75,7 @@
|
||||
<DefineConstants>TRACE;CAMERA;LANG_PL;IPERL;</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
@@ -1321,11 +1323,15 @@
|
||||
<DependentUpon>RRCfgCtrl.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunner.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliTaskInfo.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliTaskState.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\Factory.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\GetDescription.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\JsonDataFromPoseidon.cs" />
|
||||
<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" />
|
||||
@@ -3968,10 +3974,6 @@
|
||||
<Project>{5954d496-caab-4f7a-bde2-bdc8f47dab19}</Project>
|
||||
<Name>NfcS5_DLL</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\AppDiagnostic\AppDiagnostic.csproj">
|
||||
<Project>{fa9abad1-7184-4295-ade8-d44f2e3de6b2}</Project>
|
||||
<Name>AppDiagnostic</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Common\Common.csproj">
|
||||
<Project>{c8939821-ba5c-4988-a3d0-bf53b74865c7}</Project>
|
||||
<Name>Common</Name>
|
||||
@@ -4012,10 +4014,6 @@
|
||||
<Project>{0f79ca69-9dbc-41f3-a6fc-5a2937365343}</Project>
|
||||
<Name>SchematicDrawing</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SharedComponents\SharedComponents.csproj">
|
||||
<Project>{8f942729-f454-4c99-ba6c-746962065ae3}</Project>
|
||||
<Name>SharedComponents</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SharedDatabase\SharedDatabase.csproj">
|
||||
<Project>{211b5e3f-9996-48a7-abde-c878dd2d71c2}</Project>
|
||||
<Name>SharedDatabase</Name>
|
||||
@@ -4033,4 +4031,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
+2
-6
@@ -13,13 +13,9 @@ using Common;
|
||||
using Config.Entities;
|
||||
using Dirichlet.Numerics;
|
||||
using SchematicDrawing;
|
||||
using SharedDatabase;
|
||||
using TBF.Resources;
|
||||
using TBF.UiBridge;
|
||||
using TBF.UI.Shared;
|
||||
using AppDiagnostic;
|
||||
using SharedComponents;
|
||||
using System.Diagnostics;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||
|
||||
namespace TBF.UI
|
||||
@@ -1164,8 +1160,8 @@ namespace TBF.UI
|
||||
ILog loger = LogManager.GetLogger("logCache");
|
||||
log.Debug("ahoj, ideme");*/
|
||||
|
||||
LiveLogCache.Instance.AddLog("=== Live diagnostic entry ===");
|
||||
DiagApi liveDiagApi = new DiagApi();
|
||||
//LiveLogCache.Instance.AddLog("=== Live diagnostic entry ===");
|
||||
//DiagApi liveDiagApi = new DiagApi();
|
||||
|
||||
/*try
|
||||
{
|
||||
|
||||
@@ -12,161 +12,450 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
[TestSubject(typeof(CliRunner))]
|
||||
public class CliRunnerTest
|
||||
{
|
||||
|
||||
[TestMethod]
|
||||
public void ExtractJson_Test()
|
||||
{
|
||||
String allOutput = " {\n \"DeviceId\": \"1000000267\"\n}\n";
|
||||
string allOutput = " {\n \"DeviceId\": \"1000000267\"\n}\n";
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
Assert.IsTrue(!string.IsNullOrEmpty(json));
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(json));
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void ExtractJson_Test2()
|
||||
{
|
||||
String allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
|
||||
string allOutput =
|
||||
"{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
Assert.IsTrue(!string.IsNullOrEmpty(json));
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(json));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryJsonStringDeserialize_Test()
|
||||
{
|
||||
String allOutput = "{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
|
||||
string allOutput =
|
||||
"{\n \"NfcTagDetected\": true,\n \"ProductType\": 74,\n \"ProductTypeVersion\": \"B1.0.13\",\n \"DeviceId\": \"1000000267\",\n \"Reading\": \"0003292.1\",\n \"Reading_Totalizer\": \"000329218\",\n \"Reading_Digits\": \"8\",\n \"Reading_Shift\": \"-1\",\n \"Reading_Resolution\": \"-2\",\n \"Reading_Units\": \"2\",\n \"Reading_FlowDirection\": \"3\",\n \"Reading_FlowRate\": \"0\",\n \"CalibrationFactor\": \"3197\",\n \"ReadingComplete\": true,\n \"MeterState\": \"0x02\",\n \"OpticalDataMode\": \"0x00\",\n \"SpreadSpectrumParameters\": \"Disabled: 0xTrue\",\n \"BuildInformation\": \"\"\n}\n";
|
||||
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
var ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
|
||||
bool ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
|
||||
|
||||
Assert.IsTrue(ok, "Deserialization failed");
|
||||
Assert.IsNotNull(dto);
|
||||
Assert.AreEqual("1000000267", dto.DeviceId);
|
||||
Assert.AreEqual("0003292.1", dto.Reading);
|
||||
Assert.AreEqual("000329218", dto.Reading_Totalizer);
|
||||
Assert.AreEqual(true, dto.NfcTagDetected);
|
||||
Assert.AreEqual(74, dto.ProductType);
|
||||
Assert.AreEqual("74", dto.ProductType);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerialPortData_NewDefaults_ShouldUsePoseidonMthTimeout10()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData(
|
||||
"COM3",
|
||||
"HalCli.exe",
|
||||
SerialPortData.MeterProduct.Poseidon);
|
||||
|
||||
string args = serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat mth --timeout 10 --operation readall",
|
||||
args);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerialPortData_HarryHat_ShouldUseHarry()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData(
|
||||
"COM3",
|
||||
"HalCli.exe",
|
||||
SerialPortData.MeterProduct.Poseidon,
|
||||
SerialPortData.HatType.Harry,
|
||||
30);
|
||||
|
||||
string args = serialPort.DefaultArgSettings(SerialPortData.EMeterArg.DeviceId);
|
||||
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat harry --timeout 30 --operation read --parameter DeviceId",
|
||||
args);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerialPortData_OldConstructor_ShouldKeepOldMeterType()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData("COM3", "HalCli.exe", 74);
|
||||
|
||||
string args = serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat mth --timeout 10 --operation readall",
|
||||
args);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerialPortData_NewMeterTypes_ShouldGenerateCorrectMeterNames()
|
||||
{
|
||||
var testCases = new[]
|
||||
{
|
||||
new { Meter = SerialPortData.MeterProduct.Poseidon, Expected = "74" },
|
||||
new { Meter = SerialPortData.MeterProduct.Ally, Expected = "ally" },
|
||||
new { Meter = SerialPortData.MeterProduct.IperlPlus, Expected = "iperlplus" },
|
||||
new { Meter = SerialPortData.MeterProduct.IperlLegacy, Expected = "iperllegacy" }
|
||||
};
|
||||
|
||||
foreach (var testCase in testCases)
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData(
|
||||
"COM3",
|
||||
"HalCli.exe",
|
||||
testCase.Meter);
|
||||
|
||||
string args = serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
StringAssert.Contains(args, $"-m {testCase.Expected}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerialPortData_ReadMacros_ShouldGenerateCorrectOperations()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData(
|
||||
"COM3",
|
||||
"HalCliDemo.exe",
|
||||
SerialPortData.MeterProduct.Poseidon);
|
||||
|
||||
serialPort.Macro = "readmacro1";
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat mth --timeout 10 --operation readmacro1",
|
||||
serialPort.DefaultArgSettings(SerialPortData.EMeterArg.ReadMacro1));
|
||||
|
||||
serialPort.Macro = "readmacro2";
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat mth --timeout 10 --operation readmacro2",
|
||||
serialPort.DefaultArgSettings(SerialPortData.EMeterArg.ReadMacro2));
|
||||
|
||||
serialPort.Macro = "readmacro3";
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat mth --timeout 10 --operation readmacro3",
|
||||
serialPort.DefaultArgSettings(SerialPortData.EMeterArg.ReadMacro3));
|
||||
|
||||
serialPort.Macro = "xxx";
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat mth --timeout 10 --operation xxx",
|
||||
serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerialPortData_CliPath_ShouldAlwaysUseFixedCliDirectory()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData(
|
||||
"COM3",
|
||||
@"D:\obsolete\HatCliDemo.exe",
|
||||
SerialPortData.MeterProduct.Poseidon);
|
||||
|
||||
Assert.AreEqual(
|
||||
@"C:\TBF\Cli\HatCliDemo.exe",
|
||||
serialPort.SerialPortCmdClientPath);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RunMultipleTimesTestProgram_CheckParalelWork()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData("COM3","cmdSleepTest.exe",74);
|
||||
|
||||
// Check if the executable exists in current directory
|
||||
SerialPortData serialPort = new SerialPortData(
|
||||
"COM3",
|
||||
"cmdSleepTest.exe",
|
||||
SerialPortData.MeterProduct.Poseidon,
|
||||
SerialPortData.HatType.Mth,
|
||||
10);
|
||||
|
||||
if (!System.IO.File.Exists(serialPort.SerialPortCmdClientPath))
|
||||
{
|
||||
Assert.Inconclusive($"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
Assert.Inconclusive(
|
||||
$"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
|
||||
|
||||
CliRunner cliRunnerOne = new CliRunner(false);
|
||||
DateTime StartTime = DateTime.Now;
|
||||
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
cliRunnerOne.WaitAll();
|
||||
TimeSpan OneEndTime = DateTime.Now - StartTime;
|
||||
|
||||
StartTime = DateTime.Now;
|
||||
Console.WriteLine($"Start Time Loop: {StartTime.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
|
||||
|
||||
TimeSpan oneEndTime = DateTime.Now - startTime;
|
||||
|
||||
startTime = DateTime.Now;
|
||||
Console.WriteLine($"Start Time Loop: {startTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
}
|
||||
|
||||
cliRunner.WaitAll();
|
||||
|
||||
DateTime EndTime = DateTime.Now;
|
||||
Console.WriteLine($"End Time Loop: {EndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
TimeSpan delta = EndTime - StartTime;
|
||||
Console.WriteLine($"Delta Time One process {OneEndTime.Hours:D2}:{OneEndTime.Minutes:D2}:{OneEndTime.Seconds:D2}.{OneEndTime.Milliseconds:D3} " +
|
||||
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
|
||||
DateTime endTime = DateTime.Now;
|
||||
Console.WriteLine($"End Time Loop: {endTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
TimeSpan delta = endTime - startTime;
|
||||
|
||||
Console.WriteLine(
|
||||
$"Delta Time One process {oneEndTime.Hours:D2}:{oneEndTime.Minutes:D2}:{oneEndTime.Seconds:D2}.{oneEndTime.Milliseconds:D3} " +
|
||||
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
|
||||
|
||||
Console.WriteLine($"Total tasks: {cliRunner.TaskPool.Count}");
|
||||
|
||||
|
||||
foreach (Task<JsonDataFromPoseidon> task in cliRunner.TaskPool)
|
||||
|
||||
foreach (CliTaskInfo taskInfo in cliRunner.TaskPool)
|
||||
{
|
||||
if (task != null)
|
||||
Assert.IsNotNull(taskInfo);
|
||||
Assert.IsNotNull(taskInfo.Task);
|
||||
|
||||
if (taskInfo.UseResult)
|
||||
{
|
||||
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
|
||||
Assert.IsNotNull(task, "Expected Task<JsonDataFromPoseidon>.");
|
||||
|
||||
JsonDataFromPoseidon jsonDataFromPoseidon = task.Result;
|
||||
Console.WriteLine($"Result: {jsonDataFromPoseidon.DeviceId}");
|
||||
|
||||
Console.WriteLine($"Result: {jsonDataFromPoseidon?.DeviceId}");
|
||||
|
||||
Assert.IsNotNull(jsonDataFromPoseidon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail(
|
||||
$"Task did not complete successfully. State={taskInfo.State}, Status={taskInfo.Task.Status}");
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the total time is greater than the sum of the individual times
|
||||
Assert.IsTrue((cliRunner.TaskPool.Count*OneEndTime.Ticks) > delta.Ticks);
|
||||
|
||||
|
||||
Assert.IsTrue((cliRunner.TaskPool.Count * oneEndTime.Ticks) > delta.Ticks);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task RunMultipleTimesTestProgram_CheckParalelWorkCumulative()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData("COM3","cmdSleepTest.exe",74);
|
||||
|
||||
// Check if the executable exists in current directory
|
||||
SerialPortData serialPort = new SerialPortData(
|
||||
"COM3",
|
||||
"cmdSleepTest.exe",
|
||||
SerialPortData.MeterProduct.Poseidon,
|
||||
SerialPortData.HatType.Mth,
|
||||
10);
|
||||
|
||||
if (!System.IO.File.Exists(serialPort.SerialPortCmdClientPath))
|
||||
{
|
||||
Assert.Inconclusive($"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
Assert.Inconclusive(
|
||||
$"Test executable '{serialPort.SerialPortCmdClientPath}' not found. Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<CliRunner> cliRunnerList = new List<CliRunner>();
|
||||
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
cliRunnerList.Add(new CliRunner(false));
|
||||
|
||||
}
|
||||
|
||||
CliRunner cliRunnerOne = new CliRunner(false);
|
||||
DateTime StartTime = DateTime.Now;
|
||||
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort, SerialPortData.EMeterArg.AllParams);
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
cliRunnerOne.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
cliRunnerOne.WaitAll();
|
||||
TimeSpan OneEndTime = DateTime.Now - StartTime;
|
||||
|
||||
StartTime = DateTime.Now;
|
||||
Console.WriteLine($"Start Time Loop: {StartTime.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
|
||||
TimeSpan oneEndTime = DateTime.Now - startTime;
|
||||
|
||||
startTime = DateTime.Now;
|
||||
Console.WriteLine($"Start Time Loop: {startTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
for (int iCliRunner = 0; iCliRunner < cliRunnerList.Count; iCliRunner++)
|
||||
{
|
||||
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
||||
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
||||
cliRunnerList[iCliRunner].AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for ALL tasks from ALL runners
|
||||
var allTasks = cliRunnerList.SelectMany(r => r.TaskPool).ToArray();
|
||||
Task[] allTasks = cliRunnerList
|
||||
.SelectMany(r => r.TaskPool)
|
||||
.Where(ti => ti != null && ti.Task != null)
|
||||
.Select(ti => ti.Task)
|
||||
.ToArray();
|
||||
|
||||
await Task.WhenAll(allTasks);
|
||||
|
||||
DateTime EndTime = DateTime.Now;
|
||||
Console.WriteLine($"End Time Loop: {EndTime.ToString("yyyy-MM-dd HH:mm:ss")}");
|
||||
TimeSpan delta = EndTime - StartTime;
|
||||
Console.WriteLine($"Delta Time One process {OneEndTime.Hours:D2}:{OneEndTime.Minutes:D2}:{OneEndTime.Seconds:D2}.{OneEndTime.Milliseconds:D3} " +
|
||||
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
|
||||
DateTime endTime = DateTime.Now;
|
||||
Console.WriteLine($"End Time Loop: {endTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
TimeSpan delta = endTime - startTime;
|
||||
|
||||
Console.WriteLine(
|
||||
$"Delta Time One process {oneEndTime.Hours:D2}:{oneEndTime.Minutes:D2}:{oneEndTime.Seconds:D2}.{oneEndTime.Milliseconds:D3} " +
|
||||
$"vs Loop: {delta.Hours:D2}:{delta.Minutes:D2}:{delta.Seconds:D2}.{delta.Milliseconds:D3}");
|
||||
|
||||
Console.WriteLine($"Total tasks: {cliRunnerList.Sum(runner => runner.TaskPool.Count)}");
|
||||
|
||||
|
||||
for (int iCliRunner = 0; iCliRunner < cliRunnerList.Count; iCliRunner++)
|
||||
{
|
||||
string results = $"iCliRunner: {iCliRunner}";
|
||||
foreach (Task<JsonDataFromPoseidon> task in cliRunnerList[iCliRunner].TaskPool)
|
||||
|
||||
foreach (CliTaskInfo taskInfo in cliRunnerList[iCliRunner].TaskPool)
|
||||
{
|
||||
if (task != null)
|
||||
Assert.IsNotNull(taskInfo);
|
||||
Assert.IsNotNull(taskInfo.Task);
|
||||
|
||||
if (taskInfo.UseResult)
|
||||
{
|
||||
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
|
||||
Assert.IsNotNull(task, "Expected Task<JsonDataFromPoseidon>.");
|
||||
|
||||
JsonDataFromPoseidon jsonDataFromPoseidon = task.Result;
|
||||
results += ($" ID: {jsonDataFromPoseidon.DeviceId}");
|
||||
|
||||
results += $" ID: {jsonDataFromPoseidon?.DeviceId}";
|
||||
|
||||
Assert.IsNotNull(jsonDataFromPoseidon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail(
|
||||
$"Task did not complete successfully. Runner={iCliRunner}, State={taskInfo.State}, Status={taskInfo.Task.Status}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(results);
|
||||
}
|
||||
|
||||
// Check that the total time is greater than the sum of the individual times
|
||||
Assert.IsTrue((cliRunnerList.Count*OneEndTime.Ticks) > delta.Ticks);
|
||||
Assert.IsTrue((cliRunnerList.Count * oneEndTime.Ticks) > delta.Ticks);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void TryJsonStringDeserialize_ReadMacro1_Test()
|
||||
{
|
||||
string allOutput = @"{
|
||||
""DeviceId"": ""1000000176"",
|
||||
""MeterState"": ""MB012501A177"",
|
||||
""CalibrationFactor"": ""3250"",
|
||||
""Reading_Units"": ""0"",
|
||||
""Reading_Totalizer"": 0,
|
||||
""Reading_FlowRate"": 0,
|
||||
""Reading_FlowDirection"": 0,
|
||||
""Reading_Digits"": 0,
|
||||
""Reading_Shift"": 0,
|
||||
""Reading_Resolution"": 0,
|
||||
""OpticalDataMode"": ""199"",
|
||||
""MeterSize"": ""3"",
|
||||
""ManufactureDate"": ""2026/04/20 00:56:00"",
|
||||
""ProductType"": ""74"",
|
||||
""ProductTypeVersion"": ""B1.0.29"",
|
||||
""CliVersion"": ""2.0.0.0"",
|
||||
""ReadingComplete"": true,
|
||||
""NfcTagDetected"": true
|
||||
}";
|
||||
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
bool ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.IsNotNull(dto);
|
||||
Assert.AreEqual("1000000176", dto.DeviceId);
|
||||
Assert.AreEqual("MB012501A177", dto.MeterState);
|
||||
Assert.AreEqual("3250", dto.CalibrationFactor);
|
||||
Assert.AreEqual("199", dto.OpticalDataMode);
|
||||
Assert.AreEqual("3", dto.MeterSize);
|
||||
Assert.AreEqual("74", dto.ProductType);
|
||||
Assert.AreEqual("2.0.0.0", dto.CliVersion);
|
||||
Assert.IsTrue(dto.ReadingComplete);
|
||||
Assert.IsTrue(dto.NfcTagDetected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryJsonStringDeserialize_ReadMacro2_Test()
|
||||
{
|
||||
string allOutput = @"{
|
||||
""DeviceId"": ""1000000176"",
|
||||
""Reading_Units"": ""0"",
|
||||
""Reading_Totalizer"": 0,
|
||||
""Reading_FlowRate"": 0,
|
||||
""Reading_FlowDirection"": 0,
|
||||
""Reading_Digits"": 0,
|
||||
""Reading_Shift"": 0,
|
||||
""Reading_Resolution"": 0,
|
||||
""ProductType"": ""74"",
|
||||
""ProductTypeVersion"": ""B1.0.29"",
|
||||
""CliVersion"": ""2.0.0.0"",
|
||||
""ReadingComplete"": true,
|
||||
""NfcTagDetected"": true
|
||||
}";
|
||||
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
bool ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.IsNotNull(dto);
|
||||
Assert.AreEqual("1000000176", dto.DeviceId);
|
||||
Assert.AreEqual("0", dto.Reading_Units);
|
||||
Assert.AreEqual("74", dto.ProductType);
|
||||
Assert.AreEqual("B1.0.29", dto.ProductTypeVersion);
|
||||
Assert.AreEqual("2.0.0.0", dto.CliVersion);
|
||||
Assert.IsTrue(dto.ReadingComplete);
|
||||
Assert.IsTrue(dto.NfcTagDetected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryJsonStringDeserialize_ReadMacro3_Test()
|
||||
{
|
||||
string allOutput = @"{
|
||||
""DeviceId"": ""1000000176"",
|
||||
""Reading_Units"": ""0"",
|
||||
""Reading_Totalizer"": 0,
|
||||
""Reading_FlowRate"": 0,
|
||||
""Reading_FlowDirection"": 0,
|
||||
""Reading_Digits"": 0,
|
||||
""Reading_Shift"": 0,
|
||||
""Reading_Resolution"": 0,
|
||||
""CliVersion"": ""2.0.0.0"",
|
||||
""ReadingComplete"": true,
|
||||
""NfcTagDetected"": true
|
||||
}";
|
||||
|
||||
CliRunner cliRunner = new CliRunner(false);
|
||||
string json = cliRunner.ExtractJson(allOutput);
|
||||
|
||||
bool ok = cliRunner.TryJsonStringDeserialize<JsonDataFromPoseidon>(json, out var dto);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.IsNotNull(dto);
|
||||
Assert.AreEqual("1000000176", dto.DeviceId);
|
||||
Assert.AreEqual("0", dto.Reading_Units);
|
||||
Assert.AreEqual("2.0.0.0", dto.CliVersion);
|
||||
Assert.IsTrue(dto.ReadingComplete);
|
||||
Assert.IsTrue(dto.NfcTagDetected);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(CliRunner))]
|
||||
public class CliRunnerTimeoutTest
|
||||
{
|
||||
private static SerialPortData CreateDefaultSerialPortData()
|
||||
{
|
||||
return new SerialPortData(
|
||||
"COM3",
|
||||
"cmdSleepTest.exe",
|
||||
SerialPortData.MeterProduct.Poseidon,
|
||||
SerialPortData.HatType.Mth,
|
||||
30,
|
||||
TBF.Rig.RegisterReaders.PoseidonCmdStartStop.GetDescription.ToDescription(SerialPortData.MacroTypes.readall));
|
||||
}
|
||||
|
||||
private static string GetCmdSleepTestPath()
|
||||
{
|
||||
return CreateDefaultSerialPortData().SerialPortCmdClientPath;
|
||||
}
|
||||
|
||||
private static void EnsureExeExists(string exePath)
|
||||
{
|
||||
if (!File.Exists(exePath))
|
||||
{
|
||||
Assert.Inconclusive(
|
||||
$"Test executable '{exePath}' not found. " +
|
||||
"Please ensure cmdSleepTest.exe exists in the test directory.");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerialPortData_ForTimeoutTests_ShouldUseVersion2Defaults()
|
||||
{
|
||||
SerialPortData serialPort = CreateDefaultSerialPortData();
|
||||
|
||||
string args = serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat mth --timeout 30 --operation readall",
|
||||
args);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerialPortData_ForTimeoutTests_ShouldStillSupportOldMeterType()
|
||||
{
|
||||
SerialPortData serialPort = new SerialPortData(
|
||||
"COM3",
|
||||
"cmdSleepTest.exe",
|
||||
74);
|
||||
|
||||
string args = serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams);
|
||||
|
||||
Assert.AreEqual(
|
||||
"-p COM3 -m 74 --hat mth --timeout 10 --operation readall",
|
||||
args);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Timeout_ShouldBeDetected_ForLongRunningTask()
|
||||
{
|
||||
string exePath = GetCmdSleepTestPath();
|
||||
EnsureExeExists(exePath);
|
||||
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
|
||||
|
||||
await Task.Delay(200);
|
||||
|
||||
bool timedOut = cliRunner.TimeOutReceived(100);
|
||||
|
||||
Assert.IsTrue(timedOut, "Timeout should have been detected.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CancelUndoneTasksAsTimedOut_ShouldMarkRunningTaskAsTimedOut()
|
||||
{
|
||||
string exePath = GetCmdSleepTestPath();
|
||||
EnsureExeExists(exePath);
|
||||
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
|
||||
|
||||
await Task.Delay(150);
|
||||
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.AreEqual(1, cliRunner.TaskPool.Count);
|
||||
|
||||
var taskInfo = cliRunner.TaskPool.Single();
|
||||
|
||||
Assert.IsNotNull(taskInfo);
|
||||
Assert.AreEqual(CliTaskState.TimedOut, taskInfo.State);
|
||||
Assert.IsFalse(taskInfo.UseResult, "Timed out task must not be used.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CancelUndoneTasksAsTimedOut_ShouldKeepCompletedTaskCompleted()
|
||||
{
|
||||
string exePath = GetCmdSleepTestPath();
|
||||
EnsureExeExists(exePath);
|
||||
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=50");
|
||||
|
||||
try
|
||||
{
|
||||
cliRunner.WaitAll();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Let assertions inspect states.
|
||||
}
|
||||
|
||||
cliRunner.RefreshTaskStates();
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.AreEqual(1, cliRunner.TaskPool.Count);
|
||||
|
||||
var taskInfo = cliRunner.TaskPool.Single();
|
||||
|
||||
Assert.IsNotNull(taskInfo);
|
||||
Assert.AreEqual(CliTaskState.Completed, taskInfo.State);
|
||||
Assert.IsTrue(taskInfo.UseResult, "Completed task should remain usable.");
|
||||
|
||||
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
|
||||
|
||||
Assert.IsNotNull(task);
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
|
||||
Assert.IsNotNull(task.Result);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(task.Result.DeviceId));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CancelUndoneTasksAsTimedOut_ShouldKeepDoneTask_AndMarkUndoneTask()
|
||||
{
|
||||
string exePath = GetCmdSleepTestPath();
|
||||
EnsureExeExists(exePath);
|
||||
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=50");
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
|
||||
|
||||
await Task.Delay(250);
|
||||
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.AreEqual(2, cliRunner.TaskPool.Count);
|
||||
Assert.AreEqual(
|
||||
1,
|
||||
cliRunner.TaskPool.Count(t => t.State == CliTaskState.Completed),
|
||||
"Exactly one fast task should be completed before timeout handling.");
|
||||
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
var doneTask = cliRunner.TaskPool.Single(t => t.State == CliTaskState.Completed);
|
||||
var timedOutTask = cliRunner.TaskPool.Single(t => t.State == CliTaskState.TimedOut);
|
||||
|
||||
Assert.IsTrue(doneTask.UseResult);
|
||||
Assert.IsFalse(timedOutTask.UseResult);
|
||||
|
||||
var completed = doneTask.Task as Task<JsonDataFromPoseidon>;
|
||||
|
||||
Assert.IsNotNull(completed);
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, completed.Status);
|
||||
Assert.IsNotNull(completed.Result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task AreTasksDone_ShouldReturnTrue_AfterTimeoutCancellation()
|
||||
{
|
||||
string exePath = GetCmdSleepTestPath();
|
||||
EnsureExeExists(exePath);
|
||||
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
|
||||
|
||||
await Task.Delay(150);
|
||||
|
||||
Assert.IsFalse(
|
||||
cliRunner.AreTasksDone(),
|
||||
"Task should still be running before timeout cancellation.");
|
||||
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.IsTrue(
|
||||
cliRunner.AreTasksDone(),
|
||||
"After timed out tasks are marked, runner should report no running tasks.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TimedOutTask_ShouldNotHaveUseResult()
|
||||
{
|
||||
string exePath = GetCmdSleepTestPath();
|
||||
EnsureExeExists(exePath);
|
||||
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(exePath, "sleep=5000");
|
||||
|
||||
await Task.Delay(150);
|
||||
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
var taskInfo = cliRunner.TaskPool.Single();
|
||||
|
||||
Assert.AreEqual(CliTaskState.TimedOut, taskInfo.State);
|
||||
Assert.IsFalse(taskInfo.UseResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MultipleSlowTasks_ShouldAllBeMarkedTimedOut()
|
||||
{
|
||||
string exePath = GetCmdSleepTestPath();
|
||||
EnsureExeExists(exePath);
|
||||
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
exePath,
|
||||
"sleep=5000");
|
||||
}
|
||||
|
||||
await Task.Delay(200);
|
||||
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.AreEqual(5, cliRunner.TaskPool.Count);
|
||||
Assert.AreEqual(5, cliRunner.TaskPool.Count(t => t.State == CliTaskState.TimedOut));
|
||||
Assert.AreEqual(0, cliRunner.TaskPool.Count(t => t.UseResult));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FastTask_WithInvalidJson_ShouldFinishButNotProduceUsefulResult()
|
||||
{
|
||||
string exePath = GetCmdSleepTestPath();
|
||||
EnsureExeExists(exePath);
|
||||
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
cliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
exePath,
|
||||
"sleep=50 invalidjson=true");
|
||||
|
||||
try
|
||||
{
|
||||
cliRunner.WaitAll();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Let assertions inspect states.
|
||||
}
|
||||
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.AreEqual(1, cliRunner.TaskPool.Count);
|
||||
|
||||
var taskInfo = cliRunner.TaskPool.Single();
|
||||
|
||||
Assert.AreEqual(CliTaskState.Completed, taskInfo.State);
|
||||
|
||||
var task = taskInfo.Task as Task<JsonDataFromPoseidon>;
|
||||
|
||||
Assert.IsNotNull(task);
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, task.Status);
|
||||
|
||||
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,76 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(CliRunner))]
|
||||
public class CliRunnerTimeoutUnitTest
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task CancelUndoneTasksAsTimedOut_ShouldMarkOnlyRunningTasks()
|
||||
{
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
var completedTask = Task.FromResult("done");
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var runningTask = Task.Delay(TimeSpan.FromSeconds(30), cts.Token);
|
||||
|
||||
cliRunner.AddTaskForTest(completedTask, "completed");
|
||||
cliRunner.AddTaskForTest(runningTask, "running", cts);
|
||||
|
||||
await Task.Delay(50);
|
||||
|
||||
cliRunner.RefreshTaskStates();
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.AreEqual(2, cliRunner.TaskPool.Count);
|
||||
|
||||
var completed = cliRunner.TaskPool.First(t => t.Name == "completed");
|
||||
var timedOut = cliRunner.TaskPool.First(t => t.Name == "running");
|
||||
|
||||
Assert.AreEqual(CliTaskState.Completed, completed.State);
|
||||
Assert.IsTrue(completed.UseResult);
|
||||
|
||||
Assert.AreEqual(CliTaskState.TimedOut, timedOut.State);
|
||||
Assert.IsFalse(timedOut.UseResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TimeOutReceived_ShouldReturnTrue_WhenElapsedExceedsTimeout()
|
||||
{
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
Thread.Sleep(30);
|
||||
|
||||
Assert.IsTrue(cliRunner.TimeOutReceived(1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task AreTasksDone_ShouldReturnTrue_WhenTimedOutTasksAreMarked()
|
||||
{
|
||||
var cliRunner = new CliRunner(false);
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var runningTask = Task.Delay(TimeSpan.FromSeconds(30), cts.Token);
|
||||
|
||||
cliRunner.AddTaskForTest(runningTask, "running", cts);
|
||||
|
||||
await Task.Delay(50);
|
||||
|
||||
Assert.IsFalse(cliRunner.AreTasksDone());
|
||||
|
||||
cliRunner.CancelUndoneTasksAsTimedOut();
|
||||
cliRunner.RefreshTaskStates();
|
||||
|
||||
Assert.IsTrue(cliRunner.AreTasksDone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||
using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression fixtures extracted from PT50 customer CliRunner.txt (2026-09-01).
|
||||
/// They are non-zero responses returned by HatCliDemo for COM43 and COM45.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class PoseidonCustomerCliResponseTest
|
||||
{
|
||||
// The expected fixtures were calculated independently. A 0.1 ml
|
||||
// tolerance covers insignificant IEEE-754 conversion differences while
|
||||
// still detecting any meaningful incorrect transfer to the dialog.
|
||||
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()
|
||||
{
|
||||
// The first pair is a valid START dialog result; neither value is zero.
|
||||
AssertDialogValue(Com43InitialResponse, "1000000219", "002780.99", 10527.1923171862);
|
||||
AssertDialogValue(Com45InitialResponse, "1000000279", "002316.63", 8769.39850116792);
|
||||
|
||||
// The later pair must be usable by a new START or END phase, rather
|
||||
// than TBF retaining the initial START values or displaying zero.
|
||||
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);
|
||||
Assert.IsTrue(response.NfcTagDetected == true);
|
||||
Assert.IsTrue(response.ReadingComplete == true);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
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
|
||||
{
|
||||
// Exact non-zero reading shape returned by HatCliDemo in the PT50 customer log.
|
||||
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);
|
||||
Assert.AreNotEqual(0d, cliValueLitres);
|
||||
|
||||
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);
|
||||
|
||||
TextBox textBox = FindTextBox(dialog, "startTextBox1");
|
||||
Assert.AreEqual(dialog.WMStartStateStr[0], textBox.Text, "CLI value was not prefilled into the Start dialog.");
|
||||
|
||||
ConfirmDialog(dialog);
|
||||
Assert.IsTrue(dialog.Completed);
|
||||
Assert.AreEqual(expectedValue, dialog.WMStartState[0], 0.000001, "Start dialog did not parse the confirmed value.");
|
||||
|
||||
var entryForm = CreateEntryFormForTransfer(EntryForm.CurrentOp.ReadDatastream_StartStates);
|
||||
TransferAcceptedDialogValues(entryForm, dialog);
|
||||
Assert.AreEqual(expectedValue, entryForm.WMStartState(0), 0.000001,
|
||||
"EntryForm did not receive the Start value confirmed in the dialog.");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
TextBox textBox = FindTextBox(dialog, "endTextBox1");
|
||||
Assert.AreEqual(expectedValue.ToString(), textBox.Text, "CLI value was not prefilled into the End dialog.");
|
||||
|
||||
ConfirmDialog(dialog);
|
||||
Assert.IsTrue(dialog.Completed);
|
||||
Assert.AreEqual(expectedValue, dialog.WMEndState[0], 0.000001, "End dialog did not parse the confirmed value.");
|
||||
|
||||
var entryForm = CreateEntryFormForTransfer(EntryForm.CurrentOp.ReadDatastream_EndStates);
|
||||
TransferAcceptedDialogValues(entryForm, dialog);
|
||||
Assert.AreEqual(expectedValue, entryForm.WMEndState(0), 0.000001,
|
||||
"EntryForm did not receive the End value confirmed in the dialog.");
|
||||
}
|
||||
}
|
||||
|
||||
private static TextBox FindTextBox(Control dialog, string name)
|
||||
{
|
||||
TextBox textBox = dialog.Controls.Find(name, true).OfType<TextBox>().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 transferMethod = typeof(EntryForm).GetMethod("StoreAcceptedDialogValues",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.IsNotNull(transferMethod);
|
||||
transferMethod.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
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
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
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)));
|
||||
|
||||
TestContext.WriteLine("PoseidonReadCycle | scenario=FullBench | phase=Start | readers=48");
|
||||
Assert.IsFalse(RunAndLogCycleIteration("FullBench", 1, readers, true));
|
||||
Assert.IsFalse(RunAndLogCycleIteration("FullBench", 2, readers, true));
|
||||
Assert.IsFalse(RunAndLogCycleIteration("FullBench", 3, readers, true));
|
||||
Assert.IsTrue(RunAndLogCycleIteration("FullBench", 4, 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);
|
||||
}
|
||||
LogReaderSummary("FullBench final", readers);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RunIteration_ReducedBench_DoesNotRestartCompletedReaders()
|
||||
{
|
||||
var readers = new List<IPoseidonReadOperation>
|
||||
{
|
||||
new FakePoseidonReader("PoseidonPos1", 1),
|
||||
new FakePoseidonReader("PoseidonPos2", 3)
|
||||
};
|
||||
|
||||
TestContext.WriteLine("PoseidonReadCycle | scenario=ReducedBench | phase=End | readers=2");
|
||||
Assert.IsFalse(RunAndLogCycleIteration("ReducedBench", 1, 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(RunAndLogCycleIteration("ReducedBench", 2, readers, false));
|
||||
Assert.AreEqual(1, ((FakePoseidonReader)readers[0]).StartCount);
|
||||
Assert.IsFalse(RunAndLogCycleIteration("ReducedBench", 3, readers, false));
|
||||
Assert.IsTrue(RunAndLogCycleIteration("ReducedBench", 4, readers, false));
|
||||
|
||||
foreach (FakePoseidonReader reader in readers)
|
||||
{
|
||||
Assert.AreEqual(1, reader.StartCount, reader.Name + " must be armed exactly once.");
|
||||
}
|
||||
LogReaderSummary("ReducedBench final", readers);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StartThenEnd_PhasesRearmCompletedReadersExactlyOncePerPhase()
|
||||
{
|
||||
var reader = new FakePoseidonReader("PoseidonPos1", 1);
|
||||
var readers = new List<IPoseidonReadOperation> { reader };
|
||||
var startPhase = new PoseidonReadPhaseRunner(true);
|
||||
|
||||
TestContext.WriteLine("PoseidonReadCycle | scenario=StartThenEnd | phase=Start | readers=1");
|
||||
Assert.IsFalse(RunAndLogPhaseIteration("StartThenEnd", "Start", 1, startPhase, readers));
|
||||
Assert.IsTrue(RunAndLogPhaseIteration("StartThenEnd", "Start", 2, startPhase, readers));
|
||||
Assert.AreEqual(1, reader.StartCount);
|
||||
Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
|
||||
|
||||
var endPhase = new PoseidonReadPhaseRunner(false);
|
||||
TestContext.WriteLine("PoseidonReadCycle | scenario=StartThenEnd | phase=End | rearming reader completed by Start");
|
||||
Assert.IsFalse(RunAndLogPhaseIteration("StartThenEnd", "End", 1, endPhase, readers));
|
||||
Assert.IsTrue(RunAndLogPhaseIteration("StartThenEnd", "End", 2, endPhase, readers));
|
||||
|
||||
Assert.AreEqual(2, reader.StartCount,
|
||||
"A completed START read must be rearmed once for the END phase.");
|
||||
Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
|
||||
LogReaderSummary("StartThenEnd final", readers);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InputJson_ZeroReading_WithDecimalCommaOrDot_IsDeserializedAndParsedCorrectly()
|
||||
{
|
||||
TestContext.WriteLine("Poseidon JSON parsing | verifies zero is retained for both decimal separators.");
|
||||
AssertInputJsonReading("00000,0", 0.0);
|
||||
AssertInputJsonReading("00000.0", 0.0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InputJson_NonZeroReading_WithDecimalCommaOrDot_IsDeserializedAndParsedCorrectly()
|
||||
{
|
||||
TestContext.WriteLine("Poseidon JSON parsing | verifies non-zero readings for both decimal separators.");
|
||||
AssertInputJsonReading("002433,50", 2433.50);
|
||||
AssertInputJsonReading("002433.50", 2433.50);
|
||||
AssertInputJsonReading("002898,60", 2898.60);
|
||||
AssertInputJsonReading("002898.60", 2898.60);
|
||||
}
|
||||
|
||||
[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);
|
||||
TestContext.WriteLine("Poseidon CLI validation | zero reading accepted when NFC=true and ReadingComplete=true.");
|
||||
|
||||
validZero.ReadingComplete = false;
|
||||
Assert.IsFalse(CmdPoseidonReader.TryValidateCliReadResponse(validZero, out failureReason));
|
||||
Assert.AreEqual("ReadingComplete=false.", failureReason);
|
||||
TestContext.WriteLine("Poseidon CLI validation | rejected response: " + failureReason);
|
||||
|
||||
validZero.ReadingComplete = true;
|
||||
validZero.NfcTagDetected = false;
|
||||
Assert.IsFalse(CmdPoseidonReader.TryValidateCliReadResponse(validZero, out failureReason));
|
||||
Assert.AreEqual("NfcTagDetected=false.", failureReason);
|
||||
TestContext.WriteLine("Poseidon CLI validation | rejected response: " + failureReason);
|
||||
}
|
||||
|
||||
private 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);
|
||||
TestContext.WriteLine(string.Format(
|
||||
"Poseidon JSON parsing | rawReading='{0}' | parsed={1} | expected={2} | deviceId={3}",
|
||||
reading, parsedReading, expectedValue, data.DeviceId));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SimulationMode_AlwaysUsesCmdSleepTestInsteadOfConfiguredHatCli()
|
||||
{
|
||||
string simulatedCli = CmdPoseidonReader.GetCliFileNameForMode(
|
||||
Common.DebugMode.Simulate, "HatCliDemo.exe");
|
||||
string normalCli = CmdPoseidonReader.GetCliFileNameForMode(
|
||||
Common.DebugMode.Normal, "HatCliDemo.exe");
|
||||
|
||||
Assert.AreEqual("cmdSleepTest.exe", simulatedCli);
|
||||
Assert.AreEqual("HatCliDemo.exe", normalCli);
|
||||
TestContext.WriteLine(
|
||||
"Poseidon simulation CLI | mode=Simulate | configured=HatCliDemo.exe | selected=" + simulatedCli);
|
||||
TestContext.WriteLine(
|
||||
"Poseidon simulation CLI | mode=Normal | configured=HatCliDemo.exe | selected=" + normalCli);
|
||||
}
|
||||
|
||||
[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);
|
||||
TestContext.WriteLine("Poseidon cmdSleepTest | reader=PoseidonPos1 | phase=Start | cli=" + cliPath);
|
||||
|
||||
for (int iteration = 0; iteration < 300 && !IsTerminal(reader); iteration++)
|
||||
{
|
||||
reader.Run();
|
||||
if (iteration == 0 || iteration % 25 == 0 || IsTerminal(reader))
|
||||
TestContext.WriteLine(string.Format(
|
||||
"Poseidon cmdSleepTest | iteration={0} | state={1} | serial='{2}' | begin={3}",
|
||||
iteration + 1, reader.CurrentOp, reader.SerialNr, reader.BeginWMState));
|
||||
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.");
|
||||
TestContext.WriteLine(string.Format(
|
||||
"Poseidon cmdSleepTest | completed | state={0} | serial='{1}' | begin={2}",
|
||||
reader.CurrentOp, reader.SerialNr, reader.BeginWMState));
|
||||
}
|
||||
|
||||
private bool RunAndLogCycleIteration(string scenario, int iteration,
|
||||
IList<IPoseidonReadOperation> readers, bool readStart)
|
||||
{
|
||||
bool completed = PoseidonReadCycle.RunIteration(readers, readStart);
|
||||
LogReaderSummary(scenario + " iteration=" + iteration + " phase=" + (readStart ? "Start" : "End") +
|
||||
" completed=" + completed, readers);
|
||||
return completed;
|
||||
}
|
||||
|
||||
private bool RunAndLogPhaseIteration(string scenario, string phase, int iteration,
|
||||
PoseidonReadPhaseRunner runner, IList<IPoseidonReadOperation> readers)
|
||||
{
|
||||
bool completed = runner.RunIteration(readers);
|
||||
LogReaderSummary(scenario + " iteration=" + iteration + " phase=" + phase +
|
||||
" completed=" + completed, readers);
|
||||
return completed;
|
||||
}
|
||||
|
||||
private void LogReaderSummary(string label, IEnumerable<IPoseidonReadOperation> readers)
|
||||
{
|
||||
var readerList = readers.ToList();
|
||||
int notStartedCount = readerList.Count(reader => reader.IsNotStarted);
|
||||
int errorCount = readerList.Count(reader => reader.HasError);
|
||||
int doneCount = readerList.Count(reader => reader.IsFinished && !reader.HasError);
|
||||
int runningCount = readerList.Count - notStartedCount - errorCount - doneCount;
|
||||
string stateCounts = string.Format("NotStarted={0}, Running={1}, Done={2}, Error={3}",
|
||||
notStartedCount, runningCount, doneCount, errorCount);
|
||||
string startCounts = string.Join(", ", readerList
|
||||
.OfType<FakePoseidonReader>()
|
||||
.GroupBy(reader => reader.StartCount)
|
||||
.OrderBy(group => group.Key)
|
||||
.Select(group => "starts" + group.Key + "=" + group.Count()));
|
||||
|
||||
string details = readerList.Count <= 10
|
||||
? string.Join("; ", readerList.OfType<FakePoseidonReader>().Select(reader =>
|
||||
string.Format("{0}[state={1}, starts={2}, runs={3}]",
|
||||
reader.Name, reader.CurrentOp, reader.StartCount, reader.RunCount)))
|
||||
: "details=omitted for full bench";
|
||||
|
||||
TestContext.WriteLine(string.Format(
|
||||
"PoseidonReadCycle | {0} | readerCount={1} | states={2} | {3} | {4}",
|
||||
label, readerList.Count, stateCounts, startCounts, details));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+89
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,14 @@
|
||||
<Compile Include="Rig\Network\Camera\RoiForFixedStartKeyence\RoiTest.cs" />
|
||||
<Compile Include="Rig\Output\FileWriters\Enhanced\WriterTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTest.cs" />
|
||||
<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\PoseidonCustomerCliResponseTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonDialogTransferTest.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>
|
||||
@@ -113,6 +120,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>
|
||||
@@ -169,4 +180,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user