Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9cda221bb | ||
|
|
c6181a76ec | ||
|
|
1c8d01562f | ||
|
|
0258e54b4e | ||
|
|
b8e54e1fc9 | ||
|
|
8d42232589 | ||
|
|
e4d4d75642 | ||
|
|
2b5a0ffe14 | ||
|
|
8157a752bd | ||
|
|
4dd805bb1c |
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using MySql.Data.MySqlClient;
|
using MySql.Data.MySqlClient;
|
||||||
using Common;
|
using Common;
|
||||||
|
using log4net;
|
||||||
|
|
||||||
namespace Results.Entities.helpers
|
namespace Results.Entities.helpers
|
||||||
{
|
{
|
||||||
@@ -10,6 +11,8 @@ namespace Results.Entities.helpers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DatabaseMigrationHelper
|
public static class DatabaseMigrationHelper
|
||||||
{
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(DatabaseMigrationHelper));
|
||||||
|
|
||||||
public static void EnsureSchema(DBType dbType, string connectionString)
|
public static void EnsureSchema(DBType dbType, string connectionString)
|
||||||
{
|
{
|
||||||
switch (dbType)
|
switch (dbType)
|
||||||
@@ -36,9 +39,31 @@ namespace Results.Entities.helpers
|
|||||||
// EnsureColumnMySql(conn, "WaterMeter", "Q3Channel", "INT NOT NULL DEFAULT 0");
|
// EnsureColumnMySql(conn, "WaterMeter", "Q3Channel", "INT NOT NULL DEFAULT 0");
|
||||||
// EnsureColumnMySql(conn, "MeterTestRslt", "Q3Channel", "INT NOT NULL DEFAULT 0");
|
// EnsureColumnMySql(conn, "MeterTestRslt", "Q3Channel", "INT NOT NULL DEFAULT 0");
|
||||||
|
|
||||||
EnsureColumnMySql(conn, "MeterTestRslt", "FlipMode", "INT NULL");
|
EnsureColumnMySql(conn, "MeterTestRslt", "PulsesPerKilogram", "DOUBLE NOT NULL DEFAULT 0");
|
||||||
}
|
EnsureMeterTestResultMassColumnsMySql(conn);
|
||||||
}
|
EnsureColumnMySql(conn, "MeterTestRslt", "FlipMode", "INT NULL");
|
||||||
|
EnsureColumnMySql(conn, "MeterTestRslt", "ExtraDataPath", "VARCHAR(255) NULL");
|
||||||
|
EnsureMeterTestResultExtraColumnsMySql(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureMeterTestResultExtraColumnsMySql(MySqlConnection conn)
|
||||||
|
{
|
||||||
|
for (int index = 1; index <= 9; index++)
|
||||||
|
EnsureColumnMySql(conn, "MeterTestRslt", "X" + index, "FLOAT NOT NULL DEFAULT 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// These properties were added to the MeterTestRslt NHibernate mapping
|
||||||
|
// after older customer databases had already been created. Keep them in
|
||||||
|
// one migration group so a batch insert does not fail one column at a time.
|
||||||
|
private static void EnsureMeterTestResultMassColumnsMySql(MySqlConnection conn)
|
||||||
|
{
|
||||||
|
EnsureColumnMySql(conn, "MeterTestRslt", "MassMeter", "DOUBLE NOT NULL DEFAULT 0");
|
||||||
|
EnsureColumnMySql(conn, "MeterTestRslt", "MassRef", "DOUBLE NOT NULL DEFAULT 0");
|
||||||
|
EnsureColumnMySql(conn, "MeterTestRslt", "ErrorMass", "DOUBLE NOT NULL DEFAULT 0");
|
||||||
|
EnsureColumnMySql(conn, "MeterTestRslt", "PassedMass", "TINYINT(1) NOT NULL DEFAULT 0");
|
||||||
|
EnsureColumnMySql(conn, "MeterTestRslt", "QuantityUnits", "VARCHAR(32) NULL");
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnsureColumnMySql(
|
private static void EnsureColumnMySql(
|
||||||
MySqlConnection conn,
|
MySqlConnection conn,
|
||||||
@@ -74,7 +99,9 @@ namespace Results.Entities.helpers
|
|||||||
alter.Transaction = transaction;
|
alter.Transaction = transaction;
|
||||||
alter.CommandText = "ALTER TABLE `" + tableName + "` ADD COLUMN `" +
|
alter.CommandText = "ALTER TABLE `" + tableName + "` ADD COLUMN `" +
|
||||||
columnName + "` " + columnDefinition;
|
columnName + "` " + columnDefinition;
|
||||||
alter.ExecuteNonQuery();
|
alter.ExecuteNonQuery();
|
||||||
|
log.WarnFormat("Results DB migration: added {0}.{1} ({2}).",
|
||||||
|
tableName, columnName, columnDefinition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,9 +127,28 @@ namespace Results.Entities.helpers
|
|||||||
// EnsureColumnSQLite(conn, "WaterMeter", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
|
// EnsureColumnSQLite(conn, "WaterMeter", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
|
||||||
// EnsureColumnSQLite(conn, "MeterTestRslt", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
|
// EnsureColumnSQLite(conn, "MeterTestRslt", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
|
||||||
EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL");
|
EnsureColumnSQLite(conn, "MeterTestRslt", "PulsesPerKilogram", "REAL NOT NULL DEFAULT 0");
|
||||||
}
|
EnsureMeterTestResultMassColumnsSQLite(conn);
|
||||||
}
|
EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL");
|
||||||
|
EnsureColumnSQLite(conn, "MeterTestRslt", "ExtraDataPath", "TEXT NULL");
|
||||||
|
EnsureMeterTestResultExtraColumnsSQLite(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureMeterTestResultExtraColumnsSQLite(System.Data.SQLite.SQLiteConnection conn)
|
||||||
|
{
|
||||||
|
for (int index = 1; index <= 9; index++)
|
||||||
|
EnsureColumnSQLite(conn, "MeterTestRslt", "X" + index, "REAL NOT NULL DEFAULT 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureMeterTestResultMassColumnsSQLite(System.Data.SQLite.SQLiteConnection conn)
|
||||||
|
{
|
||||||
|
EnsureColumnSQLite(conn, "MeterTestRslt", "MassMeter", "REAL NOT NULL DEFAULT 0");
|
||||||
|
EnsureColumnSQLite(conn, "MeterTestRslt", "MassRef", "REAL NOT NULL DEFAULT 0");
|
||||||
|
EnsureColumnSQLite(conn, "MeterTestRslt", "ErrorMass", "REAL NOT NULL DEFAULT 0");
|
||||||
|
EnsureColumnSQLite(conn, "MeterTestRslt", "PassedMass", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
EnsureColumnSQLite(conn, "MeterTestRslt", "QuantityUnits", "TEXT NULL");
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnsureColumnSQLite(
|
private static void EnsureColumnSQLite(
|
||||||
System.Data.SQLite.SQLiteConnection conn,
|
System.Data.SQLite.SQLiteConnection conn,
|
||||||
@@ -136,7 +182,9 @@ namespace Results.Entities.helpers
|
|||||||
{
|
{
|
||||||
alter.CommandText = "ALTER TABLE " + tableName + " ADD COLUMN " +
|
alter.CommandText = "ALTER TABLE " + tableName + " ADD COLUMN " +
|
||||||
columnName + " " + columnDefinition;
|
columnName + " " + columnDefinition;
|
||||||
alter.ExecuteNonQuery();
|
alter.ExecuteNonQuery();
|
||||||
|
log.WarnFormat("Results DB migration: added {0}.{1} ({2}).",
|
||||||
|
tableName, columnName, columnDefinition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,6 +227,12 @@ namespace TBF
|
|||||||
///
|
///
|
||||||
public long OptoHeadsEnabled; /// Bit field with opto-head enabled states, used by iPerlCommunicationForm and S640CommForm
|
public long OptoHeadsEnabled; /// Bit field with opto-head enabled states, used by iPerlCommunicationForm and S640CommForm
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Smart-meter reader selection used by SmartCommunicationForm and DataEntry.UNI.
|
||||||
|
/// OptoHeadsEnabled remains for the legacy iPerl and S640 dialogs.
|
||||||
|
/// </summary>
|
||||||
|
public SmartReaderSelectionSettings SmartReaderSelections;
|
||||||
|
|
||||||
/// Serial numbers
|
/// Serial numbers
|
||||||
public string[] LastSNTexts;
|
public string[] LastSNTexts;
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
|
|||||||
@@ -3,3 +3,14 @@
|
|||||||
| Version | Source of change | Target Environment | Title | Description |
|
| Version | Source of change | Target Environment | Title | Description |
|
||||||
|------------|----------------------|--------------------------------------------------------------|-----------------------------------------------------|------------------------------------|
|
|------------|----------------------|--------------------------------------------------------------|-----------------------------------------------------|------------------------------------|
|
||||||
| 3.9.2149.0 | Michal databse error | HeatMeters, Heat meter sensors, Procedure Dilog, Tab Process | Excanged columns value 'Sensor' and 'Heat meter sensor' | Fix in code ProcedureDlg, row 1851 |
|
| 3.9.2149.0 | Michal databse error | HeatMeters, Heat meter sensors, Procedure Dilog, Tab Process | Excanged columns value 'Sensor' and 'Heat meter sensor' | Fix in code ProcedureDlg, row 1851 |
|
||||||
|
| 3.9.2149.1 | Main TBF | General release | Version iteration | Assembly and file version increment. |
|
||||||
|
| 3.9.2149.2 | Main TBF | Poseidon register reader | Poseidon pulse and timing handling | Added reference-pulse reading in `Run()` and improved Poseidon timing/task tracking. |
|
||||||
|
| 3.9.2149.4 | Main TBF | Mass collection / Poseidon start-stop | Mass collection update | Refactored standing-start mass collection, extended Poseidon start/end data-entry configuration and improved dialog/task handling. |
|
||||||
|
| 3.9.2200.1 | Main TBF | Test infrastructure | TBF test assembly access | Added `InternalsVisibleTo` support for `TBFTests`. |
|
||||||
|
| 3.9.2201.1 | Main TBF | Poseidon CLI | Poseidon CLI configuration | Added macro descriptions, refined serial-port/CLI argument configuration and extended CLI test coverage. |
|
||||||
|
| 3.9.2202.1 | Main TBF | Poseidon CLI | CLI release iteration | Assembly and file version increment for the Poseidon CLI workstream. |
|
||||||
|
| 3.9.2203.1 | Main TBF | Poseidon CLI / smart-meter sequence | CLI test and configuration update | Updated CLI executable test setup, serial-port default responses and smart-meter component-name handling. |
|
||||||
|
| 3.9.2204.1 | Morrisville | Morrisville / Poseidon CLI | Morrisville CLI diagnostics | Restored lost 2204 versioning, added detailed CLI command/response logging and used a fixed CLI directory for deployment. |
|
||||||
|
| 3.9.2205.1 | Morrisville | Morrisville / Poseidon CLI / Results DB | Poseidon read diagnostics and results DB migration | Improved Poseidon CLI execution and diagnostics: fixed CLI working directory, exit code/stdout/stderr capture, JSON/NFC/reading validation and culture-independent decimal parsing. Added reader-cycle and fake-CLI coverage. Results DB now creates missing compatibility columns (`FlipMode`, `ExtraDataPath`, `X1`-`X9`) automatically for MySQL and SQLite. |
|
||||||
|
| 3.9.2206.0 | Morrisville / Main TBF | Poseidon CLI / Results DB | Poseidon start/end rearm and simulation isolation | Re-armed a completed START reader once for END, preventing reused START values or skipped END CLI calls. Added CLI response, dialog prefill and confirmed-value diagnostics. Simulation always runs `C:\TBF\Cli\cmdSleepTest.exe` instead of the configured physical Hat CLI. Added customer-response, decimal separator, non-zero, dialog-transfer, reader-cycle and simulation-selection regression tests. Added compatibility migration for `PulsesPerKilogram`, `MassMeter`, `MassRef`, `ErrorMass`, `PassedMass` and `QuantityUnits`. |
|
||||||
|
| 3.9.3143.0 | Ally port | Ally / Poseidon CLI / Results DB | Morrisville Poseidon fixes transferred | Ported the applicable Morrisville Poseidon read, simulation, logging, regression-test and results-schema migration fixes to the Ally source branch while retaining its compatible legacy reader configuration and non-blocking UI flow. |
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
|
|
||||||
|
|
||||||
System.Windows.Forms.Form modelessDlg;
|
System.Windows.Forms.Form modelessDlg;
|
||||||
|
bool modelessDialogOpening;
|
||||||
|
bool modelessDialogWaitLogged;
|
||||||
public bool Completed { get { return (modelessDlg is IHasCompleted) ? (modelessDlg as IHasCompleted).Completed : true; } }
|
public bool Completed { get { return (modelessDlg is IHasCompleted) ? (modelessDlg as IHasCompleted).Completed : true; } }
|
||||||
|
|
||||||
double refVolume;
|
double refVolume;
|
||||||
@@ -70,6 +72,15 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
}
|
}
|
||||||
ReadDataOp readDataOp;
|
ReadDataOp readDataOp;
|
||||||
|
|
||||||
|
// Keep the CLI phase between state-machine ticks. Blocking here prevents
|
||||||
|
// the state machine from processing a UI STOP request.
|
||||||
|
PoseidonReadPhaseRunner poseidonPhaseRunner;
|
||||||
|
List<IPoseidonReadOperation> poseidonPhaseReaders;
|
||||||
|
DateTime poseidonPhaseStartedAt;
|
||||||
|
DateTime poseidonNextProgressLogAt;
|
||||||
|
bool sendStartPhaseInitialized;
|
||||||
|
int sendStartPhaseIterations;
|
||||||
|
|
||||||
|
|
||||||
public EntryForm() { }
|
public EntryForm() { }
|
||||||
|
|
||||||
@@ -126,6 +137,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
//entryFormCfg.Direction = Direction.S640;
|
//entryFormCfg.Direction = Direction.S640;
|
||||||
//currentOp = CurrentOp.EnterTestStartStates;
|
//currentOp = CurrentOp.EnterTestStartStates;
|
||||||
this.regReaders = regReaders;
|
this.regReaders = regReaders;
|
||||||
|
log.InfoFormat("Poseidon data-entry requested START; operation={0}, batchWMs={1}, readers=[{2}]",
|
||||||
|
currentOp, waterMeters == null ? -1 : waterMeters.Count, DescribeReaders(regReaders));
|
||||||
int iterator = 0;
|
int iterator = 0;
|
||||||
foreach (IRegReader reader in regReaders)
|
foreach (IRegReader reader in regReaders)
|
||||||
{
|
{
|
||||||
@@ -167,35 +180,105 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
this.refVolume = refVolume;
|
this.refVolume = refVolume;
|
||||||
this.errLimLo = errLimLo;
|
this.errLimLo = errLimLo;
|
||||||
this.errLimHi = errLimHi;
|
this.errLimHi = errLimHi;
|
||||||
|
log.InfoFormat("Poseidon data-entry requested END; operation={0}, batchWMs={1}, refVolume={2}, errLow={3}, errHigh={4}, readers=[{5}]",
|
||||||
|
currentOp, waterMeters == null ? -1 : waterMeters.Count, refVolume, errLimLo, errLimHi,
|
||||||
|
DescribeReaders(regReaders));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate void EntryFormDlgt(EntryForm myRef);
|
delegate void EntryFormDlgt(EntryForm myRef);
|
||||||
|
|
||||||
|
string DescribeReaders(IRegReader[] readers)
|
||||||
|
{
|
||||||
|
if (readers == null) return "<null>";
|
||||||
|
|
||||||
|
var descriptions = new List<string>();
|
||||||
|
for (int index = 0; index < readers.Length; index++)
|
||||||
|
{
|
||||||
|
IRegReader reader = readers[index];
|
||||||
|
if (reader == null)
|
||||||
|
{
|
||||||
|
descriptions.Add(index + ":<null>");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PoseidonReader poseidonReader = reader as PoseidonReader;
|
||||||
|
descriptions.Add(poseidonReader == null
|
||||||
|
? index + ":" + reader.GetType().Name
|
||||||
|
: string.Format("{0}:{1}(COM{2},state={3})", index, poseidonReader.Name,
|
||||||
|
poseidonReader.ComPortNr, poseidonReader.CurrentOp));
|
||||||
|
}
|
||||||
|
return string.Join("; ", descriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
int EnabledWaterMetersCount()
|
||||||
|
{
|
||||||
|
if (disabled == null) return TBF.Data.WMsCount;
|
||||||
|
int count = 0;
|
||||||
|
for (int index = 0; index < disabled.Length; index++)
|
||||||
|
if (!disabled[index]) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
///
|
///
|
||||||
void OpenBeginningDlg(EntryForm myRef)
|
void OpenBeginningDlg(EntryForm myRef)
|
||||||
{
|
{
|
||||||
if (ShowForm == 0)
|
if (ShowForm == 0)
|
||||||
return;
|
return;
|
||||||
|
log.DebugFormat("Poseidon data-entry: creating CycleBeginningForm; configuredWMs={0}, batchWMs={1}",
|
||||||
|
TBF.Data.WMsCount, waterMeters == null ? -1 : waterMeters.Count);
|
||||||
myRef.modelessDlg = new CycleBeginningForm(TBF.Data.WMsCount, myRef.entryFormCfg,
|
myRef.modelessDlg = new CycleBeginningForm(TBF.Data.WMsCount, myRef.entryFormCfg,
|
||||||
ProcessData.SelectedProcedure.OrderInfo != null ? ProcessData.SelectedProcedure.OrderInfo.POName : string.Empty);
|
ProcessData.SelectedProcedure.OrderInfo != null ? ProcessData.SelectedProcedure.OrderInfo.POName : string.Empty);
|
||||||
(myRef.modelessDlg as CycleBeginningForm)?.AutoClickOkAfterDelay();
|
(myRef.modelessDlg as CycleBeginningForm)?.AutoClickOkAfterDelay();
|
||||||
modelessDlg.Show();
|
modelessDlg.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The state machine runs on a worker thread. A synchronous Invoke can
|
||||||
|
// deadlock it while the UI is waiting for the next state-machine tick.
|
||||||
|
void BeginOpenDialog(string dialogName, EntryFormDlgt openDialog)
|
||||||
|
{
|
||||||
|
modelessDialogOpening = true;
|
||||||
|
modelessDialogWaitLogged = false;
|
||||||
|
log.InfoFormat("Poseidon data-entry: queueing dialog={0}, operation={1}, showForm={2}, readers=[{3}]",
|
||||||
|
dialogName, currentOp, ShowForm, DescribeReaders(regReaders));
|
||||||
|
Program.MainWnd.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
log.DebugFormat("Poseidon data-entry: opening dialog={0}, operation={1}", dialogName, currentOp);
|
||||||
|
openDialog(this);
|
||||||
|
log.InfoFormat("Poseidon data-entry: dialog opened={0}, operation={1}, formType={2}",
|
||||||
|
dialogName, currentOp, modelessDlg == null ? "<null>" : modelessDlg.GetType().Name);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
modelessDialogOpening = false;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
///
|
///
|
||||||
void OpenTestStartStatesDlg(EntryForm myRef)
|
void OpenTestStartStatesDlg(EntryForm myRef)
|
||||||
{
|
{
|
||||||
if (ShowForm == 0)
|
if (ShowForm == 0)
|
||||||
return;
|
return;
|
||||||
|
log.DebugFormat("Poseidon data-entry: creating TestStartEndForm START; configuredWMs={0}, enabledWMs={1}, batchWMs={2}, readers=[{3}]",
|
||||||
|
TBF.Data.WMsCount, EnabledWaterMetersCount(), waterMeters == null ? -1 : waterMeters.Count, DescribeReaders(regReaders));
|
||||||
myRef.modelessDlg = new TestStartEndForm(myRef.waterMeters.Count, myRef.regReaders, disabled);
|
myRef.modelessDlg = new TestStartEndForm(myRef.waterMeters.Count, myRef.regReaders, disabled);
|
||||||
modelessDlg.Show();
|
modelessDlg.Show();
|
||||||
|
log.DebugFormat("Poseidon data-entry: TestStartEndForm START visible={0}, handleCreated={1}",
|
||||||
|
modelessDlg.Visible, modelessDlg.IsHandleCreated);
|
||||||
}
|
}
|
||||||
///
|
///
|
||||||
void OpenTestEndStatesDlg(EntryForm myRef)
|
void OpenTestEndStatesDlg(EntryForm myRef)
|
||||||
{
|
{
|
||||||
if (ShowForm == 0)
|
if (ShowForm == 0)
|
||||||
return;
|
return;
|
||||||
|
log.DebugFormat("Poseidon data-entry: creating TestStartEndForm END; configuredWMs={0}, enabledWMs={1}, batchWMs={2}, refVolume={3}, errLow={4}, errHigh={5}, readers=[{6}]",
|
||||||
|
TBF.Data.WMsCount, EnabledWaterMetersCount(), waterMeters == null ? -1 : waterMeters.Count,
|
||||||
|
refVolume, errLimLo, errLimHi, DescribeReaders(regReaders));
|
||||||
myRef.modelessDlg = new TestStartEndForm(TBF.Data.WMsCount, myRef.regReaders, wmStartStateStr, disabled, refVolume, errLimLo, errLimHi);
|
myRef.modelessDlg = new TestStartEndForm(TBF.Data.WMsCount, myRef.regReaders, wmStartStateStr, disabled, refVolume, errLimLo, errLimHi);
|
||||||
modelessDlg.Show();
|
modelessDlg.Show();
|
||||||
|
log.DebugFormat("Poseidon data-entry: TestStartEndForm END visible={0}, handleCreated={1}",
|
||||||
|
modelessDlg.Visible, modelessDlg.IsHandleCreated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Start this operation</summary>
|
/// <summary>Start this operation</summary>
|
||||||
@@ -204,6 +287,12 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
readDataOp = ReadDataOp.None;
|
readDataOp = ReadDataOp.None;
|
||||||
readAndSetDataToMeters = false;
|
readAndSetDataToMeters = false;
|
||||||
filedDataToMeters = false;
|
filedDataToMeters = false;
|
||||||
|
poseidonPhaseRunner = null;
|
||||||
|
poseidonPhaseReaders = null;
|
||||||
|
sendStartPhaseInitialized = false;
|
||||||
|
sendStartPhaseIterations = 0;
|
||||||
|
modelessDialogOpening = false;
|
||||||
|
modelessDialogWaitLogged = false;
|
||||||
|
|
||||||
if (ShowForm == 0)
|
if (ShowForm == 0)
|
||||||
return ;
|
return ;
|
||||||
@@ -215,7 +304,7 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
//ReadAndSetDataToMeters();
|
//ReadAndSetDataToMeters();
|
||||||
if (ProcessData.SelectedProcedure.OrderInfo == null)
|
if (ProcessData.SelectedProcedure.OrderInfo == null)
|
||||||
{
|
{
|
||||||
Program.MainWnd.Invoke(new EntryFormDlgt(OpenBeginningDlg), this);
|
BeginOpenDialog("CycleBeginningForm", OpenBeginningDlg);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -225,10 +314,10 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CurrentOp.ReadDatastream_StartStates:
|
case CurrentOp.ReadDatastream_StartStates:
|
||||||
Program.MainWnd.Invoke(new EntryFormDlgt(OpenTestStartStatesDlg), this);
|
BeginOpenDialog("TestStartEndForm.START", OpenTestStartStatesDlg);
|
||||||
break;
|
break;
|
||||||
case CurrentOp.ReadDatastream_EndStates:
|
case CurrentOp.ReadDatastream_EndStates:
|
||||||
Program.MainWnd.Invoke(new EntryFormDlgt(OpenTestEndStatesDlg), this);
|
BeginOpenDialog("TestStartEndForm.END", OpenTestEndStatesDlg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,6 +329,17 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
/// <returns>Event.ResultsPrinted</returns>
|
/// <returns>Event.ResultsPrinted</returns>
|
||||||
public Event Run()
|
public Event Run()
|
||||||
{
|
{
|
||||||
|
if (modelessDialogOpening)
|
||||||
|
{
|
||||||
|
if (!modelessDialogWaitLogged)
|
||||||
|
{
|
||||||
|
log.WarnFormat("Poseidon data-entry: state machine waiting for dialog open; operation={0}, readers=[{1}]",
|
||||||
|
currentOp, DescribeReaders(regReaders));
|
||||||
|
modelessDialogWaitLogged = true;
|
||||||
|
}
|
||||||
|
return Event.ModelessFormIsOpen;
|
||||||
|
}
|
||||||
|
|
||||||
if (!readAndSetDataToMeters) // run until not finished
|
if (!readAndSetDataToMeters) // run until not finished
|
||||||
readAndSetDataToMeters = ReadAndSetDataToMeters();
|
readAndSetDataToMeters = ReadAndSetDataToMeters();
|
||||||
|
|
||||||
@@ -268,10 +368,16 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
{
|
{
|
||||||
dlg.WMStartState[item] = poseidonReader.BeginWMState;
|
dlg.WMStartState[item] = poseidonReader.BeginWMState;
|
||||||
dlg.WMStartStateStr[item] = poseidonReader.BeginWMState.ToString();
|
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)
|
if (currentOp == CurrentOp.ReadDatastream_EndStates)
|
||||||
|
{
|
||||||
dlg.WMEndState[item] = poseidonReader.EndWMState;
|
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,38 +432,44 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (modelessDlg is TestStartEndForm && currentOp == CurrentOp.ReadDatastream_StartStates)
|
else if (modelessDlg is TestStartEndForm)
|
||||||
{
|
{
|
||||||
/// Fixed start test - start
|
StoreAcceptedDialogValues((TestStartEndForm)modelessDlg);
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resultSaved = true;
|
resultSaved = true;
|
||||||
modelessDlg = null;
|
modelessDlg = null;
|
||||||
|
modelessDialogOpening = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event.ModelessFormClosed; /// Form closed
|
return Event.ModelessFormClosed; /// Form closed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StoreAcceptedDialogValues(TestStartEndForm dlg)
|
||||||
|
{
|
||||||
|
if (dlg == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (currentOp == CurrentOp.ReadDatastream_StartStates)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < dlg.WaterMetersCount; i++)
|
||||||
|
{
|
||||||
|
wmStartState[i] = dlg.WMStartState[i];
|
||||||
|
wmStartStateStr[i] = dlg.WMStartStateStr[i];
|
||||||
|
log.InfoFormat("Poseidon dialog accepted: phase=Start, WM{0}, text='{1}', value={2}",
|
||||||
|
i + 1, wmStartStateStr[i], wmStartState[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (currentOp == CurrentOp.ReadDatastream_EndStates)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < dlg.WaterMetersCount; i++)
|
||||||
|
{
|
||||||
|
wmEndState[i] = dlg.WMEndState[i];
|
||||||
|
log.InfoFormat("Poseidon dialog accepted: phase=End, WM{0}, value={1}",
|
||||||
|
i + 1, wmEndState[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stop this operation</summary>
|
/// <summary>Stop this operation</summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
@@ -369,6 +481,7 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
currentOp = CurrentOp.None;
|
currentOp = CurrentOp.None;
|
||||||
readAndSetDataToMeters = false;
|
readAndSetDataToMeters = false;
|
||||||
filedDataToMeters = false;
|
filedDataToMeters = false;
|
||||||
|
sendStartPhaseInitialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ShowForm
|
public int ShowForm
|
||||||
@@ -389,88 +502,108 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
bool bOperationSuccess = false;
|
bool bOperationSuccess = false;
|
||||||
if (currentOp == CurrentOp.SendStartDataStream)
|
if (currentOp == CurrentOp.SendStartDataStream)
|
||||||
{
|
{
|
||||||
bool finishedReading = regReaders == null; // we can work only with register readers
|
if (!sendStartPhaseInitialized)
|
||||||
while (!finishedReading) //TODO BUMI lock - fuck ?
|
|
||||||
{
|
{
|
||||||
bool bAllReadersFinished = true;
|
sendStartPhaseInitialized = true;
|
||||||
foreach (var iRegReader in regReaders )
|
sendStartPhaseIterations = 0;
|
||||||
|
poseidonPhaseStartedAt = DateTime.UtcNow;
|
||||||
|
poseidonNextProgressLogAt = poseidonPhaseStartedAt.AddSeconds(5);
|
||||||
|
log.InfoFormat("Poseidon send-start phase started; readers=[{0}]", DescribeReaders(regReaders));
|
||||||
|
if (regReaders != null)
|
||||||
{
|
{
|
||||||
if (iRegReader is PoseidonReader)
|
foreach (IRegReader regReader in regReaders)
|
||||||
{
|
{
|
||||||
PoseidonReader poseidonReader = (iRegReader as PoseidonReader);
|
PoseidonReader poseidonReader = regReader as PoseidonReader;
|
||||||
|
if (poseidonReader == null) continue;
|
||||||
poseidonReader.SetCliLogging(CliLogging);
|
poseidonReader.SetCliLogging(CliLogging);
|
||||||
|
poseidonReader.SetCurrentOp(PoseidonReader.CurrentPoseidonOp.SendStartDataStream);
|
||||||
if (poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.SendStartDataStream_Done
|
log.DebugFormat("Poseidon send-start: armed reader={0}, COM={1}, state={2}",
|
||||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.SendStartDataStream_Runing)
|
poseidonReader.Name, poseidonReader.ComPortNr, poseidonReader.CurrentOp);
|
||||||
{
|
|
||||||
poseidonReader.SetCurrentOp(PoseidonReader.CurrentPoseidonOp.SendStartDataStream);
|
|
||||||
}
|
|
||||||
/// Send start data stream
|
|
||||||
poseidonReader.Run();
|
|
||||||
readDataOp = ReadDataOp.Start;
|
|
||||||
if (!(poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Done
|
|
||||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error))
|
|
||||||
{
|
|
||||||
bAllReadersFinished = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Wait for all readers to finish
|
|
||||||
if (bAllReadersFinished)
|
|
||||||
{
|
|
||||||
finishedReading = true;
|
|
||||||
readDataOp = ReadDataOp.Done;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.Threading.Thread.Sleep(10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bOperationSuccess = true;
|
sendStartPhaseIterations++;
|
||||||
|
bool allReadersFinished = true;
|
||||||
|
if (regReaders != null)
|
||||||
|
{
|
||||||
|
foreach (IRegReader regReader in regReaders)
|
||||||
|
{
|
||||||
|
PoseidonReader poseidonReader = regReader as PoseidonReader;
|
||||||
|
if (poseidonReader == null) continue;
|
||||||
|
poseidonReader.Run();
|
||||||
|
readDataOp = ReadDataOp.Start;
|
||||||
|
if (poseidonReader.CurrentOp != PoseidonReader.CurrentPoseidonOp.Done
|
||||||
|
&& poseidonReader.CurrentOp != PoseidonReader.CurrentPoseidonOp.Error)
|
||||||
|
allReadersFinished = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allReadersFinished)
|
||||||
|
{
|
||||||
|
readDataOp = ReadDataOp.Done;
|
||||||
|
log.InfoFormat("Poseidon send-start phase completed; iterations={0}, elapsedMs={1}",
|
||||||
|
sendStartPhaseIterations, (long)(DateTime.UtcNow - poseidonPhaseStartedAt).TotalMilliseconds);
|
||||||
|
sendStartPhaseInitialized = false;
|
||||||
|
bOperationSuccess = true;
|
||||||
|
}
|
||||||
|
else if (DateTime.UtcNow >= poseidonNextProgressLogAt)
|
||||||
|
{
|
||||||
|
log.WarnFormat("Poseidon send-start phase waiting; iterations={0}, elapsedMs={1}, readers=[{2}]",
|
||||||
|
sendStartPhaseIterations, (long)(DateTime.UtcNow - poseidonPhaseStartedAt).TotalMilliseconds,
|
||||||
|
DescribeReaders(regReaders));
|
||||||
|
poseidonNextProgressLogAt = DateTime.UtcNow.AddSeconds(5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ((currentOp == CurrentOp.ReadDatastream_StartStates)
|
else if ((currentOp == CurrentOp.ReadDatastream_StartStates)
|
||||||
|| (currentOp == CurrentOp.ReadDatastream_EndStates))
|
|| (currentOp == CurrentOp.ReadDatastream_EndStates))
|
||||||
{
|
{
|
||||||
bool finishedReading = regReaders == null; // we can work only with register readers
|
if (poseidonPhaseRunner == null)
|
||||||
while (!finishedReading) //TODO BUMI lock - fuck ?
|
|
||||||
{
|
{
|
||||||
bool bAllReadersFinished = true;
|
poseidonPhaseReaders = new List<IPoseidonReadOperation>();
|
||||||
foreach (var iRegReader in regReaders )
|
if (regReaders != null)
|
||||||
{
|
{
|
||||||
if (iRegReader is PoseidonReader)
|
foreach (IRegReader regReader in regReaders)
|
||||||
{
|
{
|
||||||
PoseidonReader poseidonReader = (iRegReader as PoseidonReader);
|
PoseidonReader poseidonReader = regReader as PoseidonReader;
|
||||||
if(poseidonReader == null)
|
if (poseidonReader == null) continue;
|
||||||
continue;
|
|
||||||
poseidonReader.SetCliLogging(CliLogging);
|
poseidonReader.SetCliLogging(CliLogging);
|
||||||
if (!(poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.ReadDatastream_Done
|
poseidonPhaseReaders.Add(new PoseidonReaderOperation(poseidonReader));
|
||||||
|| 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 (bAllReadersFinished)
|
poseidonPhaseRunner = new PoseidonReadPhaseRunner(
|
||||||
{
|
currentOp == CurrentOp.ReadDatastream_StartStates);
|
||||||
finishedReading = true;
|
poseidonPhaseStartedAt = DateTime.UtcNow;
|
||||||
}
|
poseidonNextProgressLogAt = poseidonPhaseStartedAt.AddSeconds(5);
|
||||||
else
|
log.InfoFormat("Poseidon phase started: phase={0}, readers={1}, detail=[{2}]",
|
||||||
{
|
currentOp, poseidonPhaseReaders.Count, DescribeReaders(regReaders));
|
||||||
System.Threading.Thread.Sleep(10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bOperationSuccess = true;
|
bool allReadersFinished = poseidonPhaseRunner.RunIteration(poseidonPhaseReaders);
|
||||||
|
foreach (IPoseidonReadOperation poseidonReader in poseidonPhaseReaders)
|
||||||
|
if (poseidonReader.HasError)
|
||||||
|
log.ErrorFormat("Poseidon read: {0} completed with Error during {1}", poseidonReader.Name, currentOp);
|
||||||
|
|
||||||
|
if (allReadersFinished)
|
||||||
|
{
|
||||||
|
log.InfoFormat("Poseidon phase completed: phase={0}, iterations={1}, elapsedMs={2}",
|
||||||
|
currentOp, poseidonPhaseRunner.IterationCount,
|
||||||
|
(long)(DateTime.UtcNow - poseidonPhaseStartedAt).TotalMilliseconds);
|
||||||
|
poseidonPhaseRunner = null;
|
||||||
|
poseidonPhaseReaders = null;
|
||||||
|
bOperationSuccess = true;
|
||||||
|
}
|
||||||
|
else if (DateTime.UtcNow >= poseidonNextProgressLogAt)
|
||||||
|
{
|
||||||
|
var pendingReaders = new List<string>();
|
||||||
|
foreach (IPoseidonReadOperation poseidonReader in poseidonPhaseReaders)
|
||||||
|
if (!poseidonReader.IsFinished) pendingReaders.Add(poseidonReader.Name);
|
||||||
|
log.WarnFormat("Poseidon phase waiting: phase={0}, iterations={1}, elapsedMs={2}, pending={3}",
|
||||||
|
currentOp, poseidonPhaseRunner.IterationCount,
|
||||||
|
(long)(DateTime.UtcNow - poseidonPhaseStartedAt).TotalMilliseconds,
|
||||||
|
string.Join(",", pendingReaders));
|
||||||
|
poseidonNextProgressLogAt = DateTime.UtcNow.AddSeconds(5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bOperationSuccess;
|
return bOperationSuccess;
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
|
|
||||||
private void SetUiBusy(bool busy, long deltaTime = -1)
|
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
|
// show wait cursor for form and children
|
||||||
this.UseWaitCursor = busy;
|
this.UseWaitCursor = busy;
|
||||||
|
|
||||||
@@ -296,6 +298,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
BeginInvoke(new Action(() => UpdateValues(stratValue, enableEdit)));
|
BeginInvoke(new Action(() => UpdateValues(stratValue, enableEdit)));
|
||||||
return;
|
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++)
|
for (int i = 0; i < TextBoxesCount; i++)
|
||||||
{
|
{
|
||||||
@@ -320,6 +324,7 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
if (enableEdit)
|
if (enableEdit)
|
||||||
{
|
{
|
||||||
SetUiBusy(false, deltaTime);
|
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,
|
// if you also need to enable/disable editing, do it here,
|
||||||
// it's now safely on the UI thread.
|
// it's now safely on the UI thread.
|
||||||
@@ -348,6 +353,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
if (endTextBoxes[i].Visible && endTextBoxes[i].Enabled)
|
if (endTextBoxes[i].Visible && endTextBoxes[i].Enabled)
|
||||||
{
|
{
|
||||||
WMEndState[i] = Utils.ParseUDouble(endTextBoxes[i].Text);
|
WMEndState[i] = Utils.ParseUDouble(endTextBoxes[i].Text);
|
||||||
|
log.InfoFormat("Poseidon dialog OK: phase=End, WM{0}, enteredText='{1}', parsedValue={2}",
|
||||||
|
i + 1, endTextBoxes[i].Text, WMEndState[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,6 +366,8 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
|||||||
{
|
{
|
||||||
WMStartStateStr[i] = startTextBoxes[i].Text;
|
WMStartStateStr[i] = startTextBoxes[i].Text;
|
||||||
WMStartState[i] = Utils.ParseUDouble(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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ using System.Linq;
|
|||||||
using log4net;
|
using log4net;
|
||||||
using Results.Entities;
|
using Results.Entities;
|
||||||
using TBF.Rig.GenericDevices;
|
using TBF.Rig.GenericDevices;
|
||||||
|
using TBF.Rig.Sequences;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
|
||||||
namespace TBF.Rig.DataEntry.Uni
|
namespace TBF.Rig.DataEntry.Uni
|
||||||
{
|
{
|
||||||
@@ -23,21 +25,22 @@ namespace TBF.Rig.DataEntry.Uni
|
|||||||
? new IRegReader[0]
|
? new IRegReader[0]
|
||||||
: readers.ToArray();
|
: readers.ToArray();
|
||||||
long enabledHeads = Program.LocalSettings.OptoHeadsEnabled;
|
long enabledHeads = Program.LocalSettings.OptoHeadsEnabled;
|
||||||
|
SmartReaderSelection.EnsureConfiguredReaders(Program.LocalSettings, ProcessData.SmartHeadsUni);
|
||||||
|
|
||||||
IRegReader[] selected = allSlots
|
IRegReader[] selected = allSlots
|
||||||
.Where(reader => IsSelectedForDataEntry(reader, waterMeters, enabledHeads))
|
.Where(reader => IsSelectedForDataEntry(reader, waterMeters, Program.LocalSettings, enabledHeads))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
string selectedPositions = string.Join(",", selected.Select(reader => reader.Position));
|
string selectedPositions = string.Join(",", selected.Select(reader => reader.Position));
|
||||||
string skippedPositions = string.Join(",", allSlots
|
string skippedPositions = string.Join(",", allSlots
|
||||||
.Where(reader => reader != null &&
|
.Where(reader => reader != null &&
|
||||||
!IsSelectedForDataEntry(reader, waterMeters, enabledHeads))
|
!IsSelectedForDataEntry(reader, waterMeters, Program.LocalSettings, enabledHeads))
|
||||||
.Select(reader => reader.Position));
|
.Select(reader => reader.Position));
|
||||||
|
|
||||||
log.DebugFormat(
|
log.DebugFormat(
|
||||||
"DATA_ENTRY_READER_FILTER Operation={0}, OptoHeadsEnabled=0x{1:X12}, " +
|
"DATA_ENTRY_READER_FILTER Operation={0}, LegacyOptoHeadsEnabled=0x{1:X12}, " +
|
||||||
"Slots={2}, Readers={3}, Selected={4}, SelectedPositions=[{5}], " +
|
"Slots={2}, Readers={3}, Selected={4}, SelectedPositions=[{5}], " +
|
||||||
"SkippedPositions=[{6}], SelectionRule=SmartMeterMaskOrPrecheckedWaterMeter",
|
"SkippedPositions=[{6}], SelectionRule=SmartReaderFamilySelectionOrPrecheckedWaterMeter",
|
||||||
operation,
|
operation,
|
||||||
enabledHeads,
|
enabledHeads,
|
||||||
allSlots.Length,
|
allSlots.Length,
|
||||||
@@ -56,8 +59,8 @@ namespace TBF.Rig.DataEntry.Uni
|
|||||||
reader.GetType().Name,
|
reader.GetType().Name,
|
||||||
reader.Position,
|
reader.Position,
|
||||||
reader.DebugLevel,
|
reader.DebugLevel,
|
||||||
IsSelectedForDataEntry(reader, waterMeters, enabledHeads),
|
IsSelectedForDataEntry(reader, waterMeters, Program.LocalSettings, enabledHeads),
|
||||||
GetSelectionDetails(reader, waterMeters, enabledHeads));
|
GetSelectionDetails(reader, waterMeters, Program.LocalSettings, enabledHeads));
|
||||||
}
|
}
|
||||||
|
|
||||||
return selected;
|
return selected;
|
||||||
@@ -67,6 +70,15 @@ namespace TBF.Rig.DataEntry.Uni
|
|||||||
IRegReader reader,
|
IRegReader reader,
|
||||||
IList<WaterMeter> waterMeters,
|
IList<WaterMeter> waterMeters,
|
||||||
long enabledHeads)
|
long enabledHeads)
|
||||||
|
{
|
||||||
|
return IsSelectedForDataEntry(reader, waterMeters, null, enabledHeads);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsSelectedForDataEntry(
|
||||||
|
IRegReader reader,
|
||||||
|
IList<WaterMeter> waterMeters,
|
||||||
|
LocalSettings settings,
|
||||||
|
long enabledHeads)
|
||||||
{
|
{
|
||||||
if (reader == null)
|
if (reader == null)
|
||||||
return false;
|
return false;
|
||||||
@@ -87,6 +99,9 @@ namespace TBF.Rig.DataEntry.Uni
|
|||||||
|
|
||||||
if (reader is ISmartMeterReader)
|
if (reader is ISmartMeterReader)
|
||||||
{
|
{
|
||||||
|
if (settings != null && settings.SmartReaderSelections != null)
|
||||||
|
return SmartReaderSelection.IsEnabled(settings, reader);
|
||||||
|
|
||||||
int position0 = reader.Position - 1;
|
int position0 = reader.Position - 1;
|
||||||
return position0 >= 0 && position0 < 63 &&
|
return position0 >= 0 && position0 < 63 &&
|
||||||
(enabledHeads & (1L << position0)) != 0;
|
(enabledHeads & (1L << position0)) != 0;
|
||||||
@@ -115,6 +130,7 @@ namespace TBF.Rig.DataEntry.Uni
|
|||||||
private static string GetSelectionDetails(
|
private static string GetSelectionDetails(
|
||||||
IRegReader reader,
|
IRegReader reader,
|
||||||
IList<WaterMeter> waterMeters,
|
IList<WaterMeter> waterMeters,
|
||||||
|
LocalSettings settings,
|
||||||
long enabledHeads)
|
long enabledHeads)
|
||||||
{
|
{
|
||||||
#if IPERL
|
#if IPERL
|
||||||
@@ -136,6 +152,15 @@ namespace TBF.Rig.DataEntry.Uni
|
|||||||
#endif
|
#endif
|
||||||
if (reader is ISmartMeterReader)
|
if (reader is ISmartMeterReader)
|
||||||
{
|
{
|
||||||
|
if (settings != null && settings.SmartReaderSelections != null)
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
", SelectionSource=SmartReaderFamily, Family={0}, ReaderKey={1}, Enabled={2}",
|
||||||
|
SmartReaderSelection.GetFamilyKey(reader),
|
||||||
|
SmartReaderSelection.GetReaderKey(reader),
|
||||||
|
SmartReaderSelection.IsEnabled(settings, reader));
|
||||||
|
}
|
||||||
|
|
||||||
int position0 = reader.Position - 1;
|
int position0 = reader.Position - 1;
|
||||||
bool maskBit = position0 >= 0 && position0 < 63 &&
|
bool maskBit = position0 >= 0 && position0 < 63 &&
|
||||||
(enabledHeads & (1L << position0)) != 0;
|
(enabledHeads & (1L << position0)) != 0;
|
||||||
|
|||||||
@@ -26,10 +26,8 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
private const int DataEntryCommandTimeoutMs = 5000;
|
private const int DataEntryCommandTimeoutMs = 5000;
|
||||||
private const int DefaultDataEntryOpticalTimeoutMs = 3000;
|
private const int DefaultDataEntryOpticalTimeoutMs = 3000;
|
||||||
private const int MaxStoredSamples = 40000;
|
private const int MaxStoredSamples = 40000;
|
||||||
private const long RawVolumeModulo = 0x1000000L;
|
private const long RawVolumeModulo = 0x100000000L;
|
||||||
private const long RawVolumeHalfRange = RawVolumeModulo / 2;
|
private const long RawVolumeHalfRange = RawVolumeModulo / 2;
|
||||||
private const long RawTimestampModulo = 0x100000000L;
|
|
||||||
private const long RawTimestampHalfRange = RawTimestampModulo / 2;
|
|
||||||
private static readonly ILog log = LogManager.GetLogger(typeof(AllyMeterReader));
|
private static readonly ILog log = LogManager.GetLogger(typeof(AllyMeterReader));
|
||||||
|
|
||||||
private readonly object commandSync = new object();
|
private readonly object commandSync = new object();
|
||||||
@@ -42,14 +40,13 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
private AllyCommandService commandService;
|
private AllyCommandService commandService;
|
||||||
private SerialPort opticalPort;
|
private SerialPort opticalPort;
|
||||||
private bool streamEnabled;
|
private bool streamEnabled;
|
||||||
|
private bool opticalVerificationOutputActive;
|
||||||
private bool operationActive;
|
private bool operationActive;
|
||||||
private bool hasPreviousRawVolume;
|
private bool hasPreviousRawVolume;
|
||||||
private bool hasPreviousRawTimestamp;
|
|
||||||
private bool hasTestStartSample;
|
private bool hasTestStartSample;
|
||||||
private uint previousRawVolume;
|
private uint previousRawVolume;
|
||||||
private uint previousRawTimestamp;
|
|
||||||
private long extendedRawVolume;
|
private long extendedRawVolume;
|
||||||
private long extendedRawTimestamp;
|
private DateTime? firstSampleReceivedAtUtc;
|
||||||
private double beginWMState;
|
private double beginWMState;
|
||||||
private double endWMState;
|
private double endWMState;
|
||||||
private double timestampSecStart;
|
private double timestampSecStart;
|
||||||
@@ -94,6 +91,15 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
get { return allyCfg == null ? AllyMeterSize.AutoDetect : allyCfg.ConfiguredMeterSize; }
|
get { return allyCfg == null ? AllyMeterSize.AutoDetect : allyCfg.ConfiguredMeterSize; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsOpticalVolumeConversionConfigured
|
||||||
|
{
|
||||||
|
// The ALLY C6 accumulator is always expressed in quarter millilitres.
|
||||||
|
// Unlike calibration-factor limits, decoding the optical accumulator does
|
||||||
|
// not depend on the nominal meter size. Keeping this true also allows a
|
||||||
|
// bench configured with AutoDetect to persist start/end states.
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyList<AllyOpticalSample> OpticalSamples
|
public IReadOnlyList<AllyOpticalSample> OpticalSamples
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -132,12 +138,25 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
log.DebugFormat(
|
||||||
|
"ALLY_OPTO RX COM{0}: bytes={1}, ASCII='{2}', HEX={3}",
|
||||||
|
allyCfg.OptoComPortNr,
|
||||||
|
text.Length,
|
||||||
|
ToLogText(text),
|
||||||
|
ToHex(text));
|
||||||
ProcessOpticalText(text);
|
ProcessOpticalText(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
CommFailed = true;
|
CommFailed = true;
|
||||||
log.Error("ALLY optical stream read failed.", ex);
|
log.ErrorFormat(
|
||||||
|
"ALLY_OPTO read failed on COM{0} (open={1}, streaming={2}). {3}",
|
||||||
|
allyCfg == null ? 0 : allyCfg.OptoComPortNr,
|
||||||
|
opticalPort != null && opticalPort.IsOpen,
|
||||||
|
streamEnabled,
|
||||||
|
ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,30 +244,66 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
|
|
||||||
public void StartDataStreamProcessing()
|
public void StartDataStreamProcessing()
|
||||||
{
|
{
|
||||||
GetOpticalVolumeLitersPerRawUnit();
|
StartDataStreamProcessing(true);
|
||||||
|
}
|
||||||
|
|
||||||
lock (opticalSync)
|
private void StartDataStreamProcessing(bool requireVolumeConversion)
|
||||||
|
{
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_OPTO start requested: COM{0}, {1} Bd, 8N1, meter size={2}, debug={3}",
|
||||||
|
allyCfg == null ? 0 : allyCfg.OptoComPortNr,
|
||||||
|
allyCfg == null ? 0 : allyCfg.OptoBaudRate,
|
||||||
|
ConfiguredMeterSize,
|
||||||
|
DebugLevel);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (streamEnabled)
|
if (requireVolumeConversion)
|
||||||
return;
|
GetOpticalVolumeLitersPerRawUnit();
|
||||||
|
|
||||||
opticalSamples.Clear();
|
lock (opticalSync)
|
||||||
opticalBuffer.Clear();
|
|
||||||
lastOpticalLine = string.Empty;
|
|
||||||
ResetVolumeState();
|
|
||||||
if (DebugLevel == DebugMode.Normal)
|
|
||||||
{
|
{
|
||||||
opticalPort = new SerialPort(
|
if (streamEnabled)
|
||||||
"COM" + allyCfg.OptoComPortNr,
|
{
|
||||||
allyCfg.OptoBaudRate,
|
log.Debug("ALLY_OPTO start ignored: stream is already active.");
|
||||||
Parity.None,
|
return;
|
||||||
8,
|
}
|
||||||
StopBits.One);
|
|
||||||
opticalPort.Open();
|
|
||||||
opticalPort.DiscardInBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
streamEnabled = true;
|
opticalSamples.Clear();
|
||||||
|
opticalBuffer.Clear();
|
||||||
|
lastOpticalLine = string.Empty;
|
||||||
|
ResetVolumeState();
|
||||||
|
if (DebugLevel == DebugMode.Normal)
|
||||||
|
{
|
||||||
|
opticalPort = new SerialPort(
|
||||||
|
"COM" + allyCfg.OptoComPortNr,
|
||||||
|
allyCfg.OptoBaudRate,
|
||||||
|
Parity.None,
|
||||||
|
8,
|
||||||
|
StopBits.One);
|
||||||
|
opticalPort.Open();
|
||||||
|
opticalPort.DiscardInBuffer();
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_OPTO opened {0}: baud={1}, dataBits={2}, parity={3}, stopBits={4}",
|
||||||
|
opticalPort.PortName,
|
||||||
|
opticalPort.BaudRate,
|
||||||
|
opticalPort.DataBits,
|
||||||
|
opticalPort.Parity,
|
||||||
|
opticalPort.StopBits);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.Info("ALLY_OPTO simulation mode: physical optical COM port was not opened.");
|
||||||
|
}
|
||||||
|
|
||||||
|
streamEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CommFailed = true;
|
||||||
|
log.Error("ALLY_OPTO start failed.", ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,12 +313,18 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
{
|
{
|
||||||
streamEnabled = false;
|
streamEnabled = false;
|
||||||
if (opticalPort == null)
|
if (opticalPort == null)
|
||||||
|
{
|
||||||
|
log.Debug("ALLY_OPTO stopped: no optical COM port was open.");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (opticalPort.IsOpen)
|
if (opticalPort.IsOpen)
|
||||||
|
{
|
||||||
|
log.InfoFormat("ALLY_OPTO closing {0}.", opticalPort.PortName);
|
||||||
opticalPort.Close();
|
opticalPort.Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -363,6 +424,46 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
ExecuteCommand(service => service.SetDiagnosticLed(mode, timeoutMs));
|
ExecuteCommand(service => service.SetDiagnosticLed(mode, timeoutMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsFactorySealed(int timeoutMs)
|
||||||
|
{
|
||||||
|
if (DebugLevel != DebugMode.Normal)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ExecuteCommand(service => service.IsFactorySealed(timeoutMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsealFactory(int timeoutMs)
|
||||||
|
{
|
||||||
|
ExecuteCommand(service => service.UnsealFactory(timeoutMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllyFactoryUnsealData ReadFactoryUnsealData(int timeoutMs)
|
||||||
|
{
|
||||||
|
if (DebugLevel != DebugMode.Normal)
|
||||||
|
{
|
||||||
|
// Keep the complete test-method workflow executable without a
|
||||||
|
// physical meter. The simulated register is already unsealed.
|
||||||
|
return new AllyFactoryUnsealData(
|
||||||
|
false,
|
||||||
|
"SIMULATED-ALLY",
|
||||||
|
"SIMULATED",
|
||||||
|
"00000000",
|
||||||
|
0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExecuteCommand(service => service.ReadFactoryUnsealData(timeoutMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsealFactory(AllyFactoryUnsealData data, int timeoutMs)
|
||||||
|
{
|
||||||
|
ExecuteCommand(service => service.UnsealFactory(data, timeoutMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SealFactory(int timeoutMs)
|
||||||
|
{
|
||||||
|
ExecuteCommand(service => service.SealFactory(timeoutMs));
|
||||||
|
}
|
||||||
|
|
||||||
public double ResetCalibrationFactor(int timeoutMs)
|
public double ResetCalibrationFactor(int timeoutMs)
|
||||||
{
|
{
|
||||||
double factor = GetResetCalibrationFactorPercent();
|
double factor = GetResetCalibrationFactorPercent();
|
||||||
@@ -615,6 +716,18 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessOpticalText(string text)
|
private void ProcessOpticalText(string text)
|
||||||
|
{
|
||||||
|
ProcessOpticalText(text, DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kept internal for deterministic MSTest coverage of the host-receipt time
|
||||||
|
// used by ALLY C6 telegrams. C6 has no device timestamp to roll over.
|
||||||
|
internal void ProcessOpticalTextForTest(string text, DateTime receivedAtUtc)
|
||||||
|
{
|
||||||
|
ProcessOpticalText(text, receivedAtUtc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessOpticalText(string text, DateTime receivedAtUtc)
|
||||||
{
|
{
|
||||||
lock (opticalSync)
|
lock (opticalSync)
|
||||||
{
|
{
|
||||||
@@ -632,32 +745,174 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
lastOpticalLine = line;
|
lastOpticalLine = line;
|
||||||
|
|
||||||
AllyOpticalSample sample;
|
AllyOpticalSample sample;
|
||||||
if (!AllyOpticalSample.TryParse(line, DateTime.UtcNow, out sample))
|
if (!AllyOpticalSample.TryParse(line, receivedAtUtc, out sample))
|
||||||
|
{
|
||||||
|
if (AllyOpticalSample.IsMetrologyPacket(line))
|
||||||
|
{
|
||||||
|
log.WarnFormat(
|
||||||
|
"ALLY_OPTO rejected C6 metrology telegram on COM{0}: bytes={1}, ASCII='{2}', HEX={3}",
|
||||||
|
allyCfg.OptoComPortNr,
|
||||||
|
line.Length,
|
||||||
|
ToLogText(line),
|
||||||
|
ToHex(line));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.DebugFormat(
|
||||||
|
"ALLY_OPTO ignored non-metrology telegram on COM{0}: bytes={1}, ASCII='{2}', HEX={3}",
|
||||||
|
allyCfg.OptoComPortNr,
|
||||||
|
line.Length,
|
||||||
|
ToLogText(line),
|
||||||
|
ToHex(line));
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ExtendRawVolume(sample.RawVolume);
|
ExtendRawVolume(sample.RawVolume);
|
||||||
ExtendRawTimestamp(sample.RawTimestamp);
|
if (!firstSampleReceivedAtUtc.HasValue)
|
||||||
sample.ExtendedVolumeLiters =
|
firstSampleReceivedAtUtc = sample.ReceivedAtUtc;
|
||||||
extendedRawVolume * GetOpticalVolumeLitersPerRawUnit();
|
sample.ElapsedSeconds = (sample.ReceivedAtUtc - firstSampleReceivedAtUtc.Value).TotalSeconds;
|
||||||
sample.ElapsedSeconds = extendedRawTimestamp / 8192D;
|
|
||||||
|
|
||||||
if (opticalSamples.Count == MaxStoredSamples)
|
if (opticalSamples.Count == MaxStoredSamples)
|
||||||
opticalSamples.RemoveAt(0);
|
opticalSamples.RemoveAt(0);
|
||||||
opticalSamples.Add(sample);
|
opticalSamples.Add(sample);
|
||||||
|
|
||||||
endWMState = sample.ExtendedVolumeLiters;
|
if (IsOpticalVolumeConversionConfigured)
|
||||||
|
{
|
||||||
|
sample.ExtendedVolumeLiters =
|
||||||
|
extendedRawVolume * GetOpticalVolumeLitersPerRawUnit();
|
||||||
|
endWMState = sample.ExtendedVolumeLiters;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sample.ExtendedVolumeLiters = Double.NaN;
|
||||||
|
}
|
||||||
timestampSecEnd = sample.ElapsedSeconds;
|
timestampSecEnd = sample.ElapsedSeconds;
|
||||||
|
|
||||||
if (operationActive && !hasTestStartSample)
|
if (operationActive && !hasTestStartSample && IsOpticalVolumeConversionConfigured)
|
||||||
{
|
{
|
||||||
hasTestStartSample = true;
|
hasTestStartSample = true;
|
||||||
beginWMState = sample.ExtendedVolumeLiters;
|
beginWMState = sample.ExtendedVolumeLiters;
|
||||||
timestampSecStart = sample.ElapsedSeconds;
|
timestampSecStart = sample.ElapsedSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.DebugFormat(
|
||||||
|
"ALLY_OPTO parsed COM{0}: sequence=0x{1:X2}, rawVolume=0x{2:X8}, flow={3}, volume={4:F6} l, elapsed={5:F3} s, emptyPipe={6}, fastHptc={7}",
|
||||||
|
allyCfg.OptoComPortNr,
|
||||||
|
sample.Sequence,
|
||||||
|
sample.RawVolume,
|
||||||
|
sample.RawFlow,
|
||||||
|
sample.ExtendedVolumeLiters,
|
||||||
|
sample.ElapsedSeconds,
|
||||||
|
sample.IsEmptyPipe,
|
||||||
|
sample.IsFastHptc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the meter optical output and then starts COM optical capture.
|
||||||
|
/// C6 volume is decoded in quarter millilitres and is independent of the
|
||||||
|
/// configured nominal meter size.
|
||||||
|
/// </summary>
|
||||||
|
public void StartOpticalVerificationStream(int timeoutMs)
|
||||||
|
{
|
||||||
|
if (IsFactorySealed(timeoutMs))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"ALLY meter is factory sealed. Unseal the meter before starting the optical stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ExecuteCommand(service => service.StartOpticalVerificationOutput(timeoutMs));
|
||||||
|
opticalVerificationOutputActive = true;
|
||||||
|
StartDataStreamProcessing(IsOpticalVolumeConversionConfigured);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (opticalVerificationOutputActive)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
StopOpticalVerificationStream(timeoutMs);
|
||||||
|
}
|
||||||
|
catch (Exception cleanupException)
|
||||||
|
{
|
||||||
|
log.Error("ALLY optical start cleanup failed.", cleanupException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production-bench setup operation. It reads the meter-specific factory
|
||||||
|
/// data, opens the factory seal only when needed, and then enables the
|
||||||
|
/// complete optical verification stream.
|
||||||
|
/// </summary>
|
||||||
|
public void UnsealAndStartOpticalVerificationStream(int timeoutMs)
|
||||||
|
{
|
||||||
|
AllyFactoryUnsealData unsealData = ReadFactoryUnsealData(timeoutMs);
|
||||||
|
log.InfoFormat("ALLY_OPTO_SETUP_SEAL_STATE: sealed={0}, factoryId='{1}', programmableText='{2}'",
|
||||||
|
unsealData.IsSealed,
|
||||||
|
unsealData.FactoryId,
|
||||||
|
unsealData.ProgrammableText);
|
||||||
|
|
||||||
|
if (unsealData.IsSealed)
|
||||||
|
{
|
||||||
|
UnsealFactory(unsealData, timeoutMs);
|
||||||
|
if (IsFactorySealed(timeoutMs))
|
||||||
|
throw new InvalidOperationException("ALLY factory seal remained active after the unseal command.");
|
||||||
|
|
||||||
|
log.Info("ALLY_OPTO_SETUP_UNSEALED: factory seal was removed for optical verification.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.Info("ALLY_OPTO_SETUP_UNSEAL_SKIPPED: meter is already unsealed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
StartOpticalVerificationStream(timeoutMs);
|
||||||
|
log.Info("ALLY_OPTO_SETUP_STARTED: optical verification stream is active.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops COM optical capture and restores LED, meter mode and spread
|
||||||
|
/// spectrum even when the dialog is closed unexpectedly.
|
||||||
|
/// </summary>
|
||||||
|
public void StopOpticalVerificationStream(int timeoutMs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (opticalSync)
|
||||||
|
{
|
||||||
|
operationActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opticalVerificationOutputActive)
|
||||||
|
ExecuteCommand(service => service.StopOpticalVerificationOutput(timeoutMs));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
opticalVerificationOutputActive = false;
|
||||||
|
StopDataStreamProcessing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToHex(string text)
|
||||||
|
{
|
||||||
|
return BitConverter.ToString(Encoding.ASCII.GetBytes(text ?? string.Empty)).Replace("-", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToLogText(string text)
|
||||||
|
{
|
||||||
|
return (text ?? string.Empty)
|
||||||
|
.Replace("\r", "\\r")
|
||||||
|
.Replace("\n", "\\n")
|
||||||
|
.Replace("\t", "\\t");
|
||||||
|
}
|
||||||
|
|
||||||
private void ExtendRawVolume(uint rawVolume)
|
private void ExtendRawVolume(uint rawVolume)
|
||||||
{
|
{
|
||||||
if (!hasPreviousRawVolume)
|
if (!hasPreviousRawVolume)
|
||||||
@@ -678,51 +933,19 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
previousRawVolume = rawVolume;
|
previousRawVolume = rawVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExtendRawTimestamp(uint rawTimestamp)
|
|
||||||
{
|
|
||||||
if (!hasPreviousRawTimestamp)
|
|
||||||
{
|
|
||||||
hasPreviousRawTimestamp = true;
|
|
||||||
previousRawTimestamp = rawTimestamp;
|
|
||||||
extendedRawTimestamp = rawTimestamp;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
long delta = (long)rawTimestamp - previousRawTimestamp;
|
|
||||||
if (delta < -RawTimestampHalfRange)
|
|
||||||
delta += RawTimestampModulo;
|
|
||||||
else if (delta > RawTimestampHalfRange)
|
|
||||||
delta -= RawTimestampModulo;
|
|
||||||
|
|
||||||
extendedRawTimestamp += delta;
|
|
||||||
previousRawTimestamp = rawTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double GetOpticalVolumeLitersPerRawUnit()
|
private double GetOpticalVolumeLitersPerRawUnit()
|
||||||
{
|
{
|
||||||
// The optical format scales volume by flow-tube size: raw / 16000
|
// C6 accumulator is expressed in quarter millilitres, independent of tube size.
|
||||||
// for 5/8", 2 * raw / 16000 for 3/4", and 4 * raw / 16000 for 1".
|
return 1D / 4000D;
|
||||||
switch (ConfiguredMeterSize)
|
|
||||||
{
|
|
||||||
case AllyMeterSize.FiveEighths: return 1D / 16000D;
|
|
||||||
case AllyMeterSize.ThreeQuarterShort:
|
|
||||||
case AllyMeterSize.ThreeQuarterLong: return 2D / 16000D;
|
|
||||||
case AllyMeterSize.OneInch: return 4D / 16000D;
|
|
||||||
default:
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"ALLY meter size is AutoDetect. UI-2031 serial-number parsing is required before decoding optical volume.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetVolumeState()
|
private void ResetVolumeState()
|
||||||
{
|
{
|
||||||
hasPreviousRawVolume = false;
|
hasPreviousRawVolume = false;
|
||||||
hasPreviousRawTimestamp = false;
|
|
||||||
hasTestStartSample = false;
|
hasTestStartSample = false;
|
||||||
previousRawVolume = 0;
|
previousRawVolume = 0;
|
||||||
previousRawTimestamp = 0;
|
|
||||||
extendedRawVolume = 0;
|
extendedRawVolume = 0;
|
||||||
extendedRawTimestamp = 0;
|
firstSampleReceivedAtUtc = null;
|
||||||
beginWMState = 0;
|
beginWMState = 0;
|
||||||
endWMState = 0;
|
endWMState = 0;
|
||||||
timestampSecStart = 0;
|
timestampSecStart = 0;
|
||||||
|
|||||||
@@ -4,19 +4,51 @@ using System.Globalization;
|
|||||||
namespace TBF.Rig.RegisterReaders.AllyReader
|
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Validated common portion of the 42-byte optical telegram used by the
|
/// ALLY optical metrology sample. ALLY emits a tab-separated envelope:
|
||||||
/// register-reader pattern referenced by UI-2093. ALLY calibration-only
|
/// sequence, message type, Base64 binary payload and a four-hex checksum.
|
||||||
/// fields are intentionally not inferred without UI-1204/UI-1236.
|
/// Type C6 contains the 24-byte C2 water-metrology layout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AllyOpticalSample
|
public sealed class AllyOpticalSample
|
||||||
{
|
{
|
||||||
private const int TelegramLength = 42;
|
private const byte MetrologyPacketType = 0xC6;
|
||||||
|
private const int MetrologyPayloadLength = 24;
|
||||||
|
|
||||||
public string RawLine { get; private set; }
|
public string RawLine { get; private set; }
|
||||||
public DateTime ReceivedAtUtc { get; private set; }
|
public DateTime ReceivedAtUtc { get; private set; }
|
||||||
|
public byte Sequence { get; private set; }
|
||||||
|
public byte PacketType { get; private set; }
|
||||||
|
public ushort PacketChecksum { get; private set; }
|
||||||
|
public int RawAdc { get; private set; }
|
||||||
|
public short LastField { get; private set; }
|
||||||
public short RawFlow { get; private set; }
|
public short RawFlow { get; private set; }
|
||||||
public uint RawVolume { get; private set; }
|
public uint RawVolume { get; private set; }
|
||||||
public uint RawTimestamp { get; private set; }
|
public ushort FlipPeriod { get; private set; }
|
||||||
|
public ushort VinfStart { get; private set; }
|
||||||
|
public ushort VinfEnd { get; private set; }
|
||||||
|
public short ElectrodeDelta { get; private set; }
|
||||||
|
public ushort Impedance { get; private set; }
|
||||||
|
public byte FieldDriveTime { get; private set; }
|
||||||
|
public byte Flags { get; private set; }
|
||||||
|
public byte[] ExtensionBytes { get; private set; }
|
||||||
|
|
||||||
|
// C6 has no legacy 8192 Hz meter timestamp. Time is based on receipt.
|
||||||
|
public uint RawTimestamp { get { return 0; } }
|
||||||
|
public bool IsLowFlow { get { return (Flags & 0x01) != 0; } }
|
||||||
|
public bool IsEmptyPipe { get { return (Flags & 0x02) != 0; } }
|
||||||
|
public bool IsFastHptc { get { return (Flags & 0x04) != 0; } }
|
||||||
|
public bool FieldPolarity { get { return (Flags & 0x08) != 0; } }
|
||||||
|
public bool ImpedancePolarity { get { return (Flags & 0x10) != 0; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flow decoded from the C6 payload. The payload stores quarter millilitres per second.
|
||||||
|
/// </summary>
|
||||||
|
public double FlowMillilitersPerSecond { get { return RawFlow / 4D; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Accumulated volume decoded from the C6 payload. The payload stores quarter millilitres.
|
||||||
|
/// </summary>
|
||||||
|
public double VolumeLiters { get { return RawVolume / 4000D; } }
|
||||||
|
|
||||||
public double ExtendedVolumeLiters { get; internal set; }
|
public double ExtendedVolumeLiters { get; internal set; }
|
||||||
public double ElapsedSeconds { get; internal set; }
|
public double ElapsedSeconds { get; internal set; }
|
||||||
|
|
||||||
@@ -33,49 +65,77 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
if (string.IsNullOrWhiteSpace(line))
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (line.Length < TelegramLength)
|
string telegram = line.TrimEnd('\r', '\n');
|
||||||
|
string[] fields = telegram.Split('\t');
|
||||||
|
if (fields.Length != 4)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
string telegram = line.Substring(line.Length - TelegramLength, TelegramLength);
|
byte sequence;
|
||||||
if (telegram[6] != '\t' || telegram[11] != '\t' || telegram[16] != '\t' ||
|
byte packetType;
|
||||||
telegram[23] != '\t' || telegram[28] != '\t' || telegram[37] != '\t' ||
|
ushort checksum;
|
||||||
telegram[40] != '\r' || telegram[41] != '\n')
|
if (!byte.TryParse(fields[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out sequence) ||
|
||||||
|
!byte.TryParse(fields[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out packetType) ||
|
||||||
|
!ushort.TryParse(fields[3], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out checksum) ||
|
||||||
|
packetType != MetrologyPacketType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
byte[] payload;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
payload = Convert.FromBase64String(fields[2]);
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort rawFlowUnsigned;
|
if (payload.Length < MetrologyPayloadLength)
|
||||||
uint rawVolume;
|
|
||||||
uint rawTimestamp;
|
|
||||||
byte checksum;
|
|
||||||
if (!ushort.TryParse(telegram.Substring(12, 4), NumberStyles.HexNumber,
|
|
||||||
CultureInfo.InvariantCulture, out rawFlowUnsigned) ||
|
|
||||||
!uint.TryParse(telegram.Substring(17, 6), NumberStyles.HexNumber,
|
|
||||||
CultureInfo.InvariantCulture, out rawVolume) ||
|
|
||||||
!uint.TryParse(telegram.Substring(29, 8), NumberStyles.HexNumber,
|
|
||||||
CultureInfo.InvariantCulture, out rawTimestamp) ||
|
|
||||||
!byte.TryParse(telegram.Substring(38, 2), NumberStyles.HexNumber,
|
|
||||||
CultureInfo.InvariantCulture, out checksum))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte calculatedChecksum = 0;
|
|
||||||
for (int i = 0; i < TelegramLength - 4; i++)
|
|
||||||
calculatedChecksum += (byte)telegram[i];
|
|
||||||
|
|
||||||
if (calculatedChecksum != checksum || rawVolume > 0xFFFFFF)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
sample = new AllyOpticalSample
|
sample = new AllyOpticalSample
|
||||||
{
|
{
|
||||||
RawLine = telegram,
|
RawLine = line,
|
||||||
ReceivedAtUtc = receivedAtUtc,
|
ReceivedAtUtc = receivedAtUtc,
|
||||||
RawFlow = unchecked((short)rawFlowUnsigned),
|
Sequence = sequence,
|
||||||
RawVolume = rawVolume,
|
PacketType = packetType,
|
||||||
RawTimestamp = rawTimestamp
|
PacketChecksum = checksum,
|
||||||
|
RawAdc = BitConverter.ToInt32(payload, 0),
|
||||||
|
LastField = BitConverter.ToInt16(payload, 4),
|
||||||
|
RawFlow = BitConverter.ToInt16(payload, 6),
|
||||||
|
RawVolume = BitConverter.ToUInt32(payload, 8),
|
||||||
|
FlipPeriod = BitConverter.ToUInt16(payload, 12),
|
||||||
|
VinfStart = BitConverter.ToUInt16(payload, 14),
|
||||||
|
VinfEnd = BitConverter.ToUInt16(payload, 16),
|
||||||
|
ElectrodeDelta = BitConverter.ToInt16(payload, 18),
|
||||||
|
Impedance = BitConverter.ToUInt16(payload, 20),
|
||||||
|
FieldDriveTime = payload[22],
|
||||||
|
Flags = payload[23],
|
||||||
|
ExtensionBytes = CopyExtension(payload)
|
||||||
};
|
};
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsMetrologyPacket(string line)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string[] fields = line.TrimEnd('\r', '\n').Split('\t');
|
||||||
|
byte packetType;
|
||||||
|
return fields.Length == 4 &&
|
||||||
|
byte.TryParse(fields[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out packetType) &&
|
||||||
|
packetType == MetrologyPacketType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] CopyExtension(byte[] payload)
|
||||||
|
{
|
||||||
|
int length = payload.Length - MetrologyPayloadLength;
|
||||||
|
if (length <= 0)
|
||||||
|
return new byte[0];
|
||||||
|
|
||||||
|
byte[] extension = new byte[length];
|
||||||
|
Buffer.BlockCopy(payload, MetrologyPayloadLength, extension, 0, length);
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
|||||||
|
|
||||||
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities)
|
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities)
|
||||||
{
|
{
|
||||||
return new Configs.ParamsProvider.ComponentCfgCtrl(this, null);
|
return new AllyReaderCfgCtrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ComponentName { get { return Name; } }
|
public string ComponentName { get { return Name; } }
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||||
|
{
|
||||||
|
partial class AllyReaderCfgCtrl
|
||||||
|
{
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); }
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
tabControl1 = new System.Windows.Forms.TabControl(); tabPage1 = new System.Windows.Forms.TabPage(); tabPage2 = new System.Windows.Forms.TabPage();
|
||||||
|
classNameLabel = new System.Windows.Forms.Label(); nameLabel = new System.Windows.Forms.Label(); nameTextBox = new System.Windows.Forms.TextBox();
|
||||||
|
muxBoardNrLabel = new System.Windows.Forms.Label(); muxBoardNrTextBox = new System.Windows.Forms.TextBox(); label3 = new System.Windows.Forms.Label();
|
||||||
|
groupLabel = new System.Windows.Forms.Label(); groupTextBox = new System.Windows.Forms.TextBox(); label4 = new System.Windows.Forms.Label();
|
||||||
|
optoDataGroupBox = new System.Windows.Forms.GroupBox(); radioButton1 = new System.Windows.Forms.RadioButton(); radioButton2 = new System.Windows.Forms.RadioButton();
|
||||||
|
ipAddressLabel = new System.Windows.Forms.Label(); ipAddressTextBox = new System.Windows.Forms.TextBox(); tcpipPortLabel = new System.Windows.Forms.Label(); tcpipPortTextBox = new System.Windows.Forms.TextBox(); optoSerialPortLabel = new System.Windows.Forms.Label(); optoSerialPortTextBox = new System.Windows.Forms.TextBox();
|
||||||
|
groupBox1 = new System.Windows.Forms.GroupBox(); label1 = new System.Windows.Forms.Label(); comboBoxCommunicationInterface = new System.Windows.Forms.ComboBox(); rfidSerialPortNrLabel = new System.Windows.Forms.Label(); rfidPortNrTextBox = new System.Windows.Forms.TextBox();
|
||||||
|
groupBox2 = new System.Windows.Forms.GroupBox(); label2 = new System.Windows.Forms.Label(); headPortNrTextBox = new System.Windows.Forms.TextBox();
|
||||||
|
tabControl1.SuspendLayout(); tabPage1.SuspendLayout(); optoDataGroupBox.SuspendLayout(); groupBox1.SuspendLayout(); groupBox2.SuspendLayout(); SuspendLayout();
|
||||||
|
tabControl1.Controls.Add(tabPage1); tabControl1.Controls.Add(tabPage2); tabControl1.Location = new System.Drawing.Point(3,3); tabControl1.Name="tabControl1"; tabControl1.SelectedIndex=0; tabControl1.Size=new System.Drawing.Size(611,432);
|
||||||
|
tabPage1.Controls.Add(groupBox2); tabPage1.Controls.Add(label4); tabPage1.Controls.Add(label3); tabPage1.Controls.Add(groupBox1); tabPage1.Controls.Add(optoDataGroupBox); tabPage1.Controls.Add(groupTextBox); tabPage1.Controls.Add(groupLabel); tabPage1.Controls.Add(muxBoardNrTextBox); tabPage1.Controls.Add(muxBoardNrLabel); tabPage1.Controls.Add(nameTextBox); tabPage1.Controls.Add(nameLabel); tabPage1.Controls.Add(classNameLabel); tabPage1.Location=new System.Drawing.Point(4,25); tabPage1.Name="tabPage1"; tabPage1.Padding=new System.Windows.Forms.Padding(3); tabPage1.Size=new System.Drawing.Size(603,403); tabPage1.Text="Config"; tabPage1.UseVisualStyleBackColor=true;
|
||||||
|
classNameLabel.AutoSize=true; classNameLabel.Location=new System.Drawing.Point(149,11); classNameLabel.Name="classNameLabel"; classNameLabel.Text="ClassName";
|
||||||
|
nameLabel.AutoSize=true; nameLabel.Location=new System.Drawing.Point(6,44); nameLabel.Name="nameLabel"; nameLabel.Text="Name";
|
||||||
|
nameTextBox.Enabled=false; nameTextBox.Location=new System.Drawing.Point(153,40); nameTextBox.Name="nameTextBox"; nameTextBox.Size=new System.Drawing.Size(160,22);
|
||||||
|
muxBoardNrLabel.AutoSize=true; muxBoardNrLabel.Location=new System.Drawing.Point(6,72); muxBoardNrLabel.Name="muxBoardNrLabel"; muxBoardNrLabel.Text="Group 1 (mux. board)";
|
||||||
|
muxBoardNrTextBox.Enabled=false; muxBoardNrTextBox.Location=new System.Drawing.Point(153,69); muxBoardNrTextBox.Name="muxBoardNrTextBox"; muxBoardNrTextBox.Size=new System.Drawing.Size(44,22);
|
||||||
|
label3.AutoSize=true; label3.Location=new System.Drawing.Point(208,72); label3.Name="label3"; label3.Text="1 .. 4";
|
||||||
|
groupLabel.AutoSize=true; groupLabel.Location=new System.Drawing.Point(6,101); groupLabel.Name="groupLabel"; groupLabel.Text="Group 2";
|
||||||
|
groupTextBox.Enabled=false; groupTextBox.Location=new System.Drawing.Point(153,97); groupTextBox.Name="groupTextBox"; groupTextBox.Size=new System.Drawing.Size(44,22);
|
||||||
|
label4.AutoSize=true; label4.Location=new System.Drawing.Point(208,101); label4.Name="label4"; label4.Text="1 .. 10";
|
||||||
|
optoDataGroupBox.Controls.Add(tcpipPortLabel); optoDataGroupBox.Controls.Add(tcpipPortTextBox); optoDataGroupBox.Controls.Add(ipAddressLabel); optoDataGroupBox.Controls.Add(ipAddressTextBox); optoDataGroupBox.Controls.Add(radioButton1); optoDataGroupBox.Controls.Add(radioButton2); optoDataGroupBox.Controls.Add(optoSerialPortLabel); optoDataGroupBox.Controls.Add(optoSerialPortTextBox); optoDataGroupBox.Location=new System.Drawing.Point(10,131); optoDataGroupBox.Name="optoDataGroupBox"; optoDataGroupBox.Size=new System.Drawing.Size(552,119); optoDataGroupBox.Text="Opto-data";
|
||||||
|
radioButton1.AutoSize=true; radioButton1.Checked=true; radioButton1.Enabled=false; radioButton1.Location=new System.Drawing.Point(29,23); radioButton1.Name="radioButton1"; radioButton1.Text="Use TCP/IP";
|
||||||
|
radioButton2.AutoSize=true; radioButton2.Enabled=false; radioButton2.Location=new System.Drawing.Point(312,23); radioButton2.Name="radioButton2"; radioButton2.Text="Use serial port";
|
||||||
|
ipAddressLabel.AutoSize=true; ipAddressLabel.Location=new System.Drawing.Point(41,59); ipAddressLabel.Name="ipAddressLabel"; ipAddressLabel.Text="IP address. :";
|
||||||
|
ipAddressTextBox.Enabled=false; ipAddressTextBox.Location=new System.Drawing.Point(143,55); ipAddressTextBox.Name="ipAddressTextBox"; ipAddressTextBox.Size=new System.Drawing.Size(129,22);
|
||||||
|
tcpipPortLabel.AutoSize=true; tcpipPortLabel.Location=new System.Drawing.Point(41,87); tcpipPortLabel.Name="tcpipPortLabel"; tcpipPortLabel.Text="Port nr.:";
|
||||||
|
tcpipPortTextBox.Enabled=false; tcpipPortTextBox.Location=new System.Drawing.Point(143,84); tcpipPortTextBox.Name="tcpipPortTextBox"; tcpipPortTextBox.Size=new System.Drawing.Size(51,22);
|
||||||
|
optoSerialPortLabel.AutoSize=true; optoSerialPortLabel.Location=new System.Drawing.Point(321,55); optoSerialPortLabel.Name="optoSerialPortLabel"; optoSerialPortLabel.Text="Serial port nr.:";
|
||||||
|
optoSerialPortTextBox.Enabled=false; optoSerialPortTextBox.Location=new System.Drawing.Point(439,52); optoSerialPortTextBox.Name="optoSerialPortTextBox"; optoSerialPortTextBox.Size=new System.Drawing.Size(44,22);
|
||||||
|
groupBox1.Controls.Add(comboBoxCommunicationInterface); groupBox1.Controls.Add(label1); groupBox1.Controls.Add(rfidPortNrTextBox); groupBox1.Controls.Add(rfidSerialPortNrLabel); groupBox1.Location=new System.Drawing.Point(10,259); groupBox1.Name="groupBox1"; groupBox1.Size=new System.Drawing.Size(552,68); groupBox1.Text="RFID / NFC communication (in case mux. board is not used)";
|
||||||
|
label1.AutoSize=true; label1.Location=new System.Drawing.Point(41,30); label1.Name="label1"; label1.Text="Communication Interface";
|
||||||
|
comboBoxCommunicationInterface.Enabled=false; comboBoxCommunicationInterface.FormattingEnabled=true; comboBoxCommunicationInterface.Location=new System.Drawing.Point(201,27); comboBoxCommunicationInterface.Name="comboBoxCommunicationInterface"; comboBoxCommunicationInterface.Size=new System.Drawing.Size(71,24);
|
||||||
|
rfidSerialPortNrLabel.AutoSize=true; rfidSerialPortNrLabel.Location=new System.Drawing.Point(321,30); rfidSerialPortNrLabel.Name="rfidSerialPortNrLabel"; rfidSerialPortNrLabel.Text="Serial port nr.:";
|
||||||
|
rfidPortNrTextBox.Enabled=false; rfidPortNrTextBox.Location=new System.Drawing.Point(439,26); rfidPortNrTextBox.Name="rfidPortNrTextBox"; rfidPortNrTextBox.Size=new System.Drawing.Size(44,22);
|
||||||
|
groupBox2.Controls.Add(headPortNrTextBox); groupBox2.Controls.Add(label2); groupBox2.Location=new System.Drawing.Point(10,335); groupBox2.Name="groupBox2"; groupBox2.Size=new System.Drawing.Size(552,50); groupBox2.Text="Head Communication";
|
||||||
|
label2.AutoSize=true; label2.Location=new System.Drawing.Point(321,18); label2.Name="label2"; label2.Text="Serial port nr.:";
|
||||||
|
headPortNrTextBox.Enabled=false; headPortNrTextBox.Location=new System.Drawing.Point(439,15); headPortNrTextBox.Name="headPortNrTextBox"; headPortNrTextBox.Size=new System.Drawing.Size(44,22);
|
||||||
|
tabPage2.Location=new System.Drawing.Point(4,25); tabPage2.Name="tabPage2"; tabPage2.Padding=new System.Windows.Forms.Padding(3); tabPage2.Size=new System.Drawing.Size(603,403); tabPage2.Text="Test"; tabPage2.UseVisualStyleBackColor=true;
|
||||||
|
AutoScaleDimensions=new System.Drawing.SizeF(8F,16F); AutoScaleMode=System.Windows.Forms.AutoScaleMode.Font; Controls.Add(tabControl1); Margin=new System.Windows.Forms.Padding(4); Name="AllyReaderCfgCtrl"; Size=new System.Drawing.Size(617,438); tabControl1.ResumeLayout(false); tabPage1.ResumeLayout(false); tabPage1.PerformLayout(); optoDataGroupBox.ResumeLayout(false); optoDataGroupBox.PerformLayout(); groupBox1.ResumeLayout(false); groupBox1.PerformLayout(); groupBox2.ResumeLayout(false); groupBox2.PerformLayout(); ResumeLayout(false);
|
||||||
|
}
|
||||||
|
private System.Windows.Forms.TabControl tabControl1; private System.Windows.Forms.TabPage tabPage1; private System.Windows.Forms.TabPage tabPage2; private System.Windows.Forms.Label classNameLabel,nameLabel,muxBoardNrLabel,groupLabel,label1,label2,label3,label4,ipAddressLabel,tcpipPortLabel,optoSerialPortLabel,rfidSerialPortNrLabel; private System.Windows.Forms.TextBox nameTextBox,muxBoardNrTextBox,groupTextBox,ipAddressTextBox,tcpipPortTextBox,optoSerialPortTextBox,rfidPortNrTextBox,headPortNrTextBox; private System.Windows.Forms.GroupBox optoDataGroupBox,groupBox1,groupBox2; private System.Windows.Forms.RadioButton radioButton1,radioButton2; private System.Windows.Forms.ComboBox comboBoxCommunicationInterface;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Common;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
|
||||||
|
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||||
|
{
|
||||||
|
public partial class AllyReaderCfgCtrl : UserControl, IComponentCfgCtrl
|
||||||
|
{
|
||||||
|
private AllyReaderCfg config;
|
||||||
|
private readonly AllyReaderManualTestCtrl manualTestControl;
|
||||||
|
|
||||||
|
public AllyReaderCfgCtrl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
manualTestControl = new AllyReaderManualTestCtrl { Dock = DockStyle.Fill };
|
||||||
|
tabPage2.Controls.Add(manualTestControl);
|
||||||
|
tabPage1.Text = "Settings";
|
||||||
|
tabPage2.Text = "Manual test";
|
||||||
|
groupBox1.Text = "Touch-Read communication";
|
||||||
|
groupBox2.Text = "Touch-Read settings";
|
||||||
|
muxBoardNrLabel.Text = "Configured meter size";
|
||||||
|
groupLabel.Text = "Meter pulses/liter";
|
||||||
|
label3.Text = label4.Text = string.Empty;
|
||||||
|
ipAddressLabel.Text = "TCP/IP";
|
||||||
|
ipAddressTextBox.Text = "Not supported by ALLY";
|
||||||
|
tcpipPortLabel.Text = "Opto baud rate:";
|
||||||
|
label2.Text = "Baud rate:";
|
||||||
|
comboBoxCommunicationInterface.Items.Clear();
|
||||||
|
comboBoxCommunicationInterface.Items.Add("Touch-Read");
|
||||||
|
comboBoxCommunicationInterface.SelectedIndex = 0;
|
||||||
|
radioButton1.Checked = false;
|
||||||
|
radioButton2.Checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponentCfg Config { get { return config; } set { config = value as AllyReaderCfg; Redraw(); manualTestControl.Config = config; } }
|
||||||
|
public bool ShowMore { get { return false; } }
|
||||||
|
public void Unlock() { nameTextBox.Enabled = muxBoardNrTextBox.Enabled = groupTextBox.Enabled = optoSerialPortTextBox.Enabled = tcpipPortTextBox.Enabled = rfidPortNrTextBox.Enabled = headPortNrTextBox.Enabled = true; }
|
||||||
|
|
||||||
|
public CfgUpdateFlags VerifyCfg(ref string message)
|
||||||
|
{
|
||||||
|
int n; double pulses; AllyMeterSize size;
|
||||||
|
if (config == null || string.IsNullOrWhiteSpace(nameTextBox.Text)) return Invalid(ref message, "Name");
|
||||||
|
if (!Enum.TryParse(muxBoardNrTextBox.Text, out size)) return Invalid(ref message, "Configured meter size");
|
||||||
|
if (!double.TryParse(groupTextBox.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out pulses) || pulses <= 0) return Invalid(ref message, "Meter pulses/liter");
|
||||||
|
if (!int.TryParse(optoSerialPortTextBox.Text, out n) || n <= 0) return Invalid(ref message, "Optical serial port nr.");
|
||||||
|
if (!int.TryParse(tcpipPortTextBox.Text, out n) || n <= 0) return Invalid(ref message, "Optical baud rate");
|
||||||
|
if (!int.TryParse(rfidPortNrTextBox.Text, out n) || n <= 0) return Invalid(ref message, "Touch-Read serial port nr.");
|
||||||
|
if (!int.TryParse(headPortNrTextBox.Text, out n) || n <= 0) return Invalid(ref message, "Touch-Read baud rate");
|
||||||
|
return CfgUpdateFlags.None;
|
||||||
|
}
|
||||||
|
public CfgUpdateFlags UpdateCfg()
|
||||||
|
{
|
||||||
|
if (config == null) return CfgUpdateFlags.Error;
|
||||||
|
config.Name = nameTextBox.Text;
|
||||||
|
config.ConfiguredMeterSize = (AllyMeterSize)Enum.Parse(typeof(AllyMeterSize), muxBoardNrTextBox.Text);
|
||||||
|
config.MeterPulsesPerLiter = double.Parse(groupTextBox.Text, CultureInfo.InvariantCulture);
|
||||||
|
config.OptoComPortNr = int.Parse(optoSerialPortTextBox.Text); config.OptoBaudRate = int.Parse(tcpipPortTextBox.Text);
|
||||||
|
config.CommandComPortNr = int.Parse(rfidPortNrTextBox.Text); config.CommandBaudRate = int.Parse(headPortNrTextBox.Text);
|
||||||
|
return CfgUpdateFlags.RestartRqrd;
|
||||||
|
}
|
||||||
|
public void Closing() { manualTestControl.StopOpticalStream(); }
|
||||||
|
private void Redraw()
|
||||||
|
{
|
||||||
|
if (config == null) return;
|
||||||
|
classNameLabel.Text = config.Factory.ClassName; nameTextBox.Text = config.Name;
|
||||||
|
muxBoardNrTextBox.Text = config.ConfiguredMeterSize.ToString(); groupTextBox.Text = config.MeterPulsesPerLiter.ToString(CultureInfo.InvariantCulture);
|
||||||
|
optoSerialPortTextBox.Text = config.OptoComPortNr.ToString(); tcpipPortTextBox.Text = config.OptoBaudRate.ToString();
|
||||||
|
rfidPortNrTextBox.Text = config.CommandComPortNr.ToString(); headPortNrTextBox.Text = config.CommandBaudRate.ToString();
|
||||||
|
}
|
||||||
|
private static CfgUpdateFlags Invalid(ref string message, string text) { message += Environment.NewLine + "'" + text + "' is not valid"; return CfgUpdateFlags.Error; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
|
||||||
|
<resheader name="version"><value>2.0</value></resheader>
|
||||||
|
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
|
||||||
|
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||||
|
{
|
||||||
|
partial class AllyReaderManualTestCtrl
|
||||||
|
{
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
protected override void Dispose(bool disposing) { if(disposing) { StopOpticalStream(); if(components != null) components.Dispose(); } base.Dispose(disposing); }
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
optoTestGroupBox=new System.Windows.Forms.GroupBox(); optoListBox=new System.Windows.Forms.ListBox(); RfidTestGroupBox=new System.Windows.Forms.GroupBox(); rfidOutputListBox=new System.Windows.Forms.ListBox(); label2=new System.Windows.Forms.Label(); rfidCommandComboBox=new System.Windows.Forms.ComboBox(); commandTestButton=new System.Windows.Forms.Button();
|
||||||
|
optoTestGroupBox.SuspendLayout(); RfidTestGroupBox.SuspendLayout(); SuspendLayout();
|
||||||
|
optoTestGroupBox.Controls.Add(optoListBox); optoTestGroupBox.Location=new System.Drawing.Point(5,4); optoTestGroupBox.Name="optoTestGroupBox"; optoTestGroupBox.Size=new System.Drawing.Size(591,161); optoTestGroupBox.Text="Opto-data";
|
||||||
|
optoListBox.FormattingEnabled=true; optoListBox.ItemHeight=16; optoListBox.Location=new System.Drawing.Point(7,22); optoListBox.Name="optoListBox"; optoListBox.Size=new System.Drawing.Size(573,132);
|
||||||
|
RfidTestGroupBox.Controls.Add(rfidOutputListBox); RfidTestGroupBox.Controls.Add(label2); RfidTestGroupBox.Controls.Add(rfidCommandComboBox); RfidTestGroupBox.Controls.Add(commandTestButton); RfidTestGroupBox.Location=new System.Drawing.Point(5,171); RfidTestGroupBox.Name="RfidTestGroupBox"; RfidTestGroupBox.Size=new System.Drawing.Size(591,224); RfidTestGroupBox.Text="RFID / NFC data";
|
||||||
|
rfidOutputListBox.FormattingEnabled=true; rfidOutputListBox.ItemHeight=16; rfidOutputListBox.Location=new System.Drawing.Point(5,54); rfidOutputListBox.Name="rfidOutputListBox"; rfidOutputListBox.SelectionMode=System.Windows.Forms.SelectionMode.None; rfidOutputListBox.Size=new System.Drawing.Size(575,164);
|
||||||
|
label2.AutoSize=true; label2.Location=new System.Drawing.Point(2,25); label2.Name="label2"; label2.Text="Command";
|
||||||
|
rfidCommandComboBox.FormattingEnabled=true; rfidCommandComboBox.Location=new System.Drawing.Point(86,19); rfidCommandComboBox.Name="rfidCommandComboBox"; rfidCommandComboBox.Size=new System.Drawing.Size(341,24);
|
||||||
|
commandTestButton.Location=new System.Drawing.Point(449,19); commandTestButton.Name="commandTestButton"; commandTestButton.Size=new System.Drawing.Size(126,24); commandTestButton.Text="Send command"; commandTestButton.UseVisualStyleBackColor=true; commandTestButton.MouseClick += new System.Windows.Forms.MouseEventHandler(CommandTestButtonClick);
|
||||||
|
AutoScaleDimensions=new System.Drawing.SizeF(8F,16F); AutoScaleMode=System.Windows.Forms.AutoScaleMode.Font; Controls.Add(optoTestGroupBox); Controls.Add(RfidTestGroupBox); Name="AllyReaderManualTestCtrl"; Size=new System.Drawing.Size(611,432); optoTestGroupBox.ResumeLayout(false); RfidTestGroupBox.ResumeLayout(false); RfidTestGroupBox.PerformLayout(); ResumeLayout(false);
|
||||||
|
}
|
||||||
|
private System.Windows.Forms.GroupBox optoTestGroupBox,RfidTestGroupBox; private System.Windows.Forms.ListBox optoListBox,rfidOutputListBox; private System.Windows.Forms.Label label2; private System.Windows.Forms.ComboBox rfidCommandComboBox; private System.Windows.Forms.Button commandTestButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||||
|
using TBF.Rig.Sequences;
|
||||||
|
|
||||||
|
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||||
|
{
|
||||||
|
public partial class AllyReaderManualTestCtrl : UserControl
|
||||||
|
{
|
||||||
|
private const int CommandTimeoutMs = 5000;
|
||||||
|
private readonly Timer opticalPollTimer;
|
||||||
|
private AllyReaderCfg config;
|
||||||
|
|
||||||
|
public AllyReaderManualTestCtrl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
ShowNormalCommands();
|
||||||
|
opticalPollTimer = new Timer { Interval = 250 };
|
||||||
|
opticalPollTimer.Tick += OpticalPollTimer_Tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllyReaderCfg Config { set { config = value; } }
|
||||||
|
public void StopOpticalStream()
|
||||||
|
{
|
||||||
|
opticalPollTimer.Stop();
|
||||||
|
AllyMeterReader reader = FindReader();
|
||||||
|
if (reader != null)
|
||||||
|
{
|
||||||
|
try { reader.StopOpticalVerificationStream(CommandTimeoutMs); }
|
||||||
|
catch (Exception exception) { AddOutput("Stop error: " + exception.Message); }
|
||||||
|
}
|
||||||
|
ShowNormalCommands();
|
||||||
|
}
|
||||||
|
private void CommandTestButtonClick(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
AllyMeterReader r = FindReader(); string cmd = rfidCommandComboBox.SelectedItem as string;
|
||||||
|
if (r == null) { AddOutput("Configured ALLY reader is not initialized on this bench."); return; }
|
||||||
|
try {
|
||||||
|
if (cmd == "Start optical stream")
|
||||||
|
{
|
||||||
|
r.StartOpticalVerificationStream(CommandTimeoutMs);
|
||||||
|
opticalPollTimer.Start();
|
||||||
|
ShowStopOnlyCommand();
|
||||||
|
AddOutput("Optical stream started: valve open, spread spectrum disabled, mode 0x09, LED 0xC2, COM optical capture active.");
|
||||||
|
}
|
||||||
|
else if (cmd == "Stop optical stream") { StopOpticalStream(); AddOutput("Stopped"); }
|
||||||
|
else if (cmd == "Read optical data") { r.RunDeviceBefore(); AddOpto(r.ReadOptoData()); }
|
||||||
|
else if (cmd == "Read serial number") AddOutput(r.ReadSerialNumber(CommandTimeoutMs));
|
||||||
|
else if (cmd == "Read version and type") AddOutput(r.ReadVersionAndType(CommandTimeoutMs).ToString());
|
||||||
|
else if (cmd == "View factory seal") AddOutput(r.IsFactorySealed(CommandTimeoutMs) ? "Factory seal: sealed" : "Factory seal: unsealed");
|
||||||
|
else if (cmd == "Unseal meter") UnsealMeter(r);
|
||||||
|
else if (cmd == "Seal meter") SealMeter(r);
|
||||||
|
else if (cmd == "Set RFID mode") { r.SetRfidInterface(); AddOutput("OK"); }
|
||||||
|
else if (cmd == "Set NFC mode") { r.SetNfcInterface(); AddOutput("OK"); }
|
||||||
|
} catch(Exception x) { AddOutput("Error: " + x.Message); }
|
||||||
|
}
|
||||||
|
private void UnsealMeter(AllyMeterReader reader)
|
||||||
|
{
|
||||||
|
if (MessageBox.Show("Unseal the ALLY meter? This changes its factory-seal state.", "Unseal meter", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
|
||||||
|
{
|
||||||
|
AddOutput("Unseal cancelled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllyFactoryUnsealData data = reader.ReadFactoryUnsealData(CommandTimeoutMs);
|
||||||
|
AddOutput("Factory seal before unseal: " + (data.IsSealed ? "sealed" : "unsealed"));
|
||||||
|
AddOutput("Factory ID: " + data.FactoryId);
|
||||||
|
AddOutput("Programmable text: " + data.ProgrammableText);
|
||||||
|
AddOutput("Reading preset: " + data.ReadingPreset);
|
||||||
|
AddOutput("Seconds active: " + data.SecondsActive);
|
||||||
|
AddOutput("Calculated unseal hash: " + data.CredentialHex);
|
||||||
|
|
||||||
|
if (!data.IsSealed)
|
||||||
|
{
|
||||||
|
AddOutput("Unseal skipped: meter is already unsealed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.UnsealFactory(data, CommandTimeoutMs);
|
||||||
|
AddOutput("Unseal response: 0x01 COMPLETE_NO_ERRORS");
|
||||||
|
AddOutput(reader.IsFactorySealed(CommandTimeoutMs) ? "Factory seal is still sealed." : "Factory seal: unsealed");
|
||||||
|
}
|
||||||
|
private void SealMeter(AllyMeterReader reader)
|
||||||
|
{
|
||||||
|
if (MessageBox.Show("Seal the ALLY meter? This protects factory commands and optical-output configuration.", "Seal meter", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
|
||||||
|
{
|
||||||
|
AddOutput("Seal cancelled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.SealFactory(CommandTimeoutMs);
|
||||||
|
AddOutput("Seal response: 0x01 COMPLETE_NO_ERRORS");
|
||||||
|
AddOutput(reader.IsFactorySealed(CommandTimeoutMs) ? "Factory seal: sealed" : "Factory seal was not applied.");
|
||||||
|
}
|
||||||
|
private void OpticalPollTimer_Tick(object sender, EventArgs e) { AllyMeterReader r=FindReader(); if(r==null) { opticalPollTimer.Stop(); return; } try { r.RunDeviceBefore(); AddOpto(r.ReadOptoData()); } catch(Exception x) { AddOpto("Error: " + x.Message); } }
|
||||||
|
private AllyMeterReader FindReader() { return config == null || ProcessData.SmartHeadsUni == null ? null : ProcessData.SmartHeadsUni.OfType<AllyMeterReader>().FirstOrDefault(x => x.Name == config.Name); }
|
||||||
|
private void AddOpto(string text) { if(!string.IsNullOrEmpty(text)) optoListBox.Items.Insert(0,text); }
|
||||||
|
private void AddOutput(string text) { rfidOutputListBox.Items.Insert(0,text ?? string.Empty); }
|
||||||
|
private void ShowNormalCommands()
|
||||||
|
{
|
||||||
|
rfidCommandComboBox.Items.Clear();
|
||||||
|
rfidCommandComboBox.Items.AddRange(new object[] { "Read serial number", "Read version and type", "View factory seal", "Unseal meter", "Seal meter", "Set RFID mode", "Set NFC mode", "Read optical data", "Start optical stream", "Stop optical stream" });
|
||||||
|
rfidCommandComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
private void ShowStopOnlyCommand()
|
||||||
|
{
|
||||||
|
rfidCommandComboBox.Items.Clear();
|
||||||
|
rfidCommandComboBox.Items.Add("Stop optical stream");
|
||||||
|
rfidCommandComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><root><resheader name="resmimetype"><value>text/microsoft-resx</value></resheader><resheader name="version"><value>2.0</value></resheader><resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader></root>
|
||||||
@@ -93,6 +93,96 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
|||||||
timeoutMs);
|
timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the documented ALLY optical-verification output. Factory
|
||||||
|
/// sealed registers must be explicitly unsealed before this sequence.
|
||||||
|
/// </summary>
|
||||||
|
public void StartOpticalVerificationOutput(int timeoutMs)
|
||||||
|
{
|
||||||
|
if (IsFactorySealed(timeoutMs))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"ALLY meter is factory sealed. Unseal the meter before starting the optical stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenValve(timeoutMs);
|
||||||
|
SetSpreadSpectrum(false, timeoutMs);
|
||||||
|
SetMeterMode(0x09, timeoutMs);
|
||||||
|
SetDiagnosticLed(0xC2, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restores the documented normal ALLY operating state after optical
|
||||||
|
/// verification output has stopped.
|
||||||
|
/// </summary>
|
||||||
|
public void StopOpticalVerificationOutput(int timeoutMs)
|
||||||
|
{
|
||||||
|
SetDiagnosticLed(0x00, timeoutMs);
|
||||||
|
SetMeterMode(0x02, timeoutMs);
|
||||||
|
SetSpreadSpectrum(true, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFactorySealed(int timeoutMs)
|
||||||
|
{
|
||||||
|
AllyResponse response = Send(
|
||||||
|
new AllyFrameBuilder().WithDeviceCommand(AllyDeviceCommand.ViewFactorySeal).BuildBytes(),
|
||||||
|
timeoutMs);
|
||||||
|
return response.GetByte() != 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllyFactoryUnsealData ReadFactoryUnsealData(int timeoutMs)
|
||||||
|
{
|
||||||
|
bool isSealed = IsFactorySealed(timeoutMs);
|
||||||
|
string programmableText = Send(
|
||||||
|
new AllyFrameBuilder().WithCommand(AllyCommand.ViewProgrammableText).BuildBytes(),
|
||||||
|
timeoutMs).GetNullTerminatedAscii();
|
||||||
|
string factoryId = Send(
|
||||||
|
new AllyFrameBuilder().WithCommand(AllyCommand.ViewFactoryId).BuildBytes(),
|
||||||
|
timeoutMs).GetNullTerminatedAscii();
|
||||||
|
string readingPreset = Send(
|
||||||
|
new AllyFrameBuilder().WithCommand(AllyCommand.ViewPresetTotal).BuildBytes(),
|
||||||
|
timeoutMs).GetNullTerminatedAscii();
|
||||||
|
uint secondsActive = Send(
|
||||||
|
new AllyFrameBuilder().WithDeviceCommand(AllyDeviceCommand.ViewSecondsActive).BuildBytes(),
|
||||||
|
timeoutMs).GetUInt32LittleEndian();
|
||||||
|
|
||||||
|
return new AllyFactoryUnsealData(
|
||||||
|
isSealed,
|
||||||
|
factoryId,
|
||||||
|
programmableText,
|
||||||
|
readingPreset,
|
||||||
|
secondsActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsealFactory(int timeoutMs)
|
||||||
|
{
|
||||||
|
UnsealFactory(ReadFactoryUnsealData(timeoutMs), timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsealFactory(AllyFactoryUnsealData data, int timeoutMs)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
|
||||||
|
Send(
|
||||||
|
new AllyFrameBuilder()
|
||||||
|
.WithDeviceCommand(AllyDeviceCommand.SetFactorySeal)
|
||||||
|
.WithByte(0x00)
|
||||||
|
.WithBytes(data.Credential)
|
||||||
|
.BuildBytes(),
|
||||||
|
timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SealFactory(int timeoutMs)
|
||||||
|
{
|
||||||
|
Send(
|
||||||
|
new AllyFrameBuilder()
|
||||||
|
.WithDeviceCommand(AllyDeviceCommand.SetFactorySeal)
|
||||||
|
.WithByte(0x01)
|
||||||
|
.BuildBytes(),
|
||||||
|
timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
public double ReadCalibrationFactorPercent(int timeoutMs)
|
public double ReadCalibrationFactorPercent(int timeoutMs)
|
||||||
{
|
{
|
||||||
AllyResponse response = Send(
|
AllyResponse response = Send(
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Values read immediately before breaking an ALLY factory seal and the
|
||||||
|
/// corresponding, meter-specific eight-byte unseal credential.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AllyFactoryUnsealData
|
||||||
|
{
|
||||||
|
private const string FactoryFallback = "Factory";
|
||||||
|
|
||||||
|
internal AllyFactoryUnsealData(
|
||||||
|
bool isSealed,
|
||||||
|
string factoryId,
|
||||||
|
string programmableText,
|
||||||
|
string readingPreset,
|
||||||
|
uint secondsActive)
|
||||||
|
{
|
||||||
|
IsSealed = isSealed;
|
||||||
|
FactoryId = factoryId ?? string.Empty;
|
||||||
|
ProgrammableText = programmableText ?? string.Empty;
|
||||||
|
ReadingPreset = readingPreset ?? string.Empty;
|
||||||
|
SecondsActive = secondsActive;
|
||||||
|
Credential = BuildCredential();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSealed { get; private set; }
|
||||||
|
public string FactoryId { get; private set; }
|
||||||
|
public string ProgrammableText { get; private set; }
|
||||||
|
public string ReadingPreset { get; private set; }
|
||||||
|
public uint SecondsActive { get; private set; }
|
||||||
|
public byte[] Credential { get; private set; }
|
||||||
|
|
||||||
|
public string CredentialHex
|
||||||
|
{
|
||||||
|
get { return BitConverter.ToString(Credential).Replace("-", " "); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] BuildCredential()
|
||||||
|
{
|
||||||
|
byte[] result = new byte[8];
|
||||||
|
ushort secondsPart = (ushort)((SecondsActive >> 8) & 0xFFFF);
|
||||||
|
ushort factoryIdPart = CalculateIperlCrc16(NormalizeFactoryId(FactoryId));
|
||||||
|
ushort customerTextPart = CalculateCustomerTextPart(ProgrammableText);
|
||||||
|
ushort presetPart = CalculatePresetPart(ReadingPreset);
|
||||||
|
|
||||||
|
WriteUInt16LittleEndian(result, 0, secondsPart);
|
||||||
|
WriteUInt16LittleEndian(result, 2, factoryIdPart);
|
||||||
|
WriteUInt16LittleEndian(result, 4, customerTextPart);
|
||||||
|
WriteUInt16LittleEndian(result, 6, presetPart);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ushort CalculateIperlCrc16(string value)
|
||||||
|
{
|
||||||
|
ushort crc = 0;
|
||||||
|
byte[] data = Encoding.ASCII.GetBytes(value);
|
||||||
|
foreach (byte valueByte in data)
|
||||||
|
{
|
||||||
|
ushort temp = crc;
|
||||||
|
crc = (ushort)(temp >> 8);
|
||||||
|
crc += (ushort)(temp << 8);
|
||||||
|
crc ^= valueByte;
|
||||||
|
|
||||||
|
temp = (ushort)((crc >> 4) & 0x000F);
|
||||||
|
crc ^= temp;
|
||||||
|
temp = (ushort)((crc & 0x000F) << 12);
|
||||||
|
crc ^= temp;
|
||||||
|
temp = (ushort)((crc & 0x00FF) << 5);
|
||||||
|
crc ^= temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ushort CalculateCustomerTextPart(string value)
|
||||||
|
{
|
||||||
|
byte[] data = Encoding.ASCII.GetBytes(OverlayFactory(value));
|
||||||
|
ushort result = 0;
|
||||||
|
for (int index = 0; index < 4; index++)
|
||||||
|
{
|
||||||
|
byte current = data[index];
|
||||||
|
byte setBits = 0;
|
||||||
|
for (int bit = 0; bit < 8; bit++)
|
||||||
|
setBits += (byte)((current >> bit) & 0x01);
|
||||||
|
|
||||||
|
result |= (ushort)(setBits << (index * 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ushort CalculatePresetPart(string value)
|
||||||
|
{
|
||||||
|
byte[] data = Encoding.ASCII.GetBytes(OverlayFactory(value));
|
||||||
|
uint selectedCharacters = ((uint)data[2] << 24) |
|
||||||
|
((uint)data[3] << 16) |
|
||||||
|
((uint)data[4] << 8) |
|
||||||
|
data[5];
|
||||||
|
return (ushort)(selectedCharacters % 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string OverlayFactory(string value)
|
||||||
|
{
|
||||||
|
char[] result = FactoryFallback.ToCharArray();
|
||||||
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
string trimmed = value.Trim();
|
||||||
|
int count = Math.Min(result.Length, trimmed.Length);
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
|
result[index] = trimmed[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeFactoryId(string value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? FactoryFallback : value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteUInt16LittleEndian(byte[] target, int offset, ushort value)
|
||||||
|
{
|
||||||
|
target[offset] = (byte)(value & 0xFF);
|
||||||
|
target[offset + 1] = (byte)(value >> 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
|||||||
{
|
{
|
||||||
ViewFactoryId = 0x01,
|
ViewFactoryId = 0x01,
|
||||||
ViewVersionAndType = 0x05,
|
ViewVersionAndType = 0x05,
|
||||||
|
ViewProgrammableText = 0x07,
|
||||||
|
ViewPresetTotal = 0x13,
|
||||||
SetPresetTotal = 0x14,
|
SetPresetTotal = 0x14,
|
||||||
SetMeterMode = 0x1A,
|
SetMeterMode = 0x1A,
|
||||||
SetValvePosition = 0x1E
|
SetValvePosition = 0x1E
|
||||||
@@ -29,6 +31,9 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
|||||||
SetCalibration = 0x54,
|
SetCalibration = 0x54,
|
||||||
ViewRebootCount = 0x55,
|
ViewRebootCount = 0x55,
|
||||||
SetDiagnosticLed = 0x60,
|
SetDiagnosticLed = 0x60,
|
||||||
|
ViewSecondsActive = 0x3D,
|
||||||
|
ViewFactorySeal = 0x63,
|
||||||
|
SetFactorySeal = 0x64,
|
||||||
SetLcdTimeout = 0x8C,
|
SetLcdTimeout = 0x8C,
|
||||||
StartOffsetLearning = 0xD1
|
StartOffsetLearning = 0xD1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
|
using log4net;
|
||||||
|
|
||||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||||
{
|
{
|
||||||
@@ -62,6 +63,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
|||||||
|
|
||||||
public sealed class AllySerialTransport : IAllyTransport
|
public sealed class AllySerialTransport : IAllyTransport
|
||||||
{
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(AllySerialTransport));
|
||||||
private readonly object sync = new object();
|
private readonly object sync = new object();
|
||||||
private readonly string portName;
|
private readonly string portName;
|
||||||
private readonly int baudRate;
|
private readonly int baudRate;
|
||||||
@@ -113,33 +115,62 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
|||||||
|
|
||||||
serialPort.DiscardInBuffer();
|
serialPort.DiscardInBuffer();
|
||||||
serialPort.WriteTimeout = timeoutMs;
|
serialPort.WriteTimeout = timeoutMs;
|
||||||
serialPort.Write(request, 0, request.Length);
|
log.DebugFormat("ALLY_CMD TX {0}: {1}", portName, FormatRequestForLog(request));
|
||||||
|
|
||||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
try
|
||||||
int first;
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
first = ReadByte(stopwatch, timeoutMs);
|
serialPort.Write(request, 0, request.Length);
|
||||||
|
|
||||||
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||||
|
int first;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
first = ReadByte(stopwatch, timeoutMs);
|
||||||
|
}
|
||||||
|
while (first != AllyProtocol.Start);
|
||||||
|
|
||||||
|
int direction = ReadByte(stopwatch, timeoutMs);
|
||||||
|
int length = ReadByte(stopwatch, timeoutMs);
|
||||||
|
if (length < 5)
|
||||||
|
throw new FormatException("ALLY response length is invalid.");
|
||||||
|
|
||||||
|
byte[] response = new byte[length];
|
||||||
|
response[0] = (byte)first;
|
||||||
|
response[1] = (byte)direction;
|
||||||
|
response[2] = (byte)length;
|
||||||
|
|
||||||
|
for (int i = 3; i < response.Length; i++)
|
||||||
|
response[i] = (byte)ReadByte(stopwatch, timeoutMs);
|
||||||
|
|
||||||
|
log.DebugFormat("ALLY_CMD RX {0}: {1}", portName, ToHex(response));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
log.Error("ALLY_CMD failed on " + portName + "; request=" + FormatRequestForLog(request), exception);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
while (first != AllyProtocol.Start);
|
|
||||||
|
|
||||||
int direction = ReadByte(stopwatch, timeoutMs);
|
|
||||||
int length = ReadByte(stopwatch, timeoutMs);
|
|
||||||
if (length < 5)
|
|
||||||
throw new FormatException("ALLY response length is invalid.");
|
|
||||||
|
|
||||||
byte[] response = new byte[length];
|
|
||||||
response[0] = (byte)first;
|
|
||||||
response[1] = (byte)direction;
|
|
||||||
response[2] = (byte)length;
|
|
||||||
|
|
||||||
for (int i = 3; i < response.Length; i++)
|
|
||||||
response[i] = (byte)ReadByte(stopwatch, timeoutMs);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatRequestForLog(byte[] request)
|
||||||
|
{
|
||||||
|
// Factory-unseal credentials must not be persisted in the shared TBF log.
|
||||||
|
if (request != null && request.Length == 15 &&
|
||||||
|
request[0] == 0x53 && request[1] == 0x57 && request[2] == 0x0F &&
|
||||||
|
request[3] == 0xFD && request[4] == 0x64 && request[5] == 0x00 && request[14] == 0x0D)
|
||||||
|
{
|
||||||
|
return "53 57 0F FD 64 00 <factory-unseal credential redacted; 8 bytes> 0D";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToHex(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToHex(byte[] data)
|
||||||
|
{
|
||||||
|
return data == null ? "<null>" : BitConverter.ToString(data).Replace("-", " ");
|
||||||
|
}
|
||||||
|
|
||||||
private int ReadByte(Stopwatch stopwatch, int timeoutMs)
|
private int ReadByte(Stopwatch stopwatch, int timeoutMs)
|
||||||
{
|
{
|
||||||
int remaining = timeoutMs - (int)stopwatch.ElapsedMilliseconds;
|
int remaining = timeoutMs - (int)stopwatch.ElapsedMilliseconds;
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
|
||||||
|
namespace TBF.Rig.RegisterReaders.AsicReader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ASIC register reader.
|
||||||
|
///
|
||||||
|
/// The communication implementation deliberately derives from the proven
|
||||||
|
/// iPerl head implementation. This keeps the wire protocol and persisted
|
||||||
|
/// iPerl configurations compatible while the public component family is
|
||||||
|
/// migrated to RegisterReaders.AsicReader.
|
||||||
|
/// </summary>
|
||||||
|
public class AsicReader : IperlHead, ISmartReader
|
||||||
|
{
|
||||||
|
public AsicReader()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsicReader(IComponentCfg cfg)
|
||||||
|
: base(cfg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// The original IperlHead API keeps the communication interface strongly
|
||||||
|
// typed. SmartCommunicationForm presents all reader families through a
|
||||||
|
// string-based contract.
|
||||||
|
string ISmartReader.CommInterface { get { return CommInterface.ToString(); } }
|
||||||
|
bool ISmartReader.CommFailed
|
||||||
|
{
|
||||||
|
get { return CommFailed; }
|
||||||
|
set { CommFailed = value; }
|
||||||
|
}
|
||||||
|
bool ISmartReader.Disabled
|
||||||
|
{
|
||||||
|
get { return Disabled; }
|
||||||
|
set { Disabled = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISmartReader.ResetNfcInterface(bool? nfcOn) { ResetNfcInterface(nfcOn); }
|
||||||
|
void ISmartReader.SetNfcInterface() { SetNfcInterface(); }
|
||||||
|
void ISmartReader.SetRfidInterface() { SetRfidInterface(); }
|
||||||
|
|
||||||
|
void ISmartReader.SetCommunicationInterface(string commInterface)
|
||||||
|
{
|
||||||
|
CommunicationInterface parsedInterface;
|
||||||
|
if (!Enum.TryParse(commInterface, true, out parsedInterface))
|
||||||
|
throw new ArgumentException("Unknown ASIC communication interface: " + commInterface, nameof(commInterface));
|
||||||
|
|
||||||
|
SetCommunicationInterface(parsedInterface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Xml.Serialization;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
||||||
|
|
||||||
|
namespace TBF.Rig.RegisterReaders.AsicReader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration identity for newly created ASIC readers.
|
||||||
|
///
|
||||||
|
/// It intentionally inherits the proven iPerl transport fields: the ASIC
|
||||||
|
/// hardware uses the same C4/optical/NFC/RFID wiring. The separate XML root
|
||||||
|
/// prevents new ASIC components from being persisted as generic iPerl heads,
|
||||||
|
/// while Factory still accepts legacy IperlHeadCfg XML.
|
||||||
|
/// </summary>
|
||||||
|
[XmlRoot("AsicReaderCfg")]
|
||||||
|
public class AsicReaderCfg : IperlHeadCfg
|
||||||
|
{
|
||||||
|
public static readonly XmlSerializer Serializer =
|
||||||
|
XmlSerializer.FromTypes(new[] { typeof(AsicReaderCfg) })[0];
|
||||||
|
|
||||||
|
public AsicReaderCfg()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsicReaderCfg(IComponentFactory factory)
|
||||||
|
: base(factory)
|
||||||
|
{
|
||||||
|
Name = "ASIC";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override XmlSerializer GetSerializer()
|
||||||
|
{
|
||||||
|
return Serializer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
||||||
|
|
||||||
|
namespace TBF.Rig.RegisterReaders.AsicReader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory for newly configured ASIC readers. Existing database rows with
|
||||||
|
/// class name "RegisterReader for iPerl" continue to resolve through the
|
||||||
|
/// legacy iPerl factory.
|
||||||
|
/// </summary>
|
||||||
|
public class Factory : IComponentFactory
|
||||||
|
{
|
||||||
|
public string ClassName { get { return GetType().Namespace.Substring(8); } }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponent DummyComponent()
|
||||||
|
{
|
||||||
|
return new AsicReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components)
|
||||||
|
{
|
||||||
|
return new AsicReader(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New ASIC components are stored under their own XML root. AsicReaderCfg
|
||||||
|
// inherits IperlHeadCfg, so the proven transport implementation receives
|
||||||
|
// exactly the same wire/port settings as before.
|
||||||
|
public IComponentCfg DefaultConfig()
|
||||||
|
{
|
||||||
|
return new AsicReaderCfg(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ComponentCfgBase.CreateFromDbEntity(AsicReaderCfg.Serializer, component, this);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// A partially migrated bench can already contain the former
|
||||||
|
// IperlHeadCfg XML. Keep it readable rather than requiring a
|
||||||
|
// database/configuration migration before an ASIC test can run.
|
||||||
|
return ComponentCfgBase.CreateFromDbEntity(IperlHeadCfg.Serializer, component, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -222,6 +222,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
{
|
{
|
||||||
FileName = fileName,
|
FileName = fileName,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
|
WorkingDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName)),
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
@@ -258,9 +259,18 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
|
|
||||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
|
info.ExitCode = process.ExitCode;
|
||||||
|
info.StandardOutput = stdOutTask.Result ?? "";
|
||||||
|
info.StandardError = stdErrTask.Result ?? "";
|
||||||
|
string allOutput = info.StandardOutput + info.StandardError;
|
||||||
log?.Debug(allOutput);
|
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;
|
info.State = CliTaskState.Completed;
|
||||||
return allOutput;
|
return allOutput;
|
||||||
}
|
}
|
||||||
@@ -290,6 +300,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
|
|
||||||
public void AddRunAndCaptureJsonAsync<T>(string fileName, string args) where T : new()
|
public void AddRunAndCaptureJsonAsync<T>(string fileName, string args) where T : new()
|
||||||
{
|
{
|
||||||
|
log?.Debug($"CLI queued: file='{fileName}', args='{args}'");
|
||||||
ResetStartTime();
|
ResetStartTime();
|
||||||
|
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
@@ -311,6 +322,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
{
|
{
|
||||||
FileName = fileName,
|
FileName = fileName,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
|
WorkingDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName)),
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
@@ -323,6 +335,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
log?.Debug($"CLI starting: file='{psi.FileName}', args='{psi.Arguments}', workingDirectory='{psi.WorkingDirectory}', exists={System.IO.File.Exists(psi.FileName)}");
|
||||||
process.Start();
|
process.Start();
|
||||||
|
|
||||||
var stdoutTask = process.StandardOutput.ReadToEndAsync();
|
var stdoutTask = process.StandardOutput.ReadToEndAsync();
|
||||||
@@ -347,8 +360,20 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
|
|
||||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
|
|
||||||
string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
|
info.ExitCode = process.ExitCode;
|
||||||
|
info.StandardOutput = stdoutTask.Result ?? "";
|
||||||
|
info.StandardError = stderrTask.Result ?? "";
|
||||||
|
string allOutput = info.StandardOutput + info.StandardError;
|
||||||
log?.Debug(allOutput);
|
log?.Debug(allOutput);
|
||||||
|
log?.Debug($"CLI completed: name='{info.Name}', exitCode={info.ExitCode}, stdoutLength={info.StandardOutput.Length}, stderrLength={info.StandardError.Length}");
|
||||||
|
|
||||||
|
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);
|
string json = ExtractJson(allOutput);
|
||||||
T result;
|
T result;
|
||||||
@@ -358,6 +383,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
return result;
|
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;
|
info.State = CliTaskState.Completed;
|
||||||
return default(T);
|
return default(T);
|
||||||
}
|
}
|
||||||
@@ -380,18 +407,17 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
|
|
||||||
public bool TryJsonStringDeserialize<T>(string json, out T value) where T : new()
|
public bool TryJsonStringDeserialize<T>(string json, out T value) where T : new()
|
||||||
{
|
{
|
||||||
if (json != null)
|
if (!string.IsNullOrWhiteSpace(json))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
value = JsonConvert.DeserializeObject<T>(json);
|
value = JsonConvert.DeserializeObject<T>(json);
|
||||||
return true;
|
return value != null;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
log?.Debug(ex.Message);
|
log?.Debug(ex.Message);
|
||||||
value = TryConvert<T>(json);
|
return TryConvert(json, out value);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,7 +425,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T TryConvert<T>(string json) where T : new()
|
private static bool TryConvert<T>(string json, out T value) where T : new()
|
||||||
{
|
{
|
||||||
T obj = new T();
|
T obj = new T();
|
||||||
|
|
||||||
@@ -417,21 +443,24 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
object value = token.ToObject(prop.PropertyType);
|
object propertyValue = token.ToObject(prop.PropertyType);
|
||||||
prop.SetValue(obj, value);
|
prop.SetValue(obj, propertyValue);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value = obj;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"TryConvert failed: {ex.Message}");
|
Console.WriteLine($"TryConvert failed: {ex.Message}");
|
||||||
|
value = default(T);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ExtractJson(string text)
|
public string ExtractJson(string text)
|
||||||
@@ -462,4 +491,4 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
public Process Process { get; set; }
|
public Process Process { get; set; }
|
||||||
public CliTaskState State { get; set; } = CliTaskState.Running;
|
public CliTaskState State { get; set; } = CliTaskState.Running;
|
||||||
public string Name { get; set; }
|
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
|
public bool UseResult
|
||||||
{
|
{
|
||||||
@@ -22,4 +26,4 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using TBF.Rig;
|
||||||
|
|
||||||
|
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
if (reader.IsNotStarted) reader.Start(readStart);
|
||||||
|
reader.Run();
|
||||||
|
if (!reader.IsFinished) allReadersFinished = false;
|
||||||
|
}
|
||||||
|
return allReadersFinished;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Owns one dialog phase (START or STOP). A reader is armed once per phase,
|
||||||
|
/// independently of its terminal state from a preceding phase.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PoseidonReadPhaseRunner
|
||||||
|
{
|
||||||
|
private readonly bool readStart;
|
||||||
|
private readonly HashSet<IPoseidonReadOperation> startedReaders =
|
||||||
|
new HashSet<IPoseidonReadOperation>();
|
||||||
|
|
||||||
|
public int IterationCount { get; private set; }
|
||||||
|
|
||||||
|
public PoseidonReadPhaseRunner(bool readStart)
|
||||||
|
{
|
||||||
|
this.readStart = readStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RunIteration(IEnumerable<IPoseidonReadOperation> readers)
|
||||||
|
{
|
||||||
|
if (readers == null) return true;
|
||||||
|
|
||||||
|
IterationCount++;
|
||||||
|
|
||||||
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -24,6 +25,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
public class PoseidonReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, ICommonRegReader
|
public class PoseidonReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, ICommonRegReader
|
||||||
{
|
{
|
||||||
private static readonly ILog log = LogManager.GetLogger(typeof(PoseidonReader));
|
private static readonly ILog log = LogManager.GetLogger(typeof(PoseidonReader));
|
||||||
|
// Simulation must never call the CLI configured for the physical Hat.
|
||||||
|
internal const string SimulatedCliFileName = "cmdSleepTest.exe";
|
||||||
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
||||||
|
|
||||||
readonly PoseidonCfg registerReaderCfg;
|
readonly PoseidonCfg registerReaderCfg;
|
||||||
@@ -62,12 +65,19 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
CurrentPoseidonOp _currentOp;
|
CurrentPoseidonOp _currentOp;
|
||||||
private bool _isReadingStart = true;
|
private bool _isReadingStart = true;
|
||||||
private bool? _isCliLogging;
|
private bool? _isCliLogging;
|
||||||
|
private bool lastCliReadingParsed;
|
||||||
|
private string lastCliReadFailureReason;
|
||||||
|
private bool lastCliReadSucceeded;
|
||||||
|
|
||||||
public CurrentPoseidonOp CurrentOp
|
public CurrentPoseidonOp CurrentOp
|
||||||
{
|
{
|
||||||
get { return _currentOp; }
|
get { return _currentOp; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool LastCliReadingParsed { get { return lastCliReadingParsed; } }
|
||||||
|
public string LastCliReadFailureReason { get { return lastCliReadFailureReason; } }
|
||||||
|
public bool LastCliReadSucceeded { get { return lastCliReadSucceeded; } }
|
||||||
|
|
||||||
public void SetCurrentOp(CurrentPoseidonOp operation = CurrentPoseidonOp.None)
|
public void SetCurrentOp(CurrentPoseidonOp operation = CurrentPoseidonOp.None)
|
||||||
{
|
{
|
||||||
_currentOp = operation ;
|
_currentOp = operation ;
|
||||||
@@ -286,9 +296,25 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
if ((DebugLevel == DebugMode.Normal)||(DebugLevel == DebugMode.Simulate))
|
if ((DebugLevel == DebugMode.Normal)||(DebugLevel == DebugMode.Simulate))
|
||||||
{
|
{
|
||||||
/// Prepare serial port
|
/// Prepare serial port
|
||||||
|
if (registerReaderCfg.MeterType <= 0)
|
||||||
|
{
|
||||||
|
log.WarnFormat(
|
||||||
|
"{0}: configured Poseidon MeterType={1}; using CLI default MeterType={2}.",
|
||||||
|
Name,
|
||||||
|
registerReaderCfg.MeterType,
|
||||||
|
SerialPortData.DefaultPoseidonMeterType);
|
||||||
|
}
|
||||||
|
string cliFileName = GetCliFileNameForMode(DebugLevel, registerReaderCfg.CliFileName);
|
||||||
serialPort = new SerialPortData(string.Format("COM{0}", registerReaderCfg.ComPortNr),
|
serialPort = new SerialPortData(string.Format("COM{0}", registerReaderCfg.ComPortNr),
|
||||||
registerReaderCfg.CliFileName,
|
cliFileName,
|
||||||
registerReaderCfg.MeterType);
|
registerReaderCfg.MeterType);
|
||||||
|
|
||||||
|
if (DebugLevel == DebugMode.Simulate)
|
||||||
|
{
|
||||||
|
log.WarnFormat(
|
||||||
|
"{0}: simulation mode enabled; overriding configured CLI '{1}' with '{2}'.",
|
||||||
|
Name, registerReaderCfg.CliFileName, serialPort.SerialPortCmdClientPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//TODO BUMI prepare serial port - for us do nothing
|
//TODO BUMI prepare serial port - for us do nothing
|
||||||
@@ -311,6 +337,13 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string GetCliFileNameForMode(DebugMode debugMode, string configuredCliFileName)
|
||||||
|
{
|
||||||
|
return debugMode == DebugMode.Simulate
|
||||||
|
? SimulatedCliFileName
|
||||||
|
: configuredCliFileName;
|
||||||
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
log.DebugFormat("{0}:Clear()", Name);
|
log.DebugFormat("{0}:Clear()", Name);
|
||||||
@@ -407,12 +440,18 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
||||||
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
|
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
|
||||||
{
|
{
|
||||||
|
lastCliReadingParsed = false;
|
||||||
|
lastCliReadFailureReason = null;
|
||||||
|
lastCliReadSucceeded = false;
|
||||||
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
incommingTime = -1;
|
incommingTime = -1;
|
||||||
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
|
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
|
||||||
|
|
||||||
CliRunner.Clear();
|
CliRunner.Clear();
|
||||||
_lastOpTimedOut = false;
|
_lastOpTimedOut = false;
|
||||||
|
log.DebugFormat("{0}: starting CLI read, direction={1}, path='{2}', args='{3}'",
|
||||||
|
Name, _isReadingStart ? "start" : "end", serialPort.SerialPortCmdClientPath,
|
||||||
|
serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams));
|
||||||
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
||||||
SerialPortData.EMeterArg.AllParams);
|
SerialPortData.EMeterArg.AllParams);
|
||||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Running;
|
_currentOp = CurrentPoseidonOp.ReadDatastream_Running;
|
||||||
@@ -424,6 +463,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
{
|
{
|
||||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
||||||
incommingTime = CliRunner.IncommingTime;
|
incommingTime = CliRunner.IncommingTime;
|
||||||
|
log.DebugFormat("{0}: CLI task completed for {1}; incomingTime={2}", Name,
|
||||||
|
_isReadingStart ? "START" : "STOP", incommingTime);
|
||||||
}
|
}
|
||||||
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||||
{
|
{
|
||||||
@@ -431,6 +472,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
CliRunner.CancelUndoneTasksAsTimedOut();
|
CliRunner.CancelUndoneTasksAsTimedOut();
|
||||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
||||||
incommingTime = CliRunner.IncommingTime;
|
incommingTime = CliRunner.IncommingTime;
|
||||||
|
log.ErrorFormat("{0}: CLI timeout for {1}; timeoutMs={2}", Name,
|
||||||
|
_isReadingStart ? "START" : "STOP", SafetyTimeOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event.Busy;
|
return Event.Busy;
|
||||||
@@ -449,9 +492,16 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
{
|
{
|
||||||
var task = (Task<JsonDataFromPoseidon>)firstTaskInfo.Task;
|
var task = (Task<JsonDataFromPoseidon>)firstTaskInfo.Task;
|
||||||
data = task.Result;
|
data = task.Result;
|
||||||
|
if (data == null)
|
||||||
|
lastCliReadFailureReason = firstTaskInfo.FailureReason ?? "CLI returned no Poseidon JSON data.";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
lastCliReadFailureReason = _lastOpTimedOut
|
||||||
|
? "CLI read timed out."
|
||||||
|
: "No completed JsonDataFromPoseidon task was available.";
|
||||||
|
log.ErrorFormat("{0}: Poseidon {1} read failed: {2}", Name,
|
||||||
|
_isReadingStart ? "START" : "STOP", lastCliReadFailureReason);
|
||||||
if (_lastOpTimedOut)
|
if (_lastOpTimedOut)
|
||||||
{
|
{
|
||||||
log.Warn($"PoseidonReader {Name}: ReadDatastream timed out, no completed result available.");
|
log.Warn($"PoseidonReader {Name}: ReadDatastream timed out, no completed result available.");
|
||||||
@@ -464,6 +514,15 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
|
|
||||||
if (data != null)
|
if (data != null)
|
||||||
{
|
{
|
||||||
|
string validationError;
|
||||||
|
if (!TryValidateCliReadResponse(data, out validationError))
|
||||||
|
{
|
||||||
|
lastCliReadFailureReason = validationError;
|
||||||
|
log.ErrorFormat("{0}: Poseidon {1} read rejected. {2}", Name,
|
||||||
|
_isReadingStart ? "Begin" : "End", validationError);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (string.IsNullOrEmpty(wmSerialNr))
|
if (string.IsNullOrEmpty(wmSerialNr))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -476,15 +535,28 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double volume;
|
double volumeLi;
|
||||||
if (Double.TryParse(data.Reading, out volume))
|
string dialogValueFailureReason;
|
||||||
|
if (TryGetDialogValue(data, out volumeLi, out dialogValueFailureReason))
|
||||||
{
|
{
|
||||||
double volumeLi = Units.ConvertFrom(Unit.USgal, volume);
|
lastCliReadingParsed = true;
|
||||||
|
lastCliReadSucceeded = true;
|
||||||
|
double volume;
|
||||||
|
TryParseCliReading(data.Reading, out volume);
|
||||||
|
|
||||||
if (_isReadingStart)
|
if (_isReadingStart)
|
||||||
beginWMState = volumeLi;
|
beginWMState = volumeLi;
|
||||||
else
|
else
|
||||||
endWMState = volumeLi;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,6 +569,47 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryParseCliReading(string reading, out double value)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
if (String.IsNullOrWhiteSpace(reading)) return false;
|
||||||
|
return Double.TryParse(reading.Trim().Replace(',', '.'), NumberStyles.Float,
|
||||||
|
CultureInfo.InvariantCulture, out value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates a CLI response and converts its US-gallon reading to the
|
||||||
|
/// litre value assigned to the START/END dialog.
|
||||||
|
/// </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 != true) { failureReason = "NfcTagDetected is false or missing."; return false; }
|
||||||
|
if (data.ReadingComplete != true) { failureReason = "ReadingComplete is false or missing."; return false; }
|
||||||
|
if (String.IsNullOrWhiteSpace(data.Reading)) { failureReason = "Reading is empty."; return false; }
|
||||||
|
failureReason = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>Stop this operation</summary>
|
/// <summary>Stop this operation</summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
{
|
{
|
||||||
public class SerialPortData
|
public class SerialPortData
|
||||||
{
|
{
|
||||||
|
public const string CliDirectory = @"C:\TBF\Cli";
|
||||||
|
// HatCliDemo identifies Poseidon with the numeric meter type 74.
|
||||||
|
// Older persisted configurations can contain the uninitialized value 0.
|
||||||
|
public const int DefaultPoseidonMeterType = 74;
|
||||||
|
|
||||||
private Boolean? _cliExists;
|
private Boolean? _cliExists;
|
||||||
public bool CliExists { get {
|
public bool CliExists { get {
|
||||||
if (_cliExists == null || !_cliExists.HasValue)
|
if (_cliExists == null || !_cliExists.HasValue)
|
||||||
@@ -13,17 +18,26 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
}
|
}
|
||||||
return _cliExists.Value;
|
return _cliExists.Value;
|
||||||
} }
|
} }
|
||||||
public string SerialPortCmdClientPath {
|
public string SerialPortCmdClientPath
|
||||||
#if DEBUG
|
{
|
||||||
get { return Path.Combine("C:\\","TBF","Cli", CmdClientName);}
|
get
|
||||||
#else
|
{
|
||||||
get { return Path.Combine("..","Cli", CmdClientName);}
|
string cliFileName = Path.GetFileName(CmdClientName);
|
||||||
#endif
|
if (String.IsNullOrWhiteSpace(cliFileName))
|
||||||
}
|
cliFileName = "HalCli.exe";
|
||||||
|
|
||||||
|
return Path.Combine(CliDirectory, cliFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
public string CmdClientName { get; set; } = "HalCli.exe";
|
public string CmdClientName { get; set; } = "HalCli.exe";
|
||||||
public string PortName { get; set; }
|
public string PortName { get; set; }
|
||||||
public int MeterType { get; set; }
|
public int MeterType { get; set; }
|
||||||
|
|
||||||
|
public static int NormalizeMeterType(int meterType)
|
||||||
|
{
|
||||||
|
return meterType > 0 ? meterType : DefaultPoseidonMeterType;
|
||||||
|
}
|
||||||
|
|
||||||
public enum EMeterArg {
|
public enum EMeterArg {
|
||||||
Calibration = 0,
|
Calibration = 0,
|
||||||
AllParams = 2,
|
AllParams = 2,
|
||||||
@@ -53,7 +67,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
{
|
{
|
||||||
PortName = portName;
|
PortName = portName;
|
||||||
CmdClientName = cmdClientName;
|
CmdClientName = cmdClientName;
|
||||||
MeterType = meterType;
|
MeterType = NormalizeMeterType(meterType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,42 @@ namespace TBF.Rig.Sequences
|
|||||||
///
|
///
|
||||||
void OpenIPerlCommForm(iPerlCommunicationSeq myRef, SmartComponentBase method, Test test, iPerlCommunicationParams testParams)
|
void OpenIPerlCommForm(iPerlCommunicationSeq myRef, SmartComponentBase method, Test test, iPerlCommunicationParams testParams)
|
||||||
{
|
{
|
||||||
myRef.modelessDlg = new SmartCommunicationForm(method, test, testParams);
|
// This delegate runs on the WinForms thread. Without this boundary a
|
||||||
myRef.modelessDlg.Show();
|
// constructor exception is reported by Control.Invoke only, losing the
|
||||||
|
// useful SmartCommunicationForm stack frame in the application log.
|
||||||
|
log.InfoFormat(
|
||||||
|
"SMART_COMM_FORM_OPEN_START: methodType='{0}', methodName='{1}', test='{2}', activity='{3}', uiThread={4}",
|
||||||
|
method == null ? "<null>" : method.GetType().FullName,
|
||||||
|
method == null ? "<null>" : method.Name,
|
||||||
|
test == null ? "<null>" : test.Name,
|
||||||
|
testParams == null ? "<null>" : testParams.Activity,
|
||||||
|
System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
myRef.modelessDlg = new SmartCommunicationForm(method, test, testParams);
|
||||||
|
log.InfoFormat(
|
||||||
|
"SMART_COMM_FORM_OPEN_CONSTRUCTED: formType='{0}', disposed={1}, handleCreated={2}",
|
||||||
|
myRef.modelessDlg.GetType().FullName,
|
||||||
|
myRef.modelessDlg.IsDisposed,
|
||||||
|
myRef.modelessDlg.IsHandleCreated);
|
||||||
|
|
||||||
|
myRef.modelessDlg.Show();
|
||||||
|
log.InfoFormat(
|
||||||
|
"SMART_COMM_FORM_OPEN_SHOWN: visible={0}, handleCreated={1}",
|
||||||
|
myRef.modelessDlg.Visible,
|
||||||
|
myRef.modelessDlg.IsHandleCreated);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
log.ErrorFormat(
|
||||||
|
"SMART_COMM_FORM_OPEN_FAILED: methodType='{0}', test='{1}', activity='{2}', exception={3}",
|
||||||
|
method == null ? "<null>" : method.GetType().FullName,
|
||||||
|
test == null ? "<null>" : test.Name,
|
||||||
|
testParams == null ? "<null>" : testParams.Activity,
|
||||||
|
exception);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using TBF.Rig.Sequences;
|
|||||||
using Dirichlet.Numerics;
|
using Dirichlet.Numerics;
|
||||||
using TBF.Resources;
|
using TBF.Resources;
|
||||||
using TBF.Rig.RegisterReaders.iPerlReaderUNI;
|
using TBF.Rig.RegisterReaders.iPerlReaderUNI;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
|
||||||
namespace TBF.Rig
|
namespace TBF.Rig
|
||||||
@@ -313,6 +314,11 @@ namespace TBF.Rig
|
|||||||
cmpnt.StartChangeHandler(); /// Start handling parameter change events
|
cmpnt.StartChangeHandler(); /// Start handling parameter change events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep the persisted manual/DataEntry selection aligned with the
|
||||||
|
// actual smart readers configured on this bench. This does not
|
||||||
|
// create placeholder positions for other reader families.
|
||||||
|
SmartReaderSelection.EnsureConfiguredReaders(Program.LocalSettings, ProcessData.SmartHeadsUni);
|
||||||
|
|
||||||
/// Pre-initialize the control board (= buffer the arguments ctrlBrdComponent, tankCapacities)
|
/// Pre-initialize the control board (= buffer the arguments ctrlBrdComponent, tankCapacities)
|
||||||
if (ControlBoardMain == null)
|
if (ControlBoardMain == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ namespace TBF.Rig
|
|||||||
new RegisterReaders.PoseidonReader.Factory(),
|
new RegisterReaders.PoseidonReader.Factory(),
|
||||||
new RegisterReaders.IPerlReader.Factory(), /// the same functionality as TestMethods.iPerlCommunication.iPerlHead.Factory(),
|
new RegisterReaders.IPerlReader.Factory(), /// the same functionality as TestMethods.iPerlCommunication.iPerlHead.Factory(),
|
||||||
new RegisterReaders.iPerlASICReader.Factory(), /// ASIC IPerl, C4 communication
|
new RegisterReaders.iPerlASICReader.Factory(), /// ASIC IPerl, C4 communication
|
||||||
|
new RegisterReaders.AsicReader.Factory(), /// ASIC reader extracted from the proven iPerl implementation
|
||||||
|
new RegisterReaders.AllyReader.Factory(), // ALLY IPerl, communication
|
||||||
new RegisterReaders.GenesisRegReader.Factory(), /// Genesis RegisterReader - dirrect communication with the head
|
new RegisterReaders.GenesisRegReader.Factory(), /// Genesis RegisterReader - dirrect communication with the head
|
||||||
new RegisterReaders.PulsesFromUniCB.Factory(), /// 'RegisterReader'
|
new RegisterReaders.PulsesFromUniCB.Factory(), /// 'RegisterReader'
|
||||||
new RegisterReaders.StandingStartStop.Factory(), /// 'RegisterReader for standing start/stop'
|
new RegisterReaders.StandingStartStop.Factory(), /// 'RegisterReader for standing start/stop'
|
||||||
@@ -201,6 +203,8 @@ namespace TBF.Rig
|
|||||||
//new TestMethods.GenesisCommunication.GenesisHead.Factory(),
|
//new TestMethods.GenesisCommunication.GenesisHead.Factory(),
|
||||||
new TestMethods.GrabImage.Factory(),
|
new TestMethods.GrabImage.Factory(),
|
||||||
new TestMethods.iPerlCommunication.TestMethodFactory(), /// iPerlCommunication
|
new TestMethods.iPerlCommunication.TestMethodFactory(), /// iPerlCommunication
|
||||||
|
new TestMethods.AsicTest.Factory(), /// Explicit ASIC communication test
|
||||||
|
new TestMethods.AllyCalibration.Factory(), // Ally meter test
|
||||||
new TestMethods.LeakTest.Factory(),
|
new TestMethods.LeakTest.Factory(),
|
||||||
new TestMethods.LiveStream.Factory(),
|
new TestMethods.LiveStream.Factory(),
|
||||||
new TestMethods.ManualEntry.Factory(),
|
new TestMethods.ManualEntry.Factory(),
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# ALLY optical-stream procedure
|
||||||
|
|
||||||
|
## Recommended procedure layout
|
||||||
|
|
||||||
|
Use the combined activities at the boundaries of measurement:
|
||||||
|
|
||||||
|
1. `Read serial number` (optional, recommended for traceability)
|
||||||
|
2. `Unseal meter and start optical stream`
|
||||||
|
3. One or more optical measurement activities
|
||||||
|
4. `Stop optical stream`
|
||||||
|
|
||||||
|
The optical measurement must run after the start activity completes and before the stop activity begins.
|
||||||
|
|
||||||
|
## Unseal meter and start optical stream
|
||||||
|
|
||||||
|
This is a production-bench setup operation. It leaves the factory seal **unsealed** and leaves the optical stream **running**.
|
||||||
|
|
||||||
|
| Order | Action | Protocol effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1 | Read factory unseal data | Reads factory seal state, factory ID, programmable text, reading preset and seconds active. These values produce the meter-specific unseal credential. |
|
||||||
|
| 2 | Unseal only if necessary | Sends Factory Unseal with the calculated credential when the meter is sealed. An already unsealed meter is not changed. |
|
||||||
|
| 3 | Verify factory seal | Confirms that the seal is no longer active. The setup stops on failure. |
|
||||||
|
| 4 | Open ALLY valve | Sends valve state `Open`. |
|
||||||
|
| 5 | Disable Spread Spectrum | Required before enabling the diagnostic optical output. |
|
||||||
|
| 6 | Set initial meter mode | Sets meter mode `0x09`. |
|
||||||
|
| 7 | Turn diagnostic LED on | Sets LED output `0xC2`, enabling the optical verification output. |
|
||||||
|
| 8 | Start optical COM capture | Opens the configured ALLY optical port and starts parsing optical telegrams. |
|
||||||
|
|
||||||
|
### Equivalent manual composition
|
||||||
|
|
||||||
|
The procedure activity is intentionally atomic because the factory unseal credential is meter-specific. The UI manual command **Unseal meter** performs steps 1–3. The rest can be composed in the procedure as:
|
||||||
|
|
||||||
|
1. `Open ALLY valve`
|
||||||
|
2. `Disable spread spectrum`
|
||||||
|
3. `Set initial meter mode`
|
||||||
|
4. `Turn diagnostic LED on`
|
||||||
|
|
||||||
|
These individual activities configure the meter output, but do **not** open the TBF optical COM capture. Use `Unseal meter and start optical stream` when TBF must receive and parse optical data.
|
||||||
|
|
||||||
|
## Stop optical stream
|
||||||
|
|
||||||
|
Use this as the final cleanup activity after all optical measurements. It is idempotent with respect to COM capture: it always closes the local stream even if a previous command failed.
|
||||||
|
|
||||||
|
| Order | Action | Protocol effect |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1 | Stop optical processing | Stops the reader's active optical processing loop. |
|
||||||
|
| 2 | Turn diagnostic LED off | Sets LED output `0x00`. |
|
||||||
|
| 3 | Set active meter mode | Sets meter mode `0x02`. |
|
||||||
|
| 4 | Enable Spread Spectrum | Restores normal Spread Spectrum operation. |
|
||||||
|
| 5 | Close optical COM capture | Stops data-stream processing and closes the configured optical serial port, also when an earlier cleanup command failed. |
|
||||||
|
|
||||||
|
The operation does **not** reseal the meter and does not close the valve. Resealing is a separate intentional factory operation.
|
||||||
|
|
||||||
|
### Equivalent manual composition
|
||||||
|
|
||||||
|
The closest procedure-only composition is:
|
||||||
|
|
||||||
|
1. `Turn diagnostic LED off`
|
||||||
|
2. `Set active meter mode`
|
||||||
|
3. `Enable spread spectrum`
|
||||||
|
|
||||||
|
This restores meter settings but it does **not** close TBF optical COM capture. Use `Stop optical stream` whenever the stream was started by `Unseal meter and start optical stream`.
|
||||||
|
|
||||||
|
## Individual activities
|
||||||
|
|
||||||
|
| Activity | What it changes | What it does not do |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Open ALLY valve` | Opens the valve. | Does not alter optical output. |
|
||||||
|
| `Disable spread spectrum` | Disables Spread Spectrum. | Does not select mode or LED output. |
|
||||||
|
| `Set initial meter mode` | Sets mode `0x09`. | Does not enable the diagnostic LED or COM capture. |
|
||||||
|
| `Turn diagnostic LED on` | Sets LED `0xC2`. | Does not check/unseal factory seal or open COM capture. |
|
||||||
|
| `Turn diagnostic LED off` | Sets LED `0x00`. | Does not restore mode, spread spectrum or COM capture. |
|
||||||
|
| `Set active meter mode` | Sets mode `0x02`. | Does not turn off LED, enable Spread Spectrum or close COM capture. |
|
||||||
|
| `Enable spread spectrum` | Restores Spread Spectrum. | Does not stop LED or COM capture. |
|
||||||
|
| `Unseal meter and start optical stream` | Complete setup and COM capture. | Does not reseal automatically. |
|
||||||
|
| `Stop optical stream` | Complete optical cleanup and COM close. | Does not reseal or close the valve. |
|
||||||
|
|
||||||
|
## Factory-seal warning
|
||||||
|
|
||||||
|
`Unseal meter and start optical stream` changes factory-seal state when the meter is sealed. Add it only to procedures intended for authorized verification or calibration benches. A normal accuracy test should use a meter already prepared for optical verification.
|
||||||
@@ -20,7 +20,9 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
WriteCalibrationFactor,
|
WriteCalibrationFactor,
|
||||||
WriteDisplayVolume,
|
WriteDisplayVolume,
|
||||||
SetLcdTimeout,
|
SetLcdTimeout,
|
||||||
StartOffsetLearning
|
StartOffsetLearning,
|
||||||
|
UnsealAndStartOpticalStream,
|
||||||
|
StopOpticalStream
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class AllyCalibrationActivityNames
|
internal static class AllyCalibrationActivityNames
|
||||||
@@ -44,6 +46,8 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
public const string WriteDisplayVolume = "Write display volume";
|
public const string WriteDisplayVolume = "Write display volume";
|
||||||
public const string SetLcdTimeout = "Set LCD timeout";
|
public const string SetLcdTimeout = "Set LCD timeout";
|
||||||
public const string StartOffsetLearning = "Start offset learning";
|
public const string StartOffsetLearning = "Start offset learning";
|
||||||
|
public const string UnsealAndStartOpticalStream = "Unseal meter and start optical stream";
|
||||||
|
public const string StopOpticalStream = "Stop optical stream";
|
||||||
|
|
||||||
public static readonly string[] All =
|
public static readonly string[] All =
|
||||||
{
|
{
|
||||||
@@ -65,7 +69,9 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
WriteCalibrationFactor,
|
WriteCalibrationFactor,
|
||||||
WriteDisplayVolume,
|
WriteDisplayVolume,
|
||||||
SetLcdTimeout,
|
SetLcdTimeout,
|
||||||
StartOffsetLearning
|
StartOffsetLearning,
|
||||||
|
UnsealAndStartOpticalStream,
|
||||||
|
StopOpticalStream
|
||||||
};
|
};
|
||||||
|
|
||||||
public static bool TryParse(string value, out AllyCalibrationActivity activity)
|
public static bool TryParse(string value, out AllyCalibrationActivity activity)
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Common;
|
using Common;
|
||||||
using Config.Entities;
|
using Config.Entities;
|
||||||
|
using log4net;
|
||||||
using Results.Entities;
|
using Results.Entities;
|
||||||
|
using TBF.Rig.GenericDevices;
|
||||||
using TBF.Rig.RegisterReaders.AllyReader;
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||||
using TBF.Rig.Sequences;
|
using TBF.Rig.Sequences;
|
||||||
@@ -13,6 +15,7 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
{
|
{
|
||||||
public class AllyCalibrationSeq : SequenceBase
|
public class AllyCalibrationSeq : SequenceBase
|
||||||
{
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(AllyCalibrationSeq));
|
||||||
private const byte InitialMeterMode = 0x09;
|
private const byte InitialMeterMode = 0x09;
|
||||||
private const byte ActiveMeterMode = 0x02;
|
private const byte ActiveMeterMode = 0x02;
|
||||||
private const byte DiagnosticLedCalibrationMode = 0xC2;
|
private const byte DiagnosticLedCalibrationMode = 0xC2;
|
||||||
@@ -28,6 +31,13 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
TestMethodCfg cfg,
|
TestMethodCfg cfg,
|
||||||
TestMethodParams parameters)
|
TestMethodParams parameters)
|
||||||
{
|
{
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_CALIBRATION_SEQUENCE_START: test='{0}', metersPath='{1}', activity='{2}', configuredPositions={3}, registerReaders={4}",
|
||||||
|
test == null ? "<null>" : test.Name,
|
||||||
|
test == null ? "<null>" : test.MetersPath,
|
||||||
|
parameters == null ? "<null>" : parameters.Activity,
|
||||||
|
BatchRslts == null ? 0 : BatchRslts.WMPositionsCount,
|
||||||
|
sensPath == null || sensPath.RegisterReaders == null ? 0 : sensPath.RegisterReaders.Length);
|
||||||
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, Progress.JustStarted));
|
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, Progress.JustStarted));
|
||||||
|
|
||||||
TestRslt testResult = BatchRslts.GetTestRslt(test.Name, 0);
|
TestRslt testResult = BatchRslts.GetTestRslt(test.Name, 0);
|
||||||
@@ -40,7 +50,14 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
{
|
{
|
||||||
Results.Entities.WaterMeter waterMeter = BatchRslts.Batch.WaterMeters[position];
|
Results.Entities.WaterMeter waterMeter = BatchRslts.Batch.WaterMeters[position];
|
||||||
if (waterMeter == null || waterMeter.Disabled)
|
if (waterMeter == null || waterMeter.Disabled)
|
||||||
|
{
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_CALIBRATION_POSITION_SKIPPED: test='{0}', position={1}, reason={2}",
|
||||||
|
test.Name,
|
||||||
|
position + 1,
|
||||||
|
waterMeter == null ? "water meter is missing" : "water meter is disabled");
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
MeterTestRslt meterResult = BatchRslts.GetMeterTestRslt(
|
MeterTestRslt meterResult = BatchRslts.GetMeterTestRslt(
|
||||||
test.Name,
|
test.Name,
|
||||||
@@ -59,11 +76,34 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
{
|
{
|
||||||
passed = false;
|
passed = false;
|
||||||
resultMessage = "ALLY" + (position + 1) + ": ALLY reader is not configured.";
|
resultMessage = "ALLY" + (position + 1) + ": ALLY reader is not configured.";
|
||||||
|
IRegReader configuredReader = sensPath != null &&
|
||||||
|
sensPath.RegisterReaders != null &&
|
||||||
|
position < sensPath.RegisterReaders.Length
|
||||||
|
? sensPath.RegisterReaders[position]
|
||||||
|
: null;
|
||||||
|
log.WarnFormat(
|
||||||
|
"ALLY_CALIBRATION_READER_MISSING: test='{0}', position={1}; configuredReaderName='{2}', configuredReaderType='{3}', expectedType='AllyMeterReader'.",
|
||||||
|
test.Name,
|
||||||
|
position + 1,
|
||||||
|
configuredReader == null ? "<null>" : configuredReader.Name,
|
||||||
|
configuredReader == null ? "<null>" : configuredReader.GetType().FullName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_CALIBRATION_POSITION_START: test='{0}', position={1}, reader='{2}', activity='{3}'",
|
||||||
|
test.Name,
|
||||||
|
position + 1,
|
||||||
|
reader.Name,
|
||||||
|
parameters.Activity);
|
||||||
passed = ExecuteWithRetries(reader, cfg, parameters, out resultMessage);
|
passed = ExecuteWithRetries(reader, cfg, parameters, out resultMessage);
|
||||||
resultMessage = reader.Name + ": " + resultMessage;
|
resultMessage = reader.Name + ": " + resultMessage;
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_CALIBRATION_POSITION_COMPLETED: test='{0}', position={1}, passed={2}, result='{3}'",
|
||||||
|
test.Name,
|
||||||
|
position + 1,
|
||||||
|
passed,
|
||||||
|
resultMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
messages.Add(resultMessage);
|
messages.Add(resultMessage);
|
||||||
@@ -90,7 +130,7 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
return new List<Event> { Event.Done };
|
return new List<Event> { Event.Done };
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ExecuteWithRetries(
|
internal static bool ExecuteWithRetries(
|
||||||
AllyMeterReader reader,
|
AllyMeterReader reader,
|
||||||
TestMethodCfg cfg,
|
TestMethodCfg cfg,
|
||||||
TestMethodParams parameters,
|
TestMethodParams parameters,
|
||||||
@@ -103,6 +143,13 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_CALIBRATION_COMMAND_START: reader='{0}', activity='{1}', attempt={2}/{3}, timeoutMs={4}",
|
||||||
|
reader.Name,
|
||||||
|
parameters.Activity,
|
||||||
|
attempt,
|
||||||
|
attempts,
|
||||||
|
cfg.CommandTimeoutMs);
|
||||||
bool passed = ExecuteOnce(reader, cfg, parameters, out resultMessage);
|
bool passed = ExecuteOnce(reader, cfg, parameters, out resultMessage);
|
||||||
if (!passed)
|
if (!passed)
|
||||||
return false;
|
return false;
|
||||||
@@ -113,6 +160,13 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
lastException = ex;
|
lastException = ex;
|
||||||
|
log.WarnFormat(
|
||||||
|
"ALLY_CALIBRATION_COMMAND_FAILED: reader='{0}', activity='{1}', attempt={2}/{3}, error='{4}'",
|
||||||
|
reader.Name,
|
||||||
|
parameters.Activity,
|
||||||
|
attempt,
|
||||||
|
attempts,
|
||||||
|
ex.Message);
|
||||||
if (attempt < attempts && cfg.DelayBetweenAttemptsMs > 0)
|
if (attempt < attempts && cfg.DelayBetweenAttemptsMs > 0)
|
||||||
Thread.Sleep(cfg.DelayBetweenAttemptsMs);
|
Thread.Sleep(cfg.DelayBetweenAttemptsMs);
|
||||||
}
|
}
|
||||||
@@ -251,6 +305,16 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
resultMessage = "OffsetLearning=1000 samples, 1s delay";
|
resultMessage = "OffsetLearning=1000 samples, 1s delay";
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case AllyCalibrationActivity.UnsealAndStartOpticalStream:
|
||||||
|
reader.UnsealAndStartOpticalVerificationStream(timeout);
|
||||||
|
resultMessage = "FactorySeal=unsealed; OpticalStream=active (valve=open, spread spectrum=disabled, mode=0x09, LED=0xC2)";
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case AllyCalibrationActivity.StopOpticalStream:
|
||||||
|
reader.StopOpticalVerificationStream(timeout);
|
||||||
|
resultMessage = "OpticalStream=stopped (LED=off, mode=active 0x02, spread spectrum=enabled, COM capture=stopped)";
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException("Unsupported ALLY calibration activity.");
|
throw new InvalidOperationException("Unsupported ALLY calibration activity.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Common;
|
||||||
|
using Config.Entities;
|
||||||
|
using log4net;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.GenericDevices;
|
||||||
|
using TBF.Rig.Sequences;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
using TBF.UiBridge;
|
||||||
|
|
||||||
|
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ALLY equivalent of iPerlCommunicationSeq. Command execution remains in
|
||||||
|
/// AllyTestCorrections; this sequence owns only the modeless dialog lifecycle.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AllyCommunicationSeq : SequenceBase
|
||||||
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(AllyCommunicationSeq));
|
||||||
|
private Form modelessDialog;
|
||||||
|
private volatile bool dialogClosed;
|
||||||
|
|
||||||
|
private delegate void OpenFormDelegate(AllyCommunicationSeq sequence, SmartComponentBase method, Test test, ITestParams parameters);
|
||||||
|
|
||||||
|
private void OpenForm(AllyCommunicationSeq sequence, SmartComponentBase method, Test test, ITestParams parameters)
|
||||||
|
{
|
||||||
|
sequence.modelessDialog = new SmartCommunicationForm(method, test, parameters);
|
||||||
|
sequence.modelessDialog.FormClosed += delegate
|
||||||
|
{
|
||||||
|
sequence.dialogClosed = true;
|
||||||
|
log.Info("ALLY_SMART_COMM_FORM_CLOSED_SIGNAL: FormClosed event received.");
|
||||||
|
};
|
||||||
|
sequence.modelessDialog.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<Event> Execute(Test test, int repetitionNr, SmartComponentBase method, ITestParams parameters)
|
||||||
|
{
|
||||||
|
checkUiOp = new Operations.CheckUIOp(true);
|
||||||
|
modelessDialog = null;
|
||||||
|
dialogClosed = false;
|
||||||
|
|
||||||
|
Program.MainWnd.Invoke(new OpenFormDelegate(OpenForm), new object[] { this, method, test, parameters });
|
||||||
|
Bridge.OnActivity(this, TBF.Resources.Strings.Meter_Communication_in_progress);
|
||||||
|
|
||||||
|
bool stopPressed;
|
||||||
|
bool completed;
|
||||||
|
IList<Event> events;
|
||||||
|
State.Create("AllyCommunicationSeq : Wait until the SmartCommunicationForm is closed")
|
||||||
|
.AddOperation(checkUiOp)
|
||||||
|
.EnterState();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
events = StateMachine.WaitRunDevsRunOps();
|
||||||
|
stopPressed = TestAndLogUiCmdStop(test, events);
|
||||||
|
completed = dialogClosed ||
|
||||||
|
(modelessDialog is IHasCompleted && ((IHasCompleted)modelessDialog).Completed);
|
||||||
|
} while (!stopPressed && !completed);
|
||||||
|
|
||||||
|
if (stopPressed)
|
||||||
|
{
|
||||||
|
Bridge.OnCloseModelessForm(this, null);
|
||||||
|
return new List<Event> { Event.UiCmdStop };
|
||||||
|
}
|
||||||
|
|
||||||
|
Bridge.OnTestProgress(this, new TestProgressEventArgs(test.Name, Progress.Completed));
|
||||||
|
log.InfoFormat("ALLY_SMART_COMM_SEQUENCE_COMPLETED: test='{0}', formClosed={1}",
|
||||||
|
test == null ? "<null>" : test.Name, dialogClosed);
|
||||||
|
modelessDialog = null;
|
||||||
|
return new List<Event> { Event.Done };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,16 @@ using log4net;
|
|||||||
using TBF.Rig.Generic;
|
using TBF.Rig.Generic;
|
||||||
using TBF.Rig.GenericDevices;
|
using TBF.Rig.GenericDevices;
|
||||||
using TBF.Rig.Sequences;
|
using TBF.Rig.Sequences;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
using TBF.UiBridge;
|
using TBF.UiBridge;
|
||||||
|
|
||||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||||
{
|
{
|
||||||
public class TestMethod : ComponentBase, ISimultTestMethod, ITestMethodSmart
|
public class TestMethod : SmartComponentBase, ISimultTestMethod, ITestMethodSmart
|
||||||
{
|
{
|
||||||
private static readonly ILog log = LogManager.GetLogger(typeof(TestMethod));
|
private static readonly ILog log = LogManager.GetLogger(typeof(TestMethod));
|
||||||
private readonly TestMethodCfg allyCfg;
|
private readonly TestMethodCfg allyCfg;
|
||||||
|
private readonly bool[] meterCommunicationMilestones = new bool[32];
|
||||||
|
|
||||||
public TestMethod()
|
public TestMethod()
|
||||||
{
|
{
|
||||||
@@ -72,15 +74,45 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
log.FatalFormat("{0} initialized: {1}", Name, this);
|
log.FatalFormat("{0} initialized: {1}", Name, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void MeterCommMilestone(int iItem, bool value)
|
||||||
|
{
|
||||||
|
if (iItem >= 0 && iItem < meterCommunicationMilestones.Length)
|
||||||
|
meterCommunicationMilestones[iItem] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsMeterCommMilestone(int iItem)
|
||||||
|
{
|
||||||
|
return iItem >= 0 && iItem < meterCommunicationMilestones.Length && meterCommunicationMilestones[iItem];
|
||||||
|
}
|
||||||
|
|
||||||
public IList<Event> Execute(Test test, int repetitionNr, bool isLastRepetition)
|
public IList<Event> Execute(Test test, int repetitionNr, bool isLastRepetition)
|
||||||
{
|
{
|
||||||
TestMethodParams parameters = allyCfg.TestParams as TestMethodParams;
|
TestMethodParams parameters = allyCfg.TestParams as TestMethodParams;
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_TEST_EXECUTE: test='{0}', repetition={1}, last={2}, debugMode={3}, activity='{4}', parametersLoaded={5}",
|
||||||
|
test == null ? "<null>" : test.Name,
|
||||||
|
repetitionNr,
|
||||||
|
isLastRepetition,
|
||||||
|
DebugLevel,
|
||||||
|
parameters == null ? "<null>" : parameters.Activity,
|
||||||
|
parameters != null);
|
||||||
|
|
||||||
if (parameters == null)
|
if (parameters == null)
|
||||||
|
{
|
||||||
|
log.ErrorFormat(
|
||||||
|
"ALLY_TEST_NOT_STARTED: test='{0}' cannot execute because the runtime TestParams provider is null. " +
|
||||||
|
"No ALLY command, correction adapter, or SmartCommunicationForm can be started.",
|
||||||
|
test == null ? "<null>" : test.Name);
|
||||||
throw new InvalidOperationException("ALLY calibration test parameters are missing.");
|
throw new InvalidOperationException("ALLY calibration test parameters are missing.");
|
||||||
|
}
|
||||||
|
|
||||||
if (DebugLevel == DebugMode.Normal)
|
if (DebugLevel == DebugMode.Normal)
|
||||||
return new AllyCalibrationSeq().Execute(test, repetitionNr, this, allyCfg, parameters);
|
{
|
||||||
|
log.Info("ALLY_TEST_ROUTE: opening SmartCommunicationForm through AllyCommunicationSeq and AllyTestCorrections.");
|
||||||
|
return new AllyCommunicationSeq().Execute(test, repetitionNr, this, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("ALLY_TEST_ROUTE: simulation branch selected; no physical ALLY command will be sent and no Optical heads dialog will be opened.");
|
||||||
new AllyCalibrationSeq().MakeSimulatedTrivial(test, repetitionNr, test.Part);
|
new AllyCalibrationSeq().MakeSimulatedTrivial(test, repetitionNr, test.Part);
|
||||||
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, 1, Progress.Completed));
|
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, 1, Progress.Completed));
|
||||||
Bridge.OnTestCompleted(
|
Bridge.OnTestCompleted(
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Ports;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using Common;
|
using Common;
|
||||||
using Config.Entities;
|
using Config.Entities;
|
||||||
using TBF.Rig.Generic;
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||||
|
using SmartTestParams = TBF.Rig.Generic.ITestParams;
|
||||||
|
|
||||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||||
{
|
{
|
||||||
public class TestMethodCfg : ComponentCfgBase, IComponentCfg, IParamsProvider
|
// Implement the common smart-meter configuration contract so the ALLY test can
|
||||||
|
// use SmartCommunicationForm in exactly the same way as the ASIC test method.
|
||||||
|
public class TestMethodCfg : ComponentCfgBase, ITestMethodCfg, IParamsProvider
|
||||||
{
|
{
|
||||||
public static readonly XmlSerializer Serializer =
|
public static readonly XmlSerializer Serializer =
|
||||||
XmlSerializer.FromTypes(new[] { typeof(TestMethodCfg) })[0];
|
XmlSerializer.FromTypes(new[] { typeof(TestMethodCfg) })[0];
|
||||||
@@ -18,11 +23,31 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
public string ExpectedDeviceType;
|
public string ExpectedDeviceType;
|
||||||
public string ExpectedFirmwareVersion;
|
public string ExpectedFirmwareVersion;
|
||||||
|
|
||||||
|
// The generic SmartCommunicationForm consumes these values through
|
||||||
|
// ITestMethodCfg. ALLY uses one serialized command worker; the fields are
|
||||||
|
// kept separate from the ALLY-specific command timeout above.
|
||||||
|
public int DelayBetweenRetries { get; set; }
|
||||||
|
public int MaxCommRetries { get; set; }
|
||||||
|
public int WaitTimeAfterFailure { get; set; }
|
||||||
|
public int PassThroughWaitTime { get; set; }
|
||||||
|
public int NrThreads { get; set; }
|
||||||
|
public int CommTimeout { get; set; }
|
||||||
|
public int MciTimeoutMs { get; set; }
|
||||||
|
public int BaudRate { get; set; }
|
||||||
|
public int DataBits { get; set; }
|
||||||
|
public Parity ParityBit { get; set; }
|
||||||
|
public StopBits StopBits { get; set; }
|
||||||
|
public bool UseWebService { get; set; }
|
||||||
|
public string BaseUrl { get; set; }
|
||||||
|
public string RelativeUrl { get; set; }
|
||||||
|
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
public ITestParams TestParams { get; set; }
|
public SmartTestParams TestParams { get; set; }
|
||||||
|
|
||||||
private TestMethodCfg()
|
private TestMethodCfg()
|
||||||
{
|
{
|
||||||
|
InitializeAll();
|
||||||
|
TestParams = new TestMethodParams(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestMethodCfg(IComponentFactory factory)
|
public TestMethodCfg(IComponentFactory factory)
|
||||||
@@ -68,6 +93,20 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
DelayBetweenAttemptsMs = 250;
|
DelayBetweenAttemptsMs = 250;
|
||||||
ExpectedDeviceType = "SWM003";
|
ExpectedDeviceType = "SWM003";
|
||||||
ExpectedFirmwareVersion = string.Empty;
|
ExpectedFirmwareVersion = string.Empty;
|
||||||
|
DelayBetweenRetries = 250;
|
||||||
|
MaxCommRetries = 4;
|
||||||
|
WaitTimeAfterFailure = 0;
|
||||||
|
PassThroughWaitTime = 0;
|
||||||
|
NrThreads = 1;
|
||||||
|
CommTimeout = CommandTimeoutMs;
|
||||||
|
MciTimeoutMs = 5000;
|
||||||
|
BaudRate = 2400;
|
||||||
|
DataBits = 8;
|
||||||
|
ParityBit = Parity.None;
|
||||||
|
StopBits = StopBits.One;
|
||||||
|
UseWebService = false;
|
||||||
|
BaseUrl = string.Empty;
|
||||||
|
RelativeUrl = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ParamsCount()
|
public int ParamsCount()
|
||||||
@@ -179,7 +218,7 @@ namespace TBF.Rig.TestMethods.AllyCalibration
|
|||||||
DelayBetweenAttemptsMs = DelayBetweenAttemptsMs,
|
DelayBetweenAttemptsMs = DelayBetweenAttemptsMs,
|
||||||
ExpectedDeviceType = ExpectedDeviceType,
|
ExpectedDeviceType = ExpectedDeviceType,
|
||||||
ExpectedFirmwareVersion = ExpectedFirmwareVersion,
|
ExpectedFirmwareVersion = ExpectedFirmwareVersion,
|
||||||
TestParams = TestParams == null ? null : TestParams.Clone() as ITestParams
|
TestParams = TestParams == null ? null : TestParams.Clone() as SmartTestParams
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ using TBF.Resources;
|
|||||||
|
|
||||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||||
{
|
{
|
||||||
public class TestMethodParams : TestParamsBase, IParamsProvider, ITestParams
|
// Use the same parameter contract as SmartCommunicationForm explicitly.
|
||||||
|
// Config.Entities also exposes an ITestParams name in some builds.
|
||||||
|
public class TestMethodParams : TestParamsBase, IParamsProvider, TBF.Rig.Generic.ITestParams
|
||||||
{
|
{
|
||||||
public static readonly XmlSerializer Serializer =
|
public static readonly XmlSerializer Serializer =
|
||||||
XmlSerializer.FromTypes(new[] { typeof(TestMethodParams) })[0];
|
XmlSerializer.FromTypes(new[] { typeof(TestMethodParams) })[0];
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using LegacyTestMethodCfg = TBF.Rig.TestMethods.iPerlCommunication.TestMethodCfg;
|
||||||
|
|
||||||
|
namespace TBF.Rig.TestMethods.AsicTest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory for the explicit ASIC test method. It retains the legacy XML
|
||||||
|
/// settings schema so existing communication parameters stay compatible.
|
||||||
|
/// </summary>
|
||||||
|
public class Factory : IComponentFactory
|
||||||
|
{
|
||||||
|
public string ClassName { get { return GetType().Namespace.Substring(8); } }
|
||||||
|
|
||||||
|
public IComponent DummyComponent()
|
||||||
|
{
|
||||||
|
return new TestMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components)
|
||||||
|
{
|
||||||
|
return new TestMethod(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponentCfg DefaultConfig()
|
||||||
|
{
|
||||||
|
return new LegacyTestMethodCfg(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
|
||||||
|
{
|
||||||
|
return ComponentCfgBase.CreateFromDbEntity(LegacyTestMethodCfg.Serializer, component, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Common;
|
||||||
|
using Config.Entities;
|
||||||
|
using log4net;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.Sequences;
|
||||||
|
using TBF.UiBridge;
|
||||||
|
using AsicCommunicationSequence = TBF.Rig.Sequences.iPerlCommunicationSeq;
|
||||||
|
using LegacyTestMethodCfg = TBF.Rig.TestMethods.iPerlCommunication.TestMethodCfg;
|
||||||
|
using LegacyTestParams = TBF.Rig.TestMethods.iPerlCommunication.iPerlCommunicationParams;
|
||||||
|
using SmartTestParams = TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.iPerlCommunicationParams;
|
||||||
|
|
||||||
|
namespace TBF.Rig.TestMethods.AsicTest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ASIC-facing test method. It retains the proven iPerl protocol settings,
|
||||||
|
/// but executes through the shared smart-reader workflow.
|
||||||
|
/// </summary>
|
||||||
|
public class TestMethod : iPerlCommunication.TestMethod
|
||||||
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(TestMethod));
|
||||||
|
|
||||||
|
public TestMethod()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestMethod(IComponentCfg cfg)
|
||||||
|
: base(cfg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses the multi-reader sequence, not the legacy iPerl dialog. The
|
||||||
|
/// sequence opens SmartCommunicationForm, which selects AsicCorrections
|
||||||
|
/// from the register readers in the active Meters Path.
|
||||||
|
/// </summary>
|
||||||
|
public override IList<Event> Execute(Test test, int repetitionNr, bool isLastRepetition)
|
||||||
|
{
|
||||||
|
LegacyTestMethodCfg config = Cfg as LegacyTestMethodCfg;
|
||||||
|
LegacyTestParams parameters = config == null ? null : config.TestParams as LegacyTestParams;
|
||||||
|
if (parameters == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"ASIC test parameters are missing. Configure the ASIC test activity before starting the procedure.");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfoFormat(
|
||||||
|
"ASIC_TEST_ROUTE: test='{0}', repetition={1}, last={2}, debugMode={3}, activity='{4}', route=SmartCommunicationForm/AsicCorrections",
|
||||||
|
test == null ? "<null>" : test.Name,
|
||||||
|
repetitionNr,
|
||||||
|
isLastRepetition,
|
||||||
|
DebugLevel,
|
||||||
|
parameters.Activity);
|
||||||
|
|
||||||
|
if (DebugLevel == DebugMode.Normal)
|
||||||
|
{
|
||||||
|
return new AsicCommunicationSequence().Execute(test, repetitionNr, this,
|
||||||
|
ToSmartCommunicationParams(parameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
new AsicCommunicationSequence().MakeSimulatedTrivial(test, repetitionNr, test.Part);
|
||||||
|
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, 1, Progress.Completed));
|
||||||
|
Bridge.OnTestCompleted(this,
|
||||||
|
new TestCompletedEventArgs(test.Name,
|
||||||
|
ProcessData.BatchRslts.GetTestRslt(Common.Utils.GetTestName(test.Name, 1, 1), 0)));
|
||||||
|
return new List<Event> { Event.Done };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SmartTestParams ToSmartCommunicationParams(LegacyTestParams parameters)
|
||||||
|
{
|
||||||
|
// The persisted AsicTest configuration intentionally retains the
|
||||||
|
// legacy iPerl parameter type. SmartCommunicationForm uses its own
|
||||||
|
// equivalent type, so copy the three persisted values at the route
|
||||||
|
// boundary instead of changing existing procedure rows.
|
||||||
|
return new SmartTestParams
|
||||||
|
{
|
||||||
|
Activity = parameters.Activity,
|
||||||
|
SimultWithPrevious = parameters.SimultWithPrevious,
|
||||||
|
SimultWithNext = parameters.SimultWithNext
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,7 +107,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<Event> Execute(Test test, int repetNr, bool isLastRepetition)
|
public virtual IList<Event> Execute(Test test, int repetNr, bool isLastRepetition)
|
||||||
{
|
{
|
||||||
if (DebugLevel == DebugMode.Normal)
|
if (DebugLevel == DebugMode.Normal)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -190,8 +190,16 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
|||||||
///
|
///
|
||||||
public int WMPulses { get { return wmPulses; } }
|
public int WMPulses { get { return wmPulses; } }
|
||||||
public int WMRefPulses { get { return wmRefPulses; } }
|
public int WMRefPulses { get { return wmRefPulses; } }
|
||||||
public double BeginWMState { get { return ResolveNaNDouble(beginWMState); } }
|
public double BeginWMState
|
||||||
public double EndWMState { get { return ResolveNaNDouble(endWMState); } }
|
{
|
||||||
|
get { return ResolveNaNDouble(beginWMState); }
|
||||||
|
set { beginWMState = value; }
|
||||||
|
}
|
||||||
|
public double EndWMState
|
||||||
|
{
|
||||||
|
get { return ResolveNaNDouble(endWMState); }
|
||||||
|
set { endWMState = value; }
|
||||||
|
}
|
||||||
public double WMVolume { get { return ResolveNaNDouble(wmVolume); } }
|
public double WMVolume { get { return ResolveNaNDouble(wmVolume); } }
|
||||||
public double WMTestTime { get { return ResolveNaNDouble(wmTestTime); } }
|
public double WMTestTime { get { return ResolveNaNDouble(wmTestTime); } }
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
ThreadId,
|
ThreadId,
|
||||||
WMNr0,
|
WMNr0,
|
||||||
(Ihead != null) ? Ihead.Name : "null",
|
(Ihead != null) ? Ihead.Name : "null",
|
||||||
Wm.WMPosition,
|
Wm == null ? "null" : Wm.WMPosition.ToString(),
|
||||||
(CommMessage != null) ? CommMessage : "null",
|
(CommMessage != null) ? CommMessage : "null",
|
||||||
CommErr);
|
CommErr);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ using TBF.Rig.Generic;
|
|||||||
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||||
using TBF.Rig.RegisterReaders.iPerlReaderUNI;
|
using TBF.Rig.RegisterReaders.iPerlReaderUNI;
|
||||||
using TBF.Rig.RegisterReaders.PoseidonReader;
|
using TBF.Rig.RegisterReaders.PoseidonReader;
|
||||||
|
using AllyMeterReader = TBF.Rig.RegisterReaders.AllyReader.AllyMeterReader;
|
||||||
|
using AsicReader = TBF.Rig.RegisterReaders.AsicReader.AsicReader;
|
||||||
|
using IPerlASICSmartReader = TBF.Rig.RegisterReaders.iPerlASICReader.implementations.SmartReader;
|
||||||
using TBF.Rig.Sequences;
|
using TBF.Rig.Sequences;
|
||||||
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
||||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
@@ -67,6 +70,13 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
public bool Completed { get { return formCompleted; } }
|
public bool Completed { get { return formCompleted; } }
|
||||||
bool formCompleted;
|
bool formCompleted;
|
||||||
|
|
||||||
|
// A communication worker can finish before this modeless form receives
|
||||||
|
// its first paint message (most noticeably with one meter). Keep the
|
||||||
|
// result visible briefly before closing the form automatically.
|
||||||
|
private bool closeAfterResultRequested;
|
||||||
|
private System.Windows.Forms.Timer closeAfterResultTimer;
|
||||||
|
private EventHandler closeAfterResultShownHandler;
|
||||||
|
|
||||||
bool forcedClose; /// Set in the forced close handler
|
bool forcedClose; /// Set in the forced close handler
|
||||||
|
|
||||||
|
|
||||||
@@ -98,15 +108,22 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
public CheckBoxImage[] CheckBoxes { get => this.checkBoxes; }
|
public CheckBoxImage[] CheckBoxes { get => this.checkBoxes; }
|
||||||
public int[] CkbIndex { get => SmartCommunicationForm.ckbIndex; }
|
public int[] CkbIndex { get => SmartCommunicationForm.ckbIndex; }
|
||||||
public bool[] CkbState { get => SmartCommunicationForm.ckbState; }
|
public bool[] CkbState { get => SmartCommunicationForm.ckbState; }
|
||||||
|
public IList<int> WaterMeterPositions0 { get => SmartCommunicationForm.waterMeterPositions0; }
|
||||||
|
|
||||||
///
|
///
|
||||||
/// RFID multiplexer PCB / RFID serial port and worker thread related variables
|
/// RFID multiplexer PCB / RFID serial port and worker thread related variables
|
||||||
///
|
///
|
||||||
private static List<ICorrections> _corrections;
|
private static List<ICorrections> _corrections;
|
||||||
|
// Setting the initial combobox item raises SelectedIndexChanged before the
|
||||||
|
// constructor has finished preparing the form and its local settings.
|
||||||
|
private bool initializingMeterTypeItems;
|
||||||
private static ICorrections Correction
|
private static ICorrections Correction
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (_corrections == null || _corrections.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(SelectedTypeReader) && _corrections.Count > 0)
|
if (string.IsNullOrEmpty(SelectedTypeReader) && _corrections.Count > 0)
|
||||||
{
|
{
|
||||||
return _corrections.First();
|
return _corrections.First();
|
||||||
@@ -139,10 +156,22 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
{
|
{
|
||||||
List<ICorrections> correctionsList = new List<ICorrections>();
|
List<ICorrections> correctionsList = new List<ICorrections>();
|
||||||
|
|
||||||
foreach (var smartHead in ProcessData.SmartHeadsUni)
|
// In a test use the reader family from the selected Meters Path. The
|
||||||
|
// global list can contain iPerl, ASIC and ALLY readers at the same time.
|
||||||
|
IEnumerable<ISmartReader> smartHeads = ProcessData.RegisterReaders == null
|
||||||
|
? ProcessData.SmartHeadsUni
|
||||||
|
: ProcessData.RegisterReaders.OfType<ISmartReader>();
|
||||||
|
foreach (var smartHead in smartHeads)
|
||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
if (smartHead is IperlHead iperlHead)
|
if (smartHead is AsicReader)
|
||||||
|
{
|
||||||
|
if (correctionsList.Any(x => x is AsicCorrections))
|
||||||
|
continue;
|
||||||
|
correctionsList.Add(new AsicCorrections(this, componentBase, cfg, tests, multiTestParams));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (smartHead is IperlHead iperlHead)
|
||||||
{
|
{
|
||||||
if (correctionsList.Any(x => x is IPerlCorrections))
|
if (correctionsList.Any(x => x is IPerlCorrections))
|
||||||
continue;
|
continue;
|
||||||
@@ -150,15 +179,31 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smartHead is SmartReader smartReader)
|
if (smartHead is IPerlASICSmartReader)
|
||||||
{
|
{
|
||||||
if (correctionsList.Any(x => x is SmartReader))
|
if (correctionsList.Any(x => x is IPerlASICCorrections))
|
||||||
continue;
|
continue;
|
||||||
correctionsList.Add(new PoseidonCorrections(this,log, rfidDataLogger, componentBase, cfg, tests, multiTestParams));
|
correctionsList.Add(new IPerlASICCorrections(this, componentBase, cfg, tests, multiTestParams));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Exception("Unknown smart head type");
|
if (smartHead is AllyMeterReader)
|
||||||
|
{
|
||||||
|
if (correctionsList.Any(x => x is AllyTestCorrections))
|
||||||
|
continue;
|
||||||
|
correctionsList.Add(new AllyTestCorrections(this, componentBase, cfg, tests, multiTestParams));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smartHead is SmartReader smartReader)
|
||||||
|
{
|
||||||
|
if (correctionsList.Any(x => x is PoseidonCorrections))
|
||||||
|
continue;
|
||||||
|
correctionsList.Add(new PoseidonCorrections(this,log, rfidDataLogger, componentBase, cfg, tests, multiTestParams));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Unknown smart head type");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -169,68 +214,39 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
}
|
}
|
||||||
private static List<ICorrections> GetNewCorrectionList(SmartCommunicationForm form)
|
private static List<ICorrections> GetNewCorrectionList(SmartCommunicationForm form)
|
||||||
{
|
{
|
||||||
List<ICorrections> correctionsList = new List<ICorrections>();
|
// Manual communication is extended by registering a correction adapter here.
|
||||||
|
// An adapter is included only when at least one configured reader belongs to it.
|
||||||
|
// This keeps unsupported protocols out of the type selector and context menu.
|
||||||
|
List<ICorrections> candidates = new List<ICorrections>
|
||||||
|
{
|
||||||
|
new AsicCorrections(form),
|
||||||
|
new IPerlCorrections(form),
|
||||||
|
new IPerlASICCorrections(form),
|
||||||
|
new GenesisCorrections(form),
|
||||||
|
new AllyCorrections(form),
|
||||||
|
new PoseidonCorrections(form)
|
||||||
|
};
|
||||||
|
|
||||||
foreach (var smartHead in ProcessData.SmartHeadsUni)
|
if (ProcessData.SmartHeadsUni == null)
|
||||||
{
|
return new List<ICorrections>();
|
||||||
try{
|
|
||||||
if (smartHead is IperlHead iperlHead)
|
|
||||||
{
|
|
||||||
if (correctionsList.Any(x => x is IPerlCorrections))
|
|
||||||
continue;
|
|
||||||
correctionsList.Add(new IPerlCorrections(form));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (smartHead is SmartReader smartReader)
|
return candidates
|
||||||
{
|
.Where(correction => ProcessData.SmartHeadsUni.Any(correction.IsFamilyOfSmartReader))
|
||||||
if (correctionsList.Any(x => x is SmartReader))
|
.ToList();
|
||||||
continue;
|
|
||||||
correctionsList.Add(new PoseidonCorrections(form));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Unknown smart head type");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
log.Error("IdentifyReaderTypes()", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return correctionsList;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<string> IdentifyReaderTypes()
|
public static List<string> IdentifyReaderTypes()
|
||||||
{
|
{
|
||||||
List<string> typeReaders = new List<string>();
|
List<string> typeReaders = new List<string>();
|
||||||
|
|
||||||
foreach (var smartHead in ProcessData.SmartHeadsUni)
|
if (_corrections == null || ProcessData.SmartHeadsUni == null)
|
||||||
{
|
return typeReaders;
|
||||||
try
|
|
||||||
{
|
|
||||||
if (typeReaders.Contains(smartHead.ClassName))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (smartHead is IperlHead iperlHead)
|
foreach (ICorrections correction in _corrections)
|
||||||
{
|
{
|
||||||
typeReaders.Add(iperlHead.ClassName);
|
if (ProcessData.SmartHeadsUni.Any(correction.IsFamilyOfSmartReader))
|
||||||
continue;
|
typeReaders.Add(correction.TypeIdentificatorName());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smartHead is SmartReader smartReader)
|
|
||||||
{
|
|
||||||
typeReaders.Add(smartReader.ClassName);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Unknown smart head type");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
log.Error("IdentifyReaderTypes()", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return typeReaders;
|
return typeReaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,9 +292,42 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
/// <summary> Parameterless constructor (without watermeters, threads) </summary>
|
/// <summary> Parameterless constructor (without watermeters, threads) </summary>
|
||||||
public SmartCommunicationForm()
|
public SmartCommunicationForm()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.Icon = Properties.Resources.TBF_icon;
|
this.Icon = Properties.Resources.TBF_icon;
|
||||||
|
log.Info("SMART_COMM_FORM_CREATED");
|
||||||
|
Shown += (sender, args) =>
|
||||||
|
{
|
||||||
|
log.Info("SMART_COMM_FORM_OPENED");
|
||||||
|
LogFormState("SHOWN");
|
||||||
|
};
|
||||||
|
FormClosed += (sender, args) =>
|
||||||
|
{
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_CLOSED: forced={0}, completed={1}, delayedClose={2}",
|
||||||
|
forcedClose, formCompleted, closeAfterResultRequested);
|
||||||
|
LogFormState("CLOSED_STATE");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogFormState(string stage)
|
||||||
|
{
|
||||||
|
string correctionNames = _corrections == null
|
||||||
|
? "<null>"
|
||||||
|
: string.Join(",", _corrections.Select(correction => correction.TypeIdentificatorName()));
|
||||||
|
string positions = waterMeterPositions0 == null
|
||||||
|
? "<null>"
|
||||||
|
: string.Join(",", waterMeterPositions0.Select(position => (position + 1).ToString()));
|
||||||
|
|
||||||
|
log.InfoFormat(
|
||||||
|
"SMART_COMM_FORM_{0}: editMode={1}, visible={2}, activity='{3}', selectedType='{4}', corrections=[{5}], heads={6}, positions=[{7}]",
|
||||||
|
stage,
|
||||||
|
checkBoxesEditMode,
|
||||||
|
Visible,
|
||||||
|
activityLabel == null ? "<uninitialized>" : activityLabel.Text,
|
||||||
|
SelectedTypeReader ?? "<null>",
|
||||||
|
correctionNames,
|
||||||
|
iperlHeads == null ? 0 : iperlHeads.Count,
|
||||||
|
positions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -323,28 +372,40 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="waterMetersCount">Number of text boxes for serial numbers</param>
|
/// <param name="waterMetersCount">Number of text boxes for serial numbers</param>
|
||||||
public SmartCommunicationForm(Generic.IComponent componentBase, IList<Test> tests, IList<ITestParams> multiTestParams)
|
public SmartCommunicationForm(Generic.IComponent componentBase, IList<Test> tests, IList<ITestParams> multiTestParams)
|
||||||
: this()
|
: this()
|
||||||
{
|
{
|
||||||
checkBoxesEditMode = false;
|
checkBoxesEditMode = false;
|
||||||
|
|
||||||
ITestMethodCfg cfg = (componentBase as ITestMethodCfg);
|
ITestMethodCfg cfg = (componentBase as ITestMethodCfg);
|
||||||
ISmartTestMethod smartTestMethod = componentBase as ISmartTestMethod;
|
ISmartTestMethod smartTestMethod = componentBase as ISmartTestMethod;
|
||||||
|
|
||||||
|
// Resolve the Meters Path before selecting a correction adapter. This is
|
||||||
|
// essential on mixed benches: the ALLY test must not select an iPerl
|
||||||
|
// adapter merely because an iPerl reader exists elsewhere on the bench.
|
||||||
|
if (tests != null && tests.Count > 0)
|
||||||
|
{
|
||||||
|
ProcessData.RegisterReaders = StateMachine.GetMetersPath(tests[0]).RegisterReaders;
|
||||||
|
}
|
||||||
|
|
||||||
//TODO get corrections based on defined meter
|
//TODO get corrections based on defined meter
|
||||||
_corrections = GetNewCorrectionList(smartTestMethod, smartTestMethod.TestMethodCfg, tests, multiTestParams);
|
_corrections = GetNewCorrectionList(smartTestMethod, smartTestMethod.TestMethodCfg, tests, multiTestParams);
|
||||||
InitializeMeterTypeItems();
|
InitializeMeterTypeItems();
|
||||||
UpdateHeads();
|
UpdateHeads();
|
||||||
|
LogFormState("CONSTRUCTOR_HEADS_READY");
|
||||||
|
|
||||||
//_corrections = new PoseidonCorrections(this, log, rfidDataLogger, componentBase as ISmartTestMethod,componentBase.Cfg as TestMethodCfg,tests,multiTestParams);
|
//_corrections = new PoseidonCorrections(this, log, rfidDataLogger, componentBase as ISmartTestMethod,componentBase.Cfg as TestMethodCfg,tests,multiTestParams);
|
||||||
|
|
||||||
|
|
||||||
if (multiTestParams.Count > 0)
|
if (multiTestParams.Count > 0)
|
||||||
{
|
{
|
||||||
ProcessData.RegisterReaders = StateMachine.GetMetersPath(tests[0]).RegisterReaders;
|
activityLabel.Text = multiTestParams[0] == null
|
||||||
activityLabel.Text = multiTestParams[0].Activity;
|
? string.Empty
|
||||||
|
: multiTestParams[0].Activity ?? string.Empty;
|
||||||
foreach (var p in multiTestParams)
|
foreach (var p in multiTestParams)
|
||||||
{
|
{
|
||||||
if (p.Activity.ToLower() == iPerlCommunicationConstants.GetDefaultQ2CorrectionsStr.ToLower())
|
string activity = p == null ? string.Empty : p.Activity ?? string.Empty;
|
||||||
|
if (string.Equals(activity, iPerlCommunicationConstants.GetDefaultQ2CorrectionsStr,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
/// Reset IsQ2PreCorrectionCalculated that is used for synchronization/to prevent double REST call
|
/// Reset IsQ2PreCorrectionCalculated that is used for synchronization/to prevent double REST call
|
||||||
ProcessData.IsQ2PreCorrectionCalculated = false;
|
ProcessData.IsQ2PreCorrectionCalculated = false;
|
||||||
@@ -376,15 +437,25 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
else DoOnAllCompleted(sender, args);
|
else DoOnAllCompleted(sender, args);
|
||||||
};
|
};
|
||||||
formCompleted = false;
|
formCompleted = false;
|
||||||
|
log.Info("SMART_COMM_FORM_CONSTRUCTOR_FORCE_CLOSE_HANDLER_START");
|
||||||
StartForceCloseHandler();
|
StartForceCloseHandler();
|
||||||
|
log.Info("SMART_COMM_FORM_CONSTRUCTOR_FORCE_CLOSE_HANDLER_READY");
|
||||||
|
|
||||||
|
|
||||||
|
log.Info("SMART_COMM_FORM_CONSTRUCTOR_WATER_METER_DATA_START");
|
||||||
InitializeWaterMeterData();
|
InitializeWaterMeterData();
|
||||||
|
LogFormState("CONSTRUCTOR_WATER_METER_DATA_READY");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateHeads()
|
private void UpdateHeads()
|
||||||
{
|
{
|
||||||
|
log.InfoFormat(
|
||||||
|
"SMART_COMM_FORM_UPDATE_HEADS_START: selectedType='{0}', configuredSmartHeads={1}, corrections={2}",
|
||||||
|
SelectedTypeReader ?? "<null>",
|
||||||
|
ProcessData.SmartHeadsUni == null ? 0 : ProcessData.SmartHeadsUni.Count,
|
||||||
|
_corrections == null ? 0 : _corrections.Count);
|
||||||
|
|
||||||
if (!(waterMeterPositions0 == null || waterMeterPositions0.Count <= 0)
|
if (!(waterMeterPositions0 == null || waterMeterPositions0.Count <= 0)
|
||||||
&& labels != null && counters != null && messages != null && checkBoxes != null)
|
&& labels != null && counters != null && messages != null && checkBoxes != null)
|
||||||
{
|
{
|
||||||
@@ -411,33 +482,27 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
waterMeterPositions0?.Clear();
|
waterMeterPositions0?.Clear();
|
||||||
if (waterMeterPositions0 == null) waterMeterPositions0 = new List<int>();
|
if (waterMeterPositions0 == null) waterMeterPositions0 = new List<int>();
|
||||||
//iperlHeads = ProcessData.SmartHeadsUni;
|
//iperlHeads = ProcessData.SmartHeadsUni;
|
||||||
string comparedTypeReader = SelectedTypeReader;
|
ICorrections corre = null;
|
||||||
if (string.IsNullOrEmpty(SelectedTypeReader))
|
if (string.IsNullOrEmpty(SelectedTypeReader))
|
||||||
{
|
{
|
||||||
comparedTypeReader = ProcessData.SmartHeadsUni?.First()?.GetType().Name;
|
ISmartReader firstReader = ProcessData.SmartHeadsUni?.FirstOrDefault();
|
||||||
|
corre = _corrections.FirstOrDefault(correction => correction.IsFamilyOfSmartReader(firstReader));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
ICorrections corre = null;
|
|
||||||
foreach (ICorrections correction in _corrections)
|
|
||||||
{
|
{
|
||||||
if (correction.TypeIdentificatorName() == SelectedTypeReader)
|
corre = _corrections.FirstOrDefault(
|
||||||
{
|
correction => correction.TypeIdentificatorName() == SelectedTypeReader);
|
||||||
corre = correction;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (corre != null)
|
if (corre != null)
|
||||||
{
|
{
|
||||||
int wmPos = 0;
|
for (int wmPos = 0; wmPos < ProcessData.SmartHeadsUni.Count; wmPos++)
|
||||||
foreach (var smartHead in ProcessData.SmartHeadsUni)
|
|
||||||
{
|
{
|
||||||
|
ISmartReader smartHead = ProcessData.SmartHeadsUni[wmPos];
|
||||||
if (corre.IsFamilyOfSmartReader(smartHead))
|
if (corre.IsFamilyOfSmartReader(smartHead))
|
||||||
{
|
{
|
||||||
iperlHeads.Add(smartHead);
|
iperlHeads.Add(smartHead);
|
||||||
waterMeterPositions0.Add(wmPos);
|
waterMeterPositions0.Add(wmPos);
|
||||||
wmPos++;
|
|
||||||
if (wmPos >= ProcessData.WMsCount) break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,45 +513,70 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
WaterMetersCount = iperlHeads.Count;
|
WaterMetersCount = iperlHeads.Count;
|
||||||
ShuffleTextBoxes(WaterMetersCount, ProcessData.LineSize);
|
ShuffleTextBoxes(WaterMetersCount, ProcessData.LineSize);
|
||||||
|
|
||||||
this.ContextMenu = Correction.GetContextMenu();
|
this.ContextMenu = corre.GetContextMenu();
|
||||||
Correction.PrepareForTestsActivities(WaterMetersCount);
|
corre.PrepareForTestsActivities(WaterMetersCount);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.ContextMenu = null;
|
this.ContextMenu = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.InfoFormat(
|
||||||
|
"SMART_COMM_FORM_UPDATE_HEADS_COMPLETED: selectedType='{0}', correction='{1}', displayedHeads={2}, positions=[{3}]",
|
||||||
|
SelectedTypeReader ?? "<null>",
|
||||||
|
corre == null ? "<none>" : corre.TypeIdentificatorName(),
|
||||||
|
iperlHeads.Count,
|
||||||
|
string.Join(",", waterMeterPositions0.Select(position => (position + 1).ToString())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeMeterTypeItems()
|
private void InitializeMeterTypeItems()
|
||||||
{
|
{
|
||||||
if (_corrections.Count >= 0)
|
if (_corrections != null && _corrections.Count > 0)
|
||||||
{
|
{
|
||||||
meterTypeComboBox.Items.Clear();
|
initializingMeterTypeItems = true;
|
||||||
int iItem = 0;
|
try
|
||||||
foreach (ICorrections correction in _corrections)
|
{
|
||||||
{
|
meterTypeComboBox.Items.Clear();
|
||||||
string name = correction?.TypeIdentificatorName();
|
int iItem = 0;
|
||||||
if (string.IsNullOrEmpty(name))
|
foreach (ICorrections correction in _corrections)
|
||||||
{
|
{
|
||||||
name = iItem.ToString();
|
string name = correction?.TypeIdentificatorName();
|
||||||
}
|
if (string.IsNullOrEmpty(name))
|
||||||
meterTypeComboBox.Items.Add(name);
|
{
|
||||||
if (iItem == 0)
|
name = iItem.ToString();
|
||||||
SelectedTypeReader = name;
|
}
|
||||||
iItem++;
|
meterTypeComboBox.Items.Add(name);
|
||||||
}
|
if (iItem == 0)
|
||||||
meterTypeComboBox.Visible = true;
|
SelectedTypeReader = name;
|
||||||
meterTypeComboBox.Enabled = true;
|
iItem++;
|
||||||
meterTypeComboBox.SelectedIndex = 0;
|
}
|
||||||
}
|
|
||||||
}
|
meterTypeComboBox.Visible = true;
|
||||||
|
meterTypeComboBox.Enabled = true;
|
||||||
|
meterTypeComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
initializingMeterTypeItems = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializeWaterMeterData()
|
private void InitializeWaterMeterData()
|
||||||
{
|
{
|
||||||
InitializeSmartReaderLists();
|
if (checkBoxesEditMode)
|
||||||
if (iperlHeads.Count <= 0)
|
{
|
||||||
|
// In manual mode the selected family must determine the displayed heads.
|
||||||
|
// Do not preload all smart readers and overwrite the combobox selection.
|
||||||
UpdateHeads();
|
UpdateHeads();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InitializeSmartReaderLists();
|
||||||
|
if (iperlHeads.Count <= 0)
|
||||||
|
UpdateHeads();
|
||||||
|
}
|
||||||
|
|
||||||
WaterMetersCount = iperlHeads.Count;
|
WaterMetersCount = iperlHeads.Count;
|
||||||
ShuffleTextBoxes(WaterMetersCount, ProcessData.LineSize);
|
ShuffleTextBoxes(WaterMetersCount, ProcessData.LineSize);
|
||||||
@@ -530,7 +620,9 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
|
|
||||||
private void DoOnCommCompleted(object sender, CommCompletedEventArgs data)
|
private void DoOnCommCompleted(object sender, CommCompletedEventArgs data)
|
||||||
{
|
{
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_COMM_COMPLETED: {0}", data == null ? "<null>" : data.ToString());
|
||||||
Correction.DoOnCommCompleted(sender, data, waterMeterPositions0);
|
Correction.DoOnCommCompleted(sender, data, waterMeterPositions0);
|
||||||
|
LogFormState("COMM_TEXT_UPDATED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -662,6 +754,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
private void SmartCommunicationForm_Load(object sender, EventArgs e)
|
private void SmartCommunicationForm_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Localize();
|
Localize();
|
||||||
|
LogFormState("LOAD_START");
|
||||||
|
|
||||||
if (checkBoxesEditMode)
|
if (checkBoxesEditMode)
|
||||||
{
|
{
|
||||||
@@ -674,19 +767,47 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
samplePictureBox3.Visible = false;
|
samplePictureBox3.Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_corrections == null || _corrections.Count == 0 || Correction == null)
|
||||||
|
{
|
||||||
|
ContextMenu = null;
|
||||||
|
log.Warn("SmartCommunicationForm opened without a registered manual communication adapter.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_corrections.Count > 0)//enable combo for choose SmartMeter
|
if (_corrections.Count > 0)//enable combo for choose SmartMeter
|
||||||
{
|
{
|
||||||
meterTypeComboBox.Visible = true;
|
meterTypeComboBox.Visible = true;
|
||||||
}
|
}
|
||||||
Correction.Load(labels,counters,messages,checkBoxes,ckbIndex,ckbState, iperlHeads,textBoxesCount,checkBoxesEditMode );
|
Correction.Load(labels,counters,messages,checkBoxes,ckbIndex,ckbState, iperlHeads,textBoxesCount,checkBoxesEditMode );
|
||||||
|
LogFormState("CONTROLS_LOADED");
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Set location and checkbox states to values stored in local settings
|
/// Set location and checkbox states to values stored in local settings
|
||||||
///
|
///
|
||||||
TBF.LocalSettings ls = Program.LocalSettings;
|
TBF.LocalSettings ls = Program.LocalSettings;
|
||||||
Left = (ls.iPerlCommunicationsFormLeft != 0) ? ls.iPerlCommunicationsFormLeft : 150;
|
if (ls == null)
|
||||||
Top = (ls.iPerlCommunicationsFormTop != 0) ? ls.iPerlCommunicationsFormTop : 150;
|
{
|
||||||
SetCheckBoxStates(ls.OptoHeadsEnabled);
|
log.Warn("SmartCommunicationForm loaded before LocalSettings were initialized; using default window and checkbox state.");
|
||||||
|
Left = 150;
|
||||||
|
Top = 150;
|
||||||
|
SetCurrentFamilySelectionMask(SmartReaderSelection.GetMask(null, iperlHeads));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Left = (ls.iPerlCommunicationsFormLeft != 0) ? ls.iPerlCommunicationsFormLeft : 150;
|
||||||
|
Top = (ls.iPerlCommunicationsFormTop != 0) ? ls.iPerlCommunicationsFormTop : 150;
|
||||||
|
SmartReaderSelection.EnsureConfiguredReaders(ls, ProcessData.SmartHeadsUni);
|
||||||
|
long selectionMask = SmartReaderSelection.GetMask(ls, iperlHeads);
|
||||||
|
SetCurrentFamilySelectionMask(selectionMask);
|
||||||
|
LogFamilySelection("LOADED", selectionMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
int selectedHeads = checkBoxes == null ? 0 : checkBoxes.Take(Math.Min(textBoxesCount, iperlHeads.Count)).Count(checkBox => checkBox.Checked);
|
||||||
|
log.InfoFormat(
|
||||||
|
"SMART_COMM_FORM_SELECTION_APPLIED: selectedHeads={0}/{1}, activity='{2}'",
|
||||||
|
selectedHeads,
|
||||||
|
iperlHeads.Count,
|
||||||
|
activityLabel.Text);
|
||||||
|
|
||||||
if (!checkBoxesEditMode)
|
if (!checkBoxesEditMode)
|
||||||
{
|
{
|
||||||
@@ -725,6 +846,8 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
|
|
||||||
void OnForceClose(object sender, EventArgs args)
|
void OnForceClose(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
|
LogFormState("FORCE_CLOSE_REQUESTED");
|
||||||
|
CancelCloseAfterResult();
|
||||||
CommCompletedHandler = null;
|
CommCompletedHandler = null;
|
||||||
AllCompletedHandler = null;
|
AllCompletedHandler = null;
|
||||||
|
|
||||||
@@ -769,6 +892,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
void DoOnAllCompleted(object sender, AllCompletedEventArgs data)
|
void DoOnAllCompleted(object sender, AllCompletedEventArgs data)
|
||||||
{
|
{
|
||||||
Text = data.CommMessage;
|
Text = data.CommMessage;
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_ALL_COMPLETED: message='{0}'", data == null ? "<null>" : data.CommMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -872,14 +996,78 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a persisted family-local selection mask to the controls that
|
||||||
|
/// currently represent that family. GetMask/SetMask use ordinal reader
|
||||||
|
/// indices; ckbIndex is a layout/physical-position mapping and must not
|
||||||
|
/// be used as the persisted bit position when switching reader families.
|
||||||
|
/// </summary>
|
||||||
|
private void SetCurrentFamilySelectionMask(long state)
|
||||||
|
{
|
||||||
|
int count = Math.Min(textBoxesCount, iperlHeads == null ? 0 : iperlHeads.Count);
|
||||||
|
for (int localIndex = 0; localIndex < count; localIndex++)
|
||||||
|
{
|
||||||
|
bool enabled = localIndex < 63 && (state & (1L << localIndex)) != 0L;
|
||||||
|
checkBoxes[localIndex].Checked = enabled;
|
||||||
|
|
||||||
|
int wmNr0 = ckbIndex[localIndex];
|
||||||
|
if (wmNr0 >= 0 && wmNr0 < ckbState.Length)
|
||||||
|
ckbState[wmNr0] = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long GetCurrentFamilySelectionMask()
|
||||||
|
{
|
||||||
|
int count = Math.Min(textBoxesCount, iperlHeads == null ? 0 : iperlHeads.Count);
|
||||||
|
long result = 0L;
|
||||||
|
for (int localIndex = 0; localIndex < count && localIndex < 63; localIndex++)
|
||||||
|
{
|
||||||
|
if (checkBoxes[localIndex].Checked)
|
||||||
|
result |= 1L << localIndex;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogFamilySelection(string phase, long selectionMask)
|
||||||
|
{
|
||||||
|
if (iperlHeads == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string family = iperlHeads.Count == 0 ? "<none>" : SmartReaderSelection.GetFamilyKey(iperlHeads[0]);
|
||||||
|
string readers = string.Join(", ", iperlHeads.Select((reader, index) =>
|
||||||
|
string.Format("{0}:{1}={2}", index + 1, reader == null ? "<null>" : reader.Name,
|
||||||
|
index < 63 && (selectionMask & (1L << index)) != 0L ? "enabled" : "disabled")));
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_SELECTION_{0}: family='{1}', mask=0x{2:X}, readers=[{3}]",
|
||||||
|
phase, family, selectionMask, readers);
|
||||||
|
}
|
||||||
|
|
||||||
private void saveButton_Click(object sender, EventArgs e)
|
private void saveButton_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Program.LocalSettings.OptoHeadsEnabled = GetCheckBoxStates();
|
SaveCurrentFamilySelection();
|
||||||
Program.LocalSettings.Save();
|
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SaveCurrentFamilySelection()
|
||||||
|
{
|
||||||
|
if (Program.LocalSettings == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
long checkboxStates = GetCurrentFamilySelectionMask();
|
||||||
|
SmartReaderSelection.SetMask(Program.LocalSettings, iperlHeads, checkboxStates);
|
||||||
|
LogFamilySelection("SAVING", checkboxStates);
|
||||||
|
|
||||||
|
// The dedicated legacy iPerl/S640 dialogs still consume this
|
||||||
|
// bitmask. Do not overwrite it while editing another family.
|
||||||
|
if (iperlHeads != null && iperlHeads.Count > 0 &&
|
||||||
|
SmartReaderSelection.GetFamilyKey(iperlHeads[0]) == "iperl")
|
||||||
|
{
|
||||||
|
Program.LocalSettings.OptoHeadsEnabled = checkboxStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.LocalSettings.Save();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void SmartCommunicationForm_FormClosing(object sender, FormClosingEventArgs e)
|
private void SmartCommunicationForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
@@ -887,40 +1075,128 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
if (!forcedClose && !formCompleted && !checkBoxesEditMode)
|
if (!forcedClose && !formCompleted && !checkBoxesEditMode)
|
||||||
{
|
{
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
|
log.Info("SMART_COMM_FORM_CLOSE_BLOCKED: communication is still in progress.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_CLOSE_ALLOWED: forced={0}, completed={1}, editMode={2}",
|
||||||
|
forcedClose, formCompleted, checkBoxesEditMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void NormalClose()
|
public void NormalClose()
|
||||||
{
|
{
|
||||||
|
LogFormState("NORMAL_CLOSE_REQUESTED");
|
||||||
|
CancelCloseAfterResult();
|
||||||
CommCompletedHandler = null;
|
CommCompletedHandler = null;
|
||||||
AllCompletedHandler = null;
|
AllCompletedHandler = null;
|
||||||
|
|
||||||
Correction?.NormalClose(waterMeterPositions0);
|
Correction?.NormalClose(waterMeterPositions0);
|
||||||
|
|
||||||
long checkboxStates = GetCheckBoxStates();
|
long checkboxStates = GetCurrentFamilySelectionMask();
|
||||||
if (Program.LocalSettings.iPerlCommunicationsFormLeft != Location.X ||
|
if (Program.LocalSettings.iPerlCommunicationsFormLeft != Location.X ||
|
||||||
Program.LocalSettings.iPerlCommunicationsFormTop != Location.Y ||
|
Program.LocalSettings.iPerlCommunicationsFormTop != Location.Y)
|
||||||
Program.LocalSettings.OptoHeadsEnabled != checkboxStates)
|
|
||||||
{
|
{
|
||||||
/// Update local settings
|
/// Update local settings
|
||||||
Program.LocalSettings.iPerlCommunicationsFormLeft = Location.X;
|
Program.LocalSettings.iPerlCommunicationsFormLeft = Location.X;
|
||||||
Program.LocalSettings.iPerlCommunicationsFormTop = Location.Y;
|
Program.LocalSettings.iPerlCommunicationsFormTop = Location.Y;
|
||||||
Program.LocalSettings.OptoHeadsEnabled = checkboxStates;
|
|
||||||
Program.LocalSettings.Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SmartReaderSelection.SetMask(Program.LocalSettings, iperlHeads, checkboxStates);
|
||||||
|
LogFamilySelection("CLOSING", checkboxStates);
|
||||||
|
if (iperlHeads != null && iperlHeads.Count > 0 &&
|
||||||
|
SmartReaderSelection.GetFamilyKey(iperlHeads[0]) == "iperl")
|
||||||
|
{
|
||||||
|
Program.LocalSettings.OptoHeadsEnabled = checkboxStates;
|
||||||
|
}
|
||||||
|
Program.LocalSettings.Save();
|
||||||
|
|
||||||
formCompleted = true;
|
formCompleted = true;
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the modeless communication form after the result has been
|
||||||
|
/// visible for <paramref name="delayMs"/>. If a worker completes while
|
||||||
|
/// the form is still being constructed, the delay begins after Shown.
|
||||||
|
/// </summary>
|
||||||
|
public void CloseAfterResult(int delayMs)
|
||||||
|
{
|
||||||
|
if (formCompleted || IsDisposed || closeAfterResultRequested)
|
||||||
|
{
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_CLOSE_DELAY_IGNORED: completed={0}, disposed={1}, alreadyRequested={2}",
|
||||||
|
formCompleted, IsDisposed, closeAfterResultRequested);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAfterResultRequested = true;
|
||||||
|
delayMs = Math.Max(1, delayMs);
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_CLOSE_DELAY_REQUESTED: delayMs={0}, visible={1}", delayMs, Visible);
|
||||||
|
|
||||||
|
if (!Visible)
|
||||||
|
{
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_CLOSE_DELAY_WAITING_FOR_SHOW: delayMs={0}", delayMs);
|
||||||
|
closeAfterResultShownHandler = null;
|
||||||
|
closeAfterResultShownHandler = delegate
|
||||||
|
{
|
||||||
|
if (closeAfterResultShownHandler != null)
|
||||||
|
Shown -= closeAfterResultShownHandler;
|
||||||
|
closeAfterResultShownHandler = null;
|
||||||
|
StartCloseAfterResultTimer(delayMs);
|
||||||
|
};
|
||||||
|
Shown += closeAfterResultShownHandler;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartCloseAfterResultTimer(delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartCloseAfterResultTimer(int delayMs)
|
||||||
|
{
|
||||||
|
if (formCompleted || IsDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
closeAfterResultTimer = new System.Windows.Forms.Timer { Interval = Math.Max(1, delayMs) };
|
||||||
|
closeAfterResultTimer.Tick += delegate
|
||||||
|
{
|
||||||
|
CancelCloseAfterResultTimer();
|
||||||
|
log.Info("SMART_COMM_FORM_CLOSE_DELAY_ELAPSED");
|
||||||
|
NormalClose();
|
||||||
|
};
|
||||||
|
log.InfoFormat("SMART_COMM_FORM_CLOSE_DELAY_SCHEDULED: delayMs={0}, visible={1}", delayMs, Visible);
|
||||||
|
closeAfterResultTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelCloseAfterResult()
|
||||||
|
{
|
||||||
|
if (closeAfterResultShownHandler != null)
|
||||||
|
{
|
||||||
|
Shown -= closeAfterResultShownHandler;
|
||||||
|
closeAfterResultShownHandler = null;
|
||||||
|
}
|
||||||
|
CancelCloseAfterResultTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelCloseAfterResultTimer()
|
||||||
|
{
|
||||||
|
if (closeAfterResultTimer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
log.Info("SMART_COMM_FORM_CLOSE_DELAY_CANCELLED");
|
||||||
|
closeAfterResultTimer.Stop();
|
||||||
|
closeAfterResultTimer.Dispose();
|
||||||
|
closeAfterResultTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void meterTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
|
private void meterTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
ComboBox senderCombo = sender as ComboBox;
|
if (initializingMeterTypeItems) return;
|
||||||
|
ComboBox senderCombo = sender as ComboBox;
|
||||||
if (senderCombo == null) return;
|
if (senderCombo == null) return;
|
||||||
|
SaveCurrentFamilySelection();
|
||||||
SelectedTypeReader = senderCombo.SelectedItem?.ToString();
|
SelectedTypeReader = senderCombo.SelectedItem?.ToString();
|
||||||
UpdateHeads();
|
UpdateHeads();
|
||||||
SmartCommunicationForm_Load(this, EventArgs.Empty);
|
SmartCommunicationForm_Load(this, EventArgs.Empty);
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using TBF.Rig.GenericDevices;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using AsicReader = TBF.Rig.RegisterReaders.AsicReader.AsicReader;
|
||||||
|
|
||||||
|
namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves persisted smart-reader selections. The visual dialog may have a
|
||||||
|
/// fixed number of controls, but the persisted model contains only configured
|
||||||
|
/// readers in each family.
|
||||||
|
/// </summary>
|
||||||
|
public static class SmartReaderSelection
|
||||||
|
{
|
||||||
|
private const string LegacyIPerlFamilyKey = "iperl";
|
||||||
|
|
||||||
|
public static void EnsureConfiguredReaders(LocalSettings settings, IEnumerable<ISmartReader> readers)
|
||||||
|
{
|
||||||
|
if (settings == null || readers == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<ISmartReader> configuredReaders = readers.Where(reader => reader != null).ToList();
|
||||||
|
if (configuredReaders.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SmartReaderSelectionSettings selections = GetOrCreateSettings(settings);
|
||||||
|
bool migrateLegacyMask = !selections.LegacyOptoHeadsEnabledMigrated;
|
||||||
|
string legacyFamily = configuredReaders.Any(reader => GetFamilyKey(reader) == LegacyIPerlFamilyKey)
|
||||||
|
? LegacyIPerlFamilyKey
|
||||||
|
: GetFamilyKey(configuredReaders[0]);
|
||||||
|
|
||||||
|
foreach (IGrouping<string, ISmartReader> family in configuredReaders.GroupBy(GetFamilyKey))
|
||||||
|
{
|
||||||
|
SmartReaderFamilySelection storedFamily = GetOrCreateFamily(selections, family.Key);
|
||||||
|
int ordinal = 0;
|
||||||
|
foreach (ISmartReader reader in family)
|
||||||
|
{
|
||||||
|
SmartReaderSelectionItem item = storedFamily.Readers
|
||||||
|
.FirstOrDefault(candidate => candidate.ReaderKey == GetReaderKey(reader));
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
item = new SmartReaderSelectionItem
|
||||||
|
{
|
||||||
|
ReaderKey = GetReaderKey(reader),
|
||||||
|
Enabled = !migrateLegacyMask || family.Key != legacyFamily ||
|
||||||
|
IsLegacyMaskEnabled(settings.OptoHeadsEnabled, reader, ordinal)
|
||||||
|
};
|
||||||
|
storedFamily.Readers.Add(item);
|
||||||
|
}
|
||||||
|
ordinal++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (migrateLegacyMask)
|
||||||
|
selections.LegacyOptoHeadsEnabledMigrated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long GetMask(LocalSettings settings, IEnumerable<ISmartReader> readers)
|
||||||
|
{
|
||||||
|
List<ISmartReader> familyReaders = ToReaderList(readers);
|
||||||
|
if (familyReaders.Count == 0)
|
||||||
|
return 0L;
|
||||||
|
|
||||||
|
EnsureConfiguredReaders(settings, familyReaders);
|
||||||
|
long result = 0L;
|
||||||
|
for (int index = 0; index < familyReaders.Count && index < 63; index++)
|
||||||
|
{
|
||||||
|
if (IsEnabled(settings, familyReaders[index]))
|
||||||
|
result |= 1L << index;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetMask(LocalSettings settings, IEnumerable<ISmartReader> readers, long state)
|
||||||
|
{
|
||||||
|
List<ISmartReader> familyReaders = ToReaderList(readers);
|
||||||
|
if (settings == null || familyReaders.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EnsureConfiguredReaders(settings, familyReaders);
|
||||||
|
for (int index = 0; index < familyReaders.Count; index++)
|
||||||
|
SetEnabled(settings, familyReaders[index], index < 63 && (state & (1L << index)) != 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsEnabled(LocalSettings settings, IRegReader reader)
|
||||||
|
{
|
||||||
|
if (settings == null || reader == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
SmartReaderSelectionSettings selections = settings.SmartReaderSelections;
|
||||||
|
if (selections == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
SmartReaderFamilySelection family = selections.Families == null
|
||||||
|
? null
|
||||||
|
: selections.Families.FirstOrDefault(item => item.FamilyKey == GetFamilyKey(reader));
|
||||||
|
SmartReaderSelectionItem item = family == null || family.Readers == null
|
||||||
|
? null
|
||||||
|
: family.Readers.FirstOrDefault(candidate => candidate.ReaderKey == GetReaderKey(reader));
|
||||||
|
|
||||||
|
// A newly configured reader is enabled until the user explicitly
|
||||||
|
// changes it in SmartCommunicationForm.
|
||||||
|
return item == null || item.Enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetFamilyKey(IRegReader reader)
|
||||||
|
{
|
||||||
|
// Prefer the concrete extracted reader type. The string fallback
|
||||||
|
// below is retained for persisted/proxy-backed legacy readers.
|
||||||
|
if (reader is AsicReader)
|
||||||
|
return "asic";
|
||||||
|
|
||||||
|
string typeName = reader == null ? string.Empty : reader.GetType().FullName ?? string.Empty;
|
||||||
|
if (typeName.IndexOf(".AllyReader.", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
return "ally";
|
||||||
|
if (typeName.IndexOf(".GenesisRegReader.", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
return "genesis";
|
||||||
|
if (typeName.IndexOf(".AsicReader.", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
return "asic";
|
||||||
|
if (typeName.IndexOf(".iPerlASICReader.", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
return "iperl-asic";
|
||||||
|
if (typeName.IndexOf(".Poseidon", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
return "poseidon";
|
||||||
|
return LegacyIPerlFamilyKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetReaderKey(IRegReader reader)
|
||||||
|
{
|
||||||
|
if (reader == null)
|
||||||
|
return string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(reader.Name))
|
||||||
|
return reader.Name;
|
||||||
|
return string.Format("{0}#{1}", reader.GetType().FullName, reader.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetEnabled(LocalSettings settings, IRegReader reader, bool enabled)
|
||||||
|
{
|
||||||
|
SmartReaderFamilySelection family = GetOrCreateFamily(GetOrCreateSettings(settings), GetFamilyKey(reader));
|
||||||
|
SmartReaderSelectionItem item = family.Readers.FirstOrDefault(candidate => candidate.ReaderKey == GetReaderKey(reader));
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
item = new SmartReaderSelectionItem { ReaderKey = GetReaderKey(reader) };
|
||||||
|
family.Readers.Add(item);
|
||||||
|
}
|
||||||
|
item.Enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SmartReaderSelectionSettings GetOrCreateSettings(LocalSettings settings)
|
||||||
|
{
|
||||||
|
if (settings.SmartReaderSelections == null)
|
||||||
|
settings.SmartReaderSelections = new SmartReaderSelectionSettings();
|
||||||
|
if (settings.SmartReaderSelections.Families == null)
|
||||||
|
settings.SmartReaderSelections.Families = new List<SmartReaderFamilySelection>();
|
||||||
|
return settings.SmartReaderSelections;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SmartReaderFamilySelection GetOrCreateFamily(SmartReaderSelectionSettings settings, string familyKey)
|
||||||
|
{
|
||||||
|
SmartReaderFamilySelection family = settings.Families.FirstOrDefault(item => item.FamilyKey == familyKey);
|
||||||
|
if (family == null)
|
||||||
|
{
|
||||||
|
family = new SmartReaderFamilySelection { FamilyKey = familyKey };
|
||||||
|
settings.Families.Add(family);
|
||||||
|
}
|
||||||
|
if (family.Readers == null)
|
||||||
|
family.Readers = new List<SmartReaderSelectionItem>();
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsLegacyMaskEnabled(long legacyMask, IRegReader reader, int ordinal)
|
||||||
|
{
|
||||||
|
int index = reader.Position > 0 && reader.Position <= 63 ? reader.Position - 1 : ordinal;
|
||||||
|
return index >= 0 && index < 63 && (legacyMask & (1L << index)) != 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ISmartReader> ToReaderList(IEnumerable<ISmartReader> readers)
|
||||||
|
{
|
||||||
|
return readers == null ? new List<ISmartReader>() : readers.Where(reader => reader != null).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,17 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
|||||||
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(iPerlCommunicationParams) })[0];
|
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(iPerlCommunicationParams) })[0];
|
||||||
public override XmlSerializer GetSerializer() { return Serializer; }
|
public override XmlSerializer GetSerializer() { return Serializer; }
|
||||||
|
|
||||||
public string Activity; /// Communication activity
|
/// <summary>
|
||||||
|
/// Communication activity. Do not use a field here: callers commonly
|
||||||
|
/// receive this object through <see cref="ITestParams"/>, which reads the
|
||||||
|
/// inherited TestParamsBase.Activity property. A field would hide that
|
||||||
|
/// property and make the activity appear empty in SmartCommunicationForm.
|
||||||
|
/// </summary>
|
||||||
|
public string Activity
|
||||||
|
{
|
||||||
|
get { return base.Activity; }
|
||||||
|
set { base.Activity = value; }
|
||||||
|
}
|
||||||
public bool SimultWithPrevious;
|
public bool SimultWithPrevious;
|
||||||
public bool SimultWithNext;
|
public bool SimultWithNext;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
|
||||||
|
namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
||||||
|
{
|
||||||
|
internal sealed class AllyCorrections : ManualSmartCorrectionsBase<AllyMeterReader>
|
||||||
|
{
|
||||||
|
private const int CommandTimeoutMs = 5000;
|
||||||
|
|
||||||
|
public AllyCorrections(SmartCommunicationForm parent) : base(parent) { }
|
||||||
|
|
||||||
|
public override string TypeIdentificatorName() => "ALLY";
|
||||||
|
public override bool IsFamilyOfSmartReader(ISmartReader smartHead) => smartHead is AllyMeterReader;
|
||||||
|
|
||||||
|
protected override IEnumerable<MenuItem> CreateMenuItems()
|
||||||
|
{
|
||||||
|
yield return new MenuItem("Read Serial Number") { Tag = "ReadSerialNumber" };
|
||||||
|
yield return new MenuItem("Read Version and Type") { Tag = "ReadVersion" };
|
||||||
|
yield return new MenuItem("Set RFID mode") { Tag = "SetRfid" };
|
||||||
|
yield return new MenuItem("Set NFC mode") { Tag = "SetNfc" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string ExecuteManualCommand(AllyMeterReader reader, string command)
|
||||||
|
{
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case "ReadSerialNumber": return reader.ReadSerialNumber(CommandTimeoutMs);
|
||||||
|
case "ReadVersion": return reader.ReadVersionAndType(CommandTimeoutMs).ToString();
|
||||||
|
case "SetRfid": reader.SetRfidInterface(); return "OK";
|
||||||
|
case "SetNfc": reader.SetNfcInterface(); return "OK";
|
||||||
|
default: return "Unsupported ALLY operation";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+339
@@ -0,0 +1,339 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Common;
|
||||||
|
using Config.Entities;
|
||||||
|
using log4net;
|
||||||
|
using Results.Entities;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
|
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||||
|
using TBF.Rig.Sequences;
|
||||||
|
using TBF.Rig.TestMethods.AllyCalibration;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using CheckBoxImage = TBF.Boxes.CheckBoxImage;
|
||||||
|
|
||||||
|
namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test-time ALLY adapter for SmartCommunicationForm.
|
||||||
|
///
|
||||||
|
/// AllyCorrections intentionally remains the manual-command adapter. This class
|
||||||
|
/// runs configured ALLY test activities and reports every completed reader back
|
||||||
|
/// to the shared dialog, just like the iPerl ASIC correction adapter does.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class AllyTestCorrections : ICorrections
|
||||||
|
{
|
||||||
|
private ILog log { get { return ParentFrom == null ? null : ParentFrom.Log; } }
|
||||||
|
// Keep the result visible long enough to be verified by the operator.
|
||||||
|
// This is deliberately identical to the ASIC correction behaviour.
|
||||||
|
private const int ResultVisibilityDelayMs = 1500;
|
||||||
|
private readonly ISmartTestMethod testMethod;
|
||||||
|
private readonly ITestMethodCfg cfg;
|
||||||
|
private readonly IList<Test> tests;
|
||||||
|
private readonly IList<ITestParams> multiTestParams;
|
||||||
|
private readonly IList<Thread> workerThreads = new List<Thread>();
|
||||||
|
private volatile bool stopWorkerThreads;
|
||||||
|
private int completedActivities;
|
||||||
|
private int expectedActivities;
|
||||||
|
|
||||||
|
public AllyTestCorrections(SmartCommunicationForm parent, ISmartTestMethod testMethod,
|
||||||
|
ITestMethodCfg cfg, IList<Test> tests, IList<ITestParams> multiTestParams)
|
||||||
|
{
|
||||||
|
ParentFrom = parent;
|
||||||
|
this.testMethod = testMethod;
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.tests = tests ?? new List<Test>();
|
||||||
|
this.multiTestParams = multiTestParams ?? new List<ITestParams>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TypeIdentificatorName() { return "ALLY"; }
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
public int StartTimeSec { get; set; }
|
||||||
|
public SmartCommunicationForm ParentFrom { get; set; }
|
||||||
|
public ITestMethodCfg Cfg { get { return cfg; } set { } }
|
||||||
|
public IList<ITestParams> MultiTestParams { get { return multiTestParams; } }
|
||||||
|
public IList<Test> Tests { get { return tests; } set { } }
|
||||||
|
public IList<ISmartReader> iperlHeads { get { return ParentFrom == null ? null : ParentFrom.Heads; } }
|
||||||
|
|
||||||
|
public void PrepareForTestsActivities(int waterMetersCount)
|
||||||
|
{
|
||||||
|
StartTime = DateTime.Now;
|
||||||
|
StartTimeSec = StateMachine.Time;
|
||||||
|
stopWorkerThreads = false;
|
||||||
|
completedActivities = 0;
|
||||||
|
expectedActivities = Math.Max(0, waterMetersCount) * Math.Max(1, multiTestParams.Count);
|
||||||
|
workerThreads.Clear();
|
||||||
|
|
||||||
|
// ALLY command communication is serialized. A single command port must
|
||||||
|
// not be accessed concurrently even when more than one meter is shown.
|
||||||
|
workerThreads.Add(new Thread(Worker) { IsBackground = true, Name = "ALLY SmartCommunication worker" });
|
||||||
|
ParentFrom.Log.InfoFormat(
|
||||||
|
"ALLY_TEST_CORRECTIONS_PREPARE: readers={0}, activities={1}, expectedCallbacks={2}, workers=1",
|
||||||
|
waterMetersCount, multiTestParams.Count, expectedActivities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Worker(object threadData)
|
||||||
|
{
|
||||||
|
int threadId = (threadData as TBF.Boxes.IntBox) == null ? 0 : (threadData as TBF.Boxes.IntBox).Val;
|
||||||
|
ParentFrom.Log.InfoFormat("ALLY_TEST_CORRECTIONS_WORKER_START: thread={0}, readers={1}, activities={2}",
|
||||||
|
threadId, iperlHeads == null ? 0 : iperlHeads.Count, multiTestParams.Count);
|
||||||
|
|
||||||
|
for (int activityIndex = 0; activityIndex < multiTestParams.Count && !stopWorkerThreads; activityIndex++)
|
||||||
|
{
|
||||||
|
Test currentTest = activityIndex < tests.Count ? tests[activityIndex] : null;
|
||||||
|
TestMethodParams parameters = multiTestParams[activityIndex] as TestMethodParams;
|
||||||
|
bool allPassed = true;
|
||||||
|
int processedReaders = 0;
|
||||||
|
if (parameters == null)
|
||||||
|
{
|
||||||
|
ParentFrom.Log.ErrorFormat("ALLY_TEST_CORRECTIONS_INVALID_PARAMS: activityIndex={0}", activityIndex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TBF.UiBridge.Bridge.OnTestProgress(this,
|
||||||
|
new TBF.UiBridge.TestProgressEventArgs(currentTest, Progress.JustStarted));
|
||||||
|
|
||||||
|
for (int localIndex = 0; localIndex < iperlHeads.Count && !stopWorkerThreads; localIndex++)
|
||||||
|
{
|
||||||
|
AllyMeterReader reader = iperlHeads[localIndex] as AllyMeterReader;
|
||||||
|
int originalIndex = GetOriginalIndex(localIndex);
|
||||||
|
WaterMeter waterMeter = GetWaterMeter(originalIndex);
|
||||||
|
CommErr error = CommErr.None;
|
||||||
|
string message;
|
||||||
|
bool passed = false;
|
||||||
|
|
||||||
|
if (reader == null)
|
||||||
|
{
|
||||||
|
error = CommErr.WrongIPerlType;
|
||||||
|
message = "ALLY reader is not configured.";
|
||||||
|
}
|
||||||
|
else if (IsDisabled(localIndex, reader, waterMeter))
|
||||||
|
{
|
||||||
|
error = CommErr.HeadDisabledByUser;
|
||||||
|
message = "Disabled by user";
|
||||||
|
ParentFrom.Log.InfoFormat(
|
||||||
|
"ALLY_TEST_CORRECTIONS_SKIPPED_DISABLED: position={0}, selected={1}, readerDisabled={2}, waterMeterDisabled={3}",
|
||||||
|
originalIndex + 1,
|
||||||
|
IsSelected(localIndex),
|
||||||
|
reader.Disabled,
|
||||||
|
waterMeter != null && waterMeter.Disabled);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ParentFrom.Log.InfoFormat(
|
||||||
|
"ALLY_TEST_CORRECTIONS_COMMAND_START: test='{0}', position={1}, reader='{2}', activity='{3}'",
|
||||||
|
currentTest == null ? "<null>" : currentTest.Name,
|
||||||
|
originalIndex + 1,
|
||||||
|
reader.Name,
|
||||||
|
parameters.Activity);
|
||||||
|
passed = AllyCalibrationSeq.ExecuteWithRetries(reader, cfg as TestMethodCfg, parameters, out message);
|
||||||
|
message = reader.Name + ": " + message;
|
||||||
|
if (!passed)
|
||||||
|
error = CommErr.CommFailed;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
error = CommErr.CommFailed;
|
||||||
|
message = reader.Name + ": " + exception.Message;
|
||||||
|
ParentFrom.Log.Error("ALLY_TEST_CORRECTIONS_COMMAND_FAILED", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateResult(currentTest, originalIndex, waterMeter, reader, parameters.Activity, passed);
|
||||||
|
processedReaders++;
|
||||||
|
allPassed &= passed;
|
||||||
|
ParentFrom.Log.InfoFormat(
|
||||||
|
"ALLY_TEST_CORRECTIONS_COMMAND_COMPLETED: test='{0}', position={1}, passed={2}, error={3}, result='{4}'",
|
||||||
|
currentTest == null ? "<null>" : currentTest.Name,
|
||||||
|
originalIndex + 1,
|
||||||
|
passed,
|
||||||
|
error,
|
||||||
|
message);
|
||||||
|
SmartCommunicationForm.OnCommCompleted(this,
|
||||||
|
new CommCompletedEventArgs(threadId, localIndex, reader, waterMeter, message, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
CompleteTest(currentTest, processedReaders, allPassed);
|
||||||
|
}
|
||||||
|
|
||||||
|
ParentFrom.Log.InfoFormat("ALLY_TEST_CORRECTIONS_WORKER_COMPLETED: thread={0}", threadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WorkerActivity(string currentActivity, ISmartReader iHead, WaterMeter wm, Test currentTest,
|
||||||
|
int wmNr0, ref CommErr error, ref string resultStr, bool[] ckbState, int threadID, int currentActivityStep)
|
||||||
|
{
|
||||||
|
AllyMeterReader reader = iHead as AllyMeterReader;
|
||||||
|
TestMethodParams parameters = currentActivityStep < multiTestParams.Count
|
||||||
|
? multiTestParams[currentActivityStep] as TestMethodParams
|
||||||
|
: null;
|
||||||
|
if (reader == null || parameters == null)
|
||||||
|
{
|
||||||
|
error = CommErr.WrongArguments;
|
||||||
|
resultStr = "ALLY reader or activity is missing.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool passed = AllyCalibrationSeq.ExecuteWithRetries(reader, cfg as TestMethodCfg, parameters, out resultStr);
|
||||||
|
error = passed ? CommErr.None : CommErr.CommFailed;
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProcessResultOfWorkerActivity(int iMultiTestParamsItem, string currentActivity, int currentGroup,
|
||||||
|
ISmartReader iHead, WaterMeter wm, int wmNr0, CommErr error, string resultStr, bool[] ckbState, int threadID)
|
||||||
|
{
|
||||||
|
SmartCommunicationForm.OnCommCompleted(this,
|
||||||
|
new CommCompletedEventArgs(threadID, wmNr0, iHead, wm, resultStr, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopWorkerThreads(bool stopAllThreads) { stopWorkerThreads = stopAllThreads; }
|
||||||
|
public bool GetStopWorkerThreads() { return stopWorkerThreads; }
|
||||||
|
public IList<Thread> GetAllThreads() { return workerThreads; }
|
||||||
|
public ICorrections GetNewCorrection() { return this; }
|
||||||
|
public void GetGroup() { }
|
||||||
|
public ContextMenu GetContextMenu() { return new ContextMenu(); }
|
||||||
|
|
||||||
|
public void Load(Label[] labels, PictureBox[] counters, TextBox[] messages, CheckBoxImage[] checkBoxes, int[] ckbIndex,
|
||||||
|
bool[] ckbState, IList<ISmartReader> heads, int textBoxesCount, bool checkBoxesEditMode)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < textBoxesCount; index++)
|
||||||
|
{
|
||||||
|
bool hasHead = heads != null && index < heads.Count && heads[index] is AllyMeterReader;
|
||||||
|
labels[index].Visible = counters[index].Visible = messages[index].Visible = checkBoxes[index].Visible = hasHead;
|
||||||
|
if (!hasHead)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool disabled = heads[index].Disabled;
|
||||||
|
checkBoxes[index].Enabled = checkBoxes[index].Checked = ckbState[index] = !disabled;
|
||||||
|
messages[index].Text = disabled ? "Disabled by user" : "---";
|
||||||
|
counters[index].BackColor = disabled
|
||||||
|
? iPerlCommunicationConstants.DisabledColor
|
||||||
|
: iPerlCommunicationConstants.OptoNokColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHeadsCount() { return iperlHeads == null ? 0 : iperlHeads.Count; }
|
||||||
|
public void StartDataStreamProcessingForActiveMeters(int iMultiTestParamsItem) { }
|
||||||
|
|
||||||
|
public void DoOnCommCompleted(object sender, CommCompletedEventArgs data, IList<int> waterMeterPositions0)
|
||||||
|
{
|
||||||
|
if (data == null || data.WMNr0 < 0 || data.WMNr0 >= ParentFrom.Messages.Length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ParentFrom.Messages[data.WMNr0].Text = data.CommMessage;
|
||||||
|
ParentFrom.Counters[data.WMNr0].BackColor = data.CommErr == CommErr.None
|
||||||
|
? iPerlCommunicationConstants.OptoAndDirOKColor
|
||||||
|
: iPerlCommunicationConstants.OptoNokColor;
|
||||||
|
|
||||||
|
if (++completedActivities < expectedActivities || ParentFrom.IsDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ParentFrom.Log.InfoFormat("ALLY_TEST_CORRECTIONS_ALL_COMPLETED: callbacks={0}", completedActivities);
|
||||||
|
ParentFrom.CloseAfterResult(ResultVisibilityDelayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NormalClose(IList<int> waterMeterPositions0) { StopWorkerThreads(true); }
|
||||||
|
public bool IsFamilyOfSmartReader(ISmartReader smartHead) { return smartHead is AllyMeterReader; }
|
||||||
|
|
||||||
|
private bool IsDisabled(int localIndex, AllyMeterReader reader, WaterMeter waterMeter)
|
||||||
|
{
|
||||||
|
return !IsSelected(localIndex) ||
|
||||||
|
reader.Disabled ||
|
||||||
|
(waterMeter != null && waterMeter.Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSelected(int localIndex)
|
||||||
|
{
|
||||||
|
if (ParentFrom.CheckBoxes != null && localIndex >= 0 && localIndex < ParentFrom.CheckBoxes.Length)
|
||||||
|
return ParentFrom.CheckBoxes[localIndex].Checked;
|
||||||
|
|
||||||
|
return ParentFrom.CkbState == null || localIndex < 0 || localIndex >= ParentFrom.CkbState.Length ||
|
||||||
|
ParentFrom.CkbState[localIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetOriginalIndex(int localIndex)
|
||||||
|
{
|
||||||
|
IList<int> positions = ParentFrom.WaterMeterPositions0;
|
||||||
|
return positions != null && localIndex < positions.Count ? positions[localIndex] : localIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WaterMeter GetWaterMeter(int originalIndex)
|
||||||
|
{
|
||||||
|
return ProcessData.BatchRslts != null && ProcessData.BatchRslts.Batch != null &&
|
||||||
|
ProcessData.BatchRslts.Batch.WaterMeters != null &&
|
||||||
|
originalIndex >= 0 && originalIndex < ProcessData.BatchRslts.Batch.WaterMeters.Count
|
||||||
|
? ProcessData.BatchRslts.Batch.WaterMeters[originalIndex]
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateResult(Test test, int originalIndex, WaterMeter waterMeter, AllyMeterReader reader,
|
||||||
|
string activity, bool passed)
|
||||||
|
{
|
||||||
|
if (test == null || ProcessData.BatchRslts == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MeterTestRslt result = ProcessData.BatchRslts.GetMeterTestRslt(test.Name, originalIndex, CompoundMeterId.Single);
|
||||||
|
if (result == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
result.RegReaderType = (int)RegisterReaderType.DataStream;
|
||||||
|
result.TestDone = true;
|
||||||
|
result.Passed = passed;
|
||||||
|
|
||||||
|
// Follow the established iPerl/ASIC behavior: the physical reader owns
|
||||||
|
// the received value, while the displayed and persisted s/n belongs to
|
||||||
|
// the WaterMeter associated with this MeterTestRslt.
|
||||||
|
AllyCalibrationActivity parsedActivity;
|
||||||
|
if (passed && reader != null && waterMeter != null &&
|
||||||
|
AllyCalibrationActivityNames.TryParse(activity, out parsedActivity) &&
|
||||||
|
parsedActivity == AllyCalibrationActivity.ReadSerialNumber &&
|
||||||
|
!string.IsNullOrWhiteSpace(reader.SerialNr))
|
||||||
|
{
|
||||||
|
// The MeterTestRslt inverse reference can be null until NHibernate
|
||||||
|
// flushes it. The batch WaterMeter is the authoritative object used
|
||||||
|
// by the Results grid, therefore write to it directly.
|
||||||
|
waterMeter.SerialNr = reader.SerialNr;
|
||||||
|
if (result.WaterMeter != null)
|
||||||
|
result.WaterMeter.SerialNr = reader.SerialNr;
|
||||||
|
log.InfoFormat(
|
||||||
|
"ALLY_TEST_SERIAL_NUMBER_MAPPED: test='{0}', position={1}, serialNumber='{2}'",
|
||||||
|
test.Name,
|
||||||
|
originalIndex + 1,
|
||||||
|
reader.SerialNr);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfoFormat("ALLY_TEST_RESULT_UPDATED: test='{0}', position={1}, passed={2}, testDone={3}",
|
||||||
|
test.Name, originalIndex + 1, result.Passed, result.TestDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompleteTest(Test test, int processedReaders, bool allPassed)
|
||||||
|
{
|
||||||
|
if (test == null || ProcessData.BatchRslts == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TestRslt testResult = ProcessData.BatchRslts.GetTestRslt(test.Name, 0);
|
||||||
|
if (testResult == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (testResult.StartTime == DateTime.MinValue)
|
||||||
|
testResult.StartTime = DateTime.Now;
|
||||||
|
testResult.EndTime = DateTime.Now;
|
||||||
|
testResult.TestDone = true;
|
||||||
|
testResult.Remark = processedReaders == 0
|
||||||
|
? "No ALLY reader is configured for this Meters Path."
|
||||||
|
: allPassed ? "ALLY communication completed." : "ALLY communication failed.";
|
||||||
|
|
||||||
|
log.InfoFormat("ALLY_TEST_RESULT_COMPLETED: test='{0}', readers={1}, resultDone={2}, remark='{3}'",
|
||||||
|
test.Name, processedReaders, testResult.TestDone, testResult.Remark);
|
||||||
|
|
||||||
|
TBF.UiBridge.Bridge.OnTestProgress(this,
|
||||||
|
new TBF.UiBridge.TestProgressEventArgs(test, Progress.Completed));
|
||||||
|
TBF.UiBridge.Bridge.OnTestCompleted(this,
|
||||||
|
new TBF.UiBridge.TestCompletedEventArgs(test.Name, testResult));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+480
@@ -0,0 +1,480 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Common;
|
||||||
|
using Config.Entities;
|
||||||
|
using Results.Entities;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.RegisterReaders.AsicReader;
|
||||||
|
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||||
|
using TBF.Rig.Sequences;
|
||||||
|
using TBF.Rig.TestMethods.iPerlCommunication;
|
||||||
|
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
|
||||||
|
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using CheckBoxImage = TBF.Boxes.CheckBoxImage;
|
||||||
|
|
||||||
|
namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SmartCommunicationForm adapter for the extracted ASIC reader family.
|
||||||
|
/// It calls the proven IperlHead OptoHeadTest implementation; it does not
|
||||||
|
/// route ASIC readers through the unrelated legacy IPerlCorrections class.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AsicCorrections : ICorrections
|
||||||
|
{
|
||||||
|
// Keep the final state/result readable for a single-meter operation.
|
||||||
|
// The timer itself belongs to the form and starts only after it is shown.
|
||||||
|
private const int ResultVisibilityDelayMs = 1500;
|
||||||
|
private readonly ISmartTestMethod testMethod;
|
||||||
|
private readonly ITestMethodCfg cfg;
|
||||||
|
private readonly IList<Test> tests;
|
||||||
|
private readonly IList<ITestParams> multiTestParams;
|
||||||
|
private readonly IList<Thread> workerThreads = new List<Thread>();
|
||||||
|
private volatile bool stopWorkerThreads;
|
||||||
|
private int completedCallbacks;
|
||||||
|
private int expectedCallbacks;
|
||||||
|
|
||||||
|
public AsicCorrections(SmartCommunicationForm parent)
|
||||||
|
: this(parent, null, null, new List<Test>(), new List<ITestParams>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsicCorrections(SmartCommunicationForm parent, ISmartTestMethod testMethod,
|
||||||
|
ITestMethodCfg cfg, IList<Test> tests, IList<ITestParams> multiTestParams)
|
||||||
|
{
|
||||||
|
ParentFrom = parent;
|
||||||
|
this.testMethod = testMethod;
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.tests = tests ?? new List<Test>();
|
||||||
|
this.multiTestParams = multiTestParams ?? new List<ITestParams>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TypeIdentificatorName() { return "ASIC"; }
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
public int StartTimeSec { get; set; }
|
||||||
|
public SmartCommunicationForm ParentFrom { get; set; }
|
||||||
|
public ITestMethodCfg Cfg { get { return cfg; } set { } }
|
||||||
|
public IList<ITestParams> MultiTestParams { get { return multiTestParams; } }
|
||||||
|
public IList<Test> Tests { get { return tests; } set { } }
|
||||||
|
public IList<ISmartReader> iperlHeads { get { return ParentFrom == null ? null : ParentFrom.Heads; } }
|
||||||
|
|
||||||
|
public void PrepareForTestsActivities(int waterMeterPositions0)
|
||||||
|
{
|
||||||
|
StartTime = DateTime.Now;
|
||||||
|
StartTimeSec = StateMachine.Time;
|
||||||
|
stopWorkerThreads = false;
|
||||||
|
completedCallbacks = 0;
|
||||||
|
expectedCallbacks = Math.Max(0, waterMeterPositions0) * Math.Max(1, multiTestParams.Count);
|
||||||
|
workerThreads.Clear();
|
||||||
|
workerThreads.Add(new Thread(Worker) { IsBackground = true, Name = "ASIC SmartCommunication worker" });
|
||||||
|
LogInfo("ASIC_CORRECTIONS_PREPARE: readers={0}, activities={1}, expectedCallbacks={2}",
|
||||||
|
waterMeterPositions0, multiTestParams.Count, expectedCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Worker(object threadData)
|
||||||
|
{
|
||||||
|
int threadId = threadData is TBF.Boxes.IntBox ? ((TBF.Boxes.IntBox)threadData).Val : 0;
|
||||||
|
IList<ISmartReader> readers = iperlHeads ?? new List<ISmartReader>();
|
||||||
|
|
||||||
|
for (int activityIndex = 0; activityIndex < multiTestParams.Count && !stopWorkerThreads; activityIndex++)
|
||||||
|
{
|
||||||
|
Test test = activityIndex < tests.Count ? tests[activityIndex] : null;
|
||||||
|
string activity = multiTestParams[activityIndex] == null ? string.Empty : multiTestParams[activityIndex].Activity;
|
||||||
|
bool allPassed = true;
|
||||||
|
int processed = 0;
|
||||||
|
|
||||||
|
// Match the established iPerl workflow: the optical stream is
|
||||||
|
// opened after entering test mode, before the next configured
|
||||||
|
// activity. It remains open for the following measurement.
|
||||||
|
StartDataStreamProcessingForActiveMeters(activityIndex);
|
||||||
|
|
||||||
|
for (int localIndex = 0; localIndex < readers.Count && !stopWorkerThreads; localIndex++)
|
||||||
|
{
|
||||||
|
AsicReader reader = readers[localIndex] as AsicReader;
|
||||||
|
int originalIndex = GetOriginalIndex(localIndex);
|
||||||
|
WaterMeter waterMeter = GetWaterMeter(originalIndex);
|
||||||
|
CommErr error = CommErr.None;
|
||||||
|
string result = string.Empty;
|
||||||
|
bool passed = WorkerActivity(activity, reader, waterMeter, test, localIndex, ref error, ref result,
|
||||||
|
ParentFrom == null ? null : ParentFrom.CkbState, threadId, activityIndex);
|
||||||
|
|
||||||
|
UpdateResult(test, originalIndex, waterMeter, reader, activity, passed);
|
||||||
|
processed++;
|
||||||
|
allPassed &= passed;
|
||||||
|
LogInfo("ASIC_CORRECTIONS_ACTIVITY: activity='{0}', position={1}, passed={2}, error={3}, result='{4}'",
|
||||||
|
activity, originalIndex + 1, passed, error, result);
|
||||||
|
SmartCommunicationForm.OnCommCompleted(this,
|
||||||
|
new CommCompletedEventArgs(threadId, localIndex, reader, waterMeter, result, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
CompleteTest(test, processed, allPassed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WorkerActivity(string currentActivity, ISmartReader iHead, WaterMeter wm, Test currentTest,
|
||||||
|
int wmNr0, ref CommErr error, ref string resultStr, bool[] ckbState, int threadID, int currentActivityStep)
|
||||||
|
{
|
||||||
|
AsicReader reader = iHead as AsicReader;
|
||||||
|
if (reader == null)
|
||||||
|
{
|
||||||
|
error = CommErr.WrongIPerlType;
|
||||||
|
resultStr = "ASIC reader is not configured.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (IsDisabled(wmNr0, reader, wm, ckbState))
|
||||||
|
{
|
||||||
|
error = CommErr.HeadDisabledByUser;
|
||||||
|
resultStr = "Disabled by user";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string activity = currentActivity ?? string.Empty;
|
||||||
|
string normalized = activity.ToLowerInvariant();
|
||||||
|
bool succeeded;
|
||||||
|
CommErr failureError = CommErr.CommFailed;
|
||||||
|
|
||||||
|
if (normalized.Contains(iPerlCommunicationForm.ReadSerialNrStr.ToLowerInvariant()) ||
|
||||||
|
normalized.Contains("read pcb"))
|
||||||
|
{
|
||||||
|
succeeded = reader.OptoHeadTest.ReadSerialNr();
|
||||||
|
resultStr = succeeded ? "Serial No: " + reader.OptoHeadTest.ReadRequest_PCB() : "Failed Read Serial No";
|
||||||
|
}
|
||||||
|
else if (normalized.Contains(iPerlCommunicationForm.ReadConfigurationStr.ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
succeeded = reader.OptoHeadTest.ReadConfiguration(DiagnosticLedState.State7);
|
||||||
|
resultStr = succeeded && reader.ConfigStruct != null ? reader.ConfigStruct.ToString(1) : "Failed to read configuration";
|
||||||
|
}
|
||||||
|
else if (normalized.Contains(iPerlCommunicationConstants.ReadAdditionalCommonParametersStr.ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
// Preserve the legacy iPerl workflow: this command fills
|
||||||
|
// ConfigStruct and copies the relevant values to the WM.
|
||||||
|
if (wm == null)
|
||||||
|
{
|
||||||
|
error = CommErr.CommFailed;
|
||||||
|
resultStr = "Read additional common parameters: WaterMeter is null.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded = reader.OptoHeadTest.ReadAdditionalCommonParameters(out resultStr, false);
|
||||||
|
failureError = CommErr.Read;
|
||||||
|
if (succeeded)
|
||||||
|
CopyAdditionalCommonParametersToWaterMeter(reader, wm);
|
||||||
|
else if (string.IsNullOrWhiteSpace(resultStr))
|
||||||
|
resultStr = "Failed to read additional common parameters.";
|
||||||
|
}
|
||||||
|
else if (normalized.Contains(iPerlCommunicationConstants.SetFlipModeConstantStr.ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
succeeded = reader.OptoHeadTest.SetFlipMode(FlipMode.Constant);
|
||||||
|
failureError = CommErr.Write;
|
||||||
|
resultStr = succeeded
|
||||||
|
? iPerlCommunicationConstants.SetFlipModeConstantStr + ": OK (0x00)"
|
||||||
|
: iPerlCommunicationConstants.SetFlipModeConstantStr + ": FAILED";
|
||||||
|
}
|
||||||
|
else if (normalized.Contains(iPerlCommunicationConstants.SetFlipModeRandomizedStr.ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
succeeded = reader.OptoHeadTest.SetFlipMode(FlipMode.Randomized);
|
||||||
|
failureError = CommErr.Write;
|
||||||
|
resultStr = succeeded
|
||||||
|
? iPerlCommunicationConstants.SetFlipModeRandomizedStr + ": OK (0x01)"
|
||||||
|
: iPerlCommunicationConstants.SetFlipModeRandomizedStr + ": FAILED";
|
||||||
|
}
|
||||||
|
else if (normalized.Contains(iPerlCommunicationForm.SetTestModeStr.ToLowerInvariant()) ||
|
||||||
|
normalized.Contains(iPerlCommunicationForm.SetTestModeOpto7Str.ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
succeeded = reader.OptoHeadTest.SetTestMode();
|
||||||
|
resultStr = succeeded ? "Test mode enabled" : "Failed Set Test Mode";
|
||||||
|
}
|
||||||
|
else if (normalized.Equals(iPerlCommunicationForm.SetActiveModeStr.ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
succeeded = reader.OptoHeadTest.SetActiveMode();
|
||||||
|
resultStr = succeeded ? "Active mode enabled" : "Failed Set Active Mode";
|
||||||
|
if (succeeded)
|
||||||
|
reader.StopDataStreamProcessing();
|
||||||
|
}
|
||||||
|
else if (normalized.Equals(iPerlCommunicationForm.SetIdleModeStr.ToLowerInvariant()))
|
||||||
|
{
|
||||||
|
succeeded = reader.OptoHeadTest.SetIdleMode();
|
||||||
|
resultStr = succeeded ? "Idle mode enabled" : "Failed Set Idle Mode";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error = CommErr.WrongArguments;
|
||||||
|
resultStr = "ASIC activity is not supported by AsicCorrections: " + activity;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.CommFailed = !succeeded;
|
||||||
|
error = succeeded ? CommErr.None : failureError;
|
||||||
|
return succeeded;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
reader.CommFailed = true;
|
||||||
|
error = CommErr.CommFailed;
|
||||||
|
resultStr = "ASIC communication failed: " + exception.Message;
|
||||||
|
ParentFrom?.Log.Error("ASIC_CORRECTIONS_ACTIVITY_FAILED", exception);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProcessResultOfWorkerActivity(int iMultiTestParamsItem, string currentActivity, int currentGroup,
|
||||||
|
ISmartReader iHead, WaterMeter wm, int wmNr0, CommErr error, string resultStr, bool[] ckbState, int threadID)
|
||||||
|
{
|
||||||
|
SmartCommunicationForm.OnCommCompleted(this,
|
||||||
|
new CommCompletedEventArgs(threadID, wmNr0, iHead, wm, resultStr, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopWorkerThreads(bool stopAllThreads) { stopWorkerThreads = stopAllThreads; }
|
||||||
|
public bool GetStopWorkerThreads() { return stopWorkerThreads; }
|
||||||
|
public IList<Thread> GetAllThreads() { return workerThreads; }
|
||||||
|
public ICorrections GetNewCorrection() { return this; }
|
||||||
|
public void GetGroup() { }
|
||||||
|
public ContextMenu GetContextMenu()
|
||||||
|
{
|
||||||
|
ContextMenu menu = new ContextMenu();
|
||||||
|
menu.MenuItems.Add(NewMenuItem("Read PCB number", iPerlCommunicationForm.ReadSerialNrStr));
|
||||||
|
menu.MenuItems.Add(NewMenuItem("Read configuration", iPerlCommunicationForm.ReadConfigurationStr));
|
||||||
|
menu.MenuItems.Add(NewMenuItem(iPerlCommunicationConstants.ReadAdditionalCommonParametersStr,
|
||||||
|
iPerlCommunicationConstants.ReadAdditionalCommonParametersStr));
|
||||||
|
menu.MenuItems.Add(NewMenuItem(iPerlCommunicationConstants.SetFlipModeConstantStr,
|
||||||
|
iPerlCommunicationConstants.SetFlipModeConstantStr));
|
||||||
|
menu.MenuItems.Add(NewMenuItem(iPerlCommunicationConstants.SetFlipModeRandomizedStr,
|
||||||
|
iPerlCommunicationConstants.SetFlipModeRandomizedStr));
|
||||||
|
menu.MenuItems.Add(NewMenuItem("Set test mode", iPerlCommunicationForm.SetTestModeStr));
|
||||||
|
menu.MenuItems.Add(NewMenuItem("Set active mode", iPerlCommunicationForm.SetActiveModeStr));
|
||||||
|
menu.MenuItems.Add(NewMenuItem("Start optical stream", "__start_stream"));
|
||||||
|
menu.MenuItems.Add(NewMenuItem("Stop optical stream", "__stop_stream"));
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(Label[] labels, PictureBox[] counters, TextBox[] messages, CheckBoxImage[] checkBoxes, int[] ckbIndex,
|
||||||
|
bool[] ckbState, IList<ISmartReader> heads, int textBoxesCount, bool checkBoxesEditMode)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < textBoxesCount; index++)
|
||||||
|
{
|
||||||
|
bool hasReader = heads != null && index < heads.Count && heads[index] is AsicReader;
|
||||||
|
labels[index].Visible = counters[index].Visible = messages[index].Visible = checkBoxes[index].Visible = hasReader;
|
||||||
|
if (!hasReader)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool disabled = heads[index].Disabled;
|
||||||
|
checkBoxes[index].Enabled = checkBoxes[index].Checked = ckbState[index] = !disabled;
|
||||||
|
messages[index].Text = disabled ? "Disabled by user" : "---";
|
||||||
|
counters[index].BackColor = disabled ? iPerlCommunicationConstants.DisabledColor : iPerlCommunicationConstants.OptoNokColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHeadsCount() { return iperlHeads == null ? 0 : iperlHeads.Count; }
|
||||||
|
|
||||||
|
public void StartDataStreamProcessingForActiveMeters(int iMultiTestParamsItem)
|
||||||
|
{
|
||||||
|
if (iperlHeads == null || iMultiTestParamsItem <= 0 || multiTestParams == null ||
|
||||||
|
iMultiTestParamsItem - 1 >= multiTestParams.Count ||
|
||||||
|
!ShouldStartDataStreamForActivity(multiTestParams[iMultiTestParamsItem - 1] == null
|
||||||
|
? null
|
||||||
|
: multiTestParams[iMultiTestParamsItem - 1].Activity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int started = 0;
|
||||||
|
for (int localIndex = 0; localIndex < iperlHeads.Count; localIndex++)
|
||||||
|
{
|
||||||
|
AsicReader reader = iperlHeads[localIndex] as AsicReader;
|
||||||
|
WaterMeter waterMeter = GetWaterMeter(GetOriginalIndex(localIndex));
|
||||||
|
if (reader == null || reader.Disabled || (waterMeter != null && waterMeter.Disabled))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
lock (reader)
|
||||||
|
{
|
||||||
|
reader.StartDataStreamProcessing();
|
||||||
|
started++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogInfo("ASIC_CORRECTIONS_STREAM_STARTED: previousActivity='{0}', readers={1}",
|
||||||
|
multiTestParams[iMultiTestParamsItem - 1].Activity, started);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DoOnCommCompleted(object sender, CommCompletedEventArgs data, IList<int> waterMeterPositions0)
|
||||||
|
{
|
||||||
|
if (data == null || ParentFrom == null || data.WMNr0 < 0 || data.WMNr0 >= ParentFrom.Messages.Length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ParentFrom.Messages[data.WMNr0].Text = data.CommMessage;
|
||||||
|
ParentFrom.Counters[data.WMNr0].BackColor = data.CommErr == CommErr.None
|
||||||
|
? iPerlCommunicationConstants.OptoAndDirOKColor
|
||||||
|
: iPerlCommunicationConstants.OptoNokColor;
|
||||||
|
|
||||||
|
if (++completedCallbacks >= expectedCallbacks && expectedCallbacks > 0 && !ParentFrom.IsDisposed)
|
||||||
|
ParentFrom.CloseAfterResult(ResultVisibilityDelayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NormalClose(IList<int> waterMeterPositions0)
|
||||||
|
{
|
||||||
|
// Do not stop the stream here. The stream must survive closing this
|
||||||
|
// setup dialog so that the subsequent measurement can consume it.
|
||||||
|
// The explicit "Stop optical stream" command or Set Active mode is
|
||||||
|
// responsible for closing the physical port.
|
||||||
|
StopWorkerThreads(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFamilyOfSmartReader(ISmartReader smartHead)
|
||||||
|
{
|
||||||
|
// Use the central family resolver rather than a concrete CLR type.
|
||||||
|
// Readers can be supplied through a proxy/legacy configuration while
|
||||||
|
// still belonging to the extracted ASIC family.
|
||||||
|
return string.Equals(SmartReaderSelection.GetFamilyKey(smartHead), "asic", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuItem NewMenuItem(string text, string command)
|
||||||
|
{
|
||||||
|
MenuItem item = new MenuItem { Text = text, Tag = command };
|
||||||
|
item.Click += OnManualCommand;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnManualCommand(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
MenuItem item = sender as MenuItem;
|
||||||
|
if (item == null || ParentFrom == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string command = item.Tag as string ?? string.Empty;
|
||||||
|
ParentFrom.ActivityLabel.Text = item.Text;
|
||||||
|
for (int index = 0; index < iperlHeads.Count; index++)
|
||||||
|
{
|
||||||
|
AsicReader reader = iperlHeads[index] as AsicReader;
|
||||||
|
if (reader == null || (ParentFrom.CkbState != null && index < ParentFrom.CkbState.Length && !ParentFrom.CkbState[index]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string result;
|
||||||
|
CommErr error = CommErr.None;
|
||||||
|
if (command == "__start_stream")
|
||||||
|
{
|
||||||
|
reader.StartDataStreamProcessing();
|
||||||
|
result = "Optical stream started";
|
||||||
|
}
|
||||||
|
else if (command == "__stop_stream")
|
||||||
|
{
|
||||||
|
reader.StopDataStreamProcessing();
|
||||||
|
result = "Optical stream stopped";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = string.Empty;
|
||||||
|
WorkerActivity(command, reader, null, null, index, ref error, ref result,
|
||||||
|
ParentFrom.CkbState, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < ParentFrom.Messages.Length)
|
||||||
|
ParentFrom.Messages[index].Text = result;
|
||||||
|
if (index < ParentFrom.Counters.Length)
|
||||||
|
ParentFrom.Counters[index].BackColor = error == CommErr.None
|
||||||
|
? iPerlCommunicationConstants.OptoAndDirOKColor
|
||||||
|
: iPerlCommunicationConstants.OptoNokColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDisabled(int localIndex, AsicReader reader, WaterMeter waterMeter, bool[] ckbState)
|
||||||
|
{
|
||||||
|
return (ckbState != null && localIndex < ckbState.Length && !ckbState[localIndex]) || reader.Disabled ||
|
||||||
|
(waterMeter != null && waterMeter.Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ShouldStartDataStreamForActivity(string previousActivity)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(previousActivity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return previousActivity.IndexOf(iPerlCommunicationForm.SetTestModeStr, StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||||
|
previousActivity.IndexOf("80", StringComparison.Ordinal) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetOriginalIndex(int localIndex)
|
||||||
|
{
|
||||||
|
IList<int> positions = ParentFrom == null ? null : ParentFrom.WaterMeterPositions0;
|
||||||
|
return positions != null && localIndex < positions.Count ? positions[localIndex] : localIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WaterMeter GetWaterMeter(int originalIndex)
|
||||||
|
{
|
||||||
|
return ProcessData.BatchRslts != null && ProcessData.BatchRslts.Batch != null &&
|
||||||
|
ProcessData.BatchRslts.Batch.WaterMeters != null && originalIndex >= 0 &&
|
||||||
|
originalIndex < ProcessData.BatchRslts.Batch.WaterMeters.Count
|
||||||
|
? ProcessData.BatchRslts.Batch.WaterMeters[originalIndex]
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyAdditionalCommonParametersToWaterMeter(AsicReader reader, WaterMeter waterMeter)
|
||||||
|
{
|
||||||
|
if (reader == null || reader.ConfigStruct == null || waterMeter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (reader.ConfigStruct.VersionType != null)
|
||||||
|
{
|
||||||
|
waterMeter.FWVersion = string.Format("{0} {1} {2}",
|
||||||
|
reader.ConfigStruct.VersionType.TouchReadVersion,
|
||||||
|
reader.ConfigStruct.VersionType.MeterDeviceType,
|
||||||
|
reader.ConfigStruct.VersionType.MeterFirmwareVersion);
|
||||||
|
}
|
||||||
|
if (reader.ConfigStruct.ReadingUnits.HasValue)
|
||||||
|
waterMeter.SerialNrAux = reader.ConfigStruct.ReadingUnits.Value.ToString();
|
||||||
|
if (reader.ConfigStruct.Calibration != null)
|
||||||
|
waterMeter.CalibFactor = reader.ConfigStruct.Calibration.RawValue;
|
||||||
|
|
||||||
|
LogInfo("ASIC_CORRECTIONS_ADDITIONAL_COMMON_PARAMETERS: reader='{0}', FWVersion='{1}', ReadingUnits='{2}', FlipMode='{3}', CalibFactor={4}",
|
||||||
|
reader.Name,
|
||||||
|
waterMeter.FWVersion,
|
||||||
|
waterMeter.SerialNrAux,
|
||||||
|
reader.ConfigStruct.FlipMode.HasValue ? reader.ConfigStruct.FlipMode.Value.ToString() : "<not read>",
|
||||||
|
waterMeter.CalibFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateResult(Test test, int originalIndex, WaterMeter waterMeter, AsicReader reader, string activity, bool passed)
|
||||||
|
{
|
||||||
|
if (test == null || ProcessData.BatchRslts == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MeterTestRslt result = ProcessData.BatchRslts.GetMeterTestRslt(test.Name, originalIndex, CompoundMeterId.Single);
|
||||||
|
if (result == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
result.RegReaderType = (int)RegisterReaderType.DataStream;
|
||||||
|
result.TestDone = true;
|
||||||
|
result.Passed = passed;
|
||||||
|
if (passed && activity != null && activity.ToLowerInvariant().Contains(iPerlCommunicationForm.ReadSerialNrStr.ToLowerInvariant()) &&
|
||||||
|
reader != null && waterMeter != null && !string.IsNullOrWhiteSpace(reader.SerialNr))
|
||||||
|
{
|
||||||
|
waterMeter.SerialNr = reader.SerialNr;
|
||||||
|
if (result.WaterMeter != null)
|
||||||
|
result.WaterMeter.SerialNr = reader.SerialNr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompleteTest(Test test, int processedReaders, bool allPassed)
|
||||||
|
{
|
||||||
|
if (test == null || ProcessData.BatchRslts == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TestRslt testResult = ProcessData.BatchRslts.GetTestRslt(test.Name, 0);
|
||||||
|
if (testResult == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (testResult.StartTime == DateTime.MinValue)
|
||||||
|
testResult.StartTime = DateTime.Now;
|
||||||
|
testResult.EndTime = DateTime.Now;
|
||||||
|
testResult.TestDone = true;
|
||||||
|
testResult.Remark = processedReaders == 0 ? "No ASIC reader is configured for this Meters Path."
|
||||||
|
: allPassed ? "ASIC communication completed." : "ASIC communication failed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogInfo(string format, params object[] values)
|
||||||
|
{
|
||||||
|
ParentFrom?.Log.InfoFormat(format, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
|
||||||
|
namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
||||||
|
{
|
||||||
|
internal sealed class GenesisCorrections : ManualSmartCorrectionsBase<GenesisSmartReader>
|
||||||
|
{
|
||||||
|
public GenesisCorrections(SmartCommunicationForm parent) : base(parent) { }
|
||||||
|
|
||||||
|
public override string TypeIdentificatorName() => "Genesis";
|
||||||
|
public override bool IsFamilyOfSmartReader(ISmartReader smartHead) => smartHead is GenesisSmartReader;
|
||||||
|
|
||||||
|
protected override IEnumerable<MenuItem> CreateMenuItems()
|
||||||
|
{
|
||||||
|
yield return new MenuItem("Read PCB Number") { Tag = "ReadPcb" };
|
||||||
|
yield return new MenuItem("Set RFID mode") { Tag = "SetRfid" };
|
||||||
|
yield return new MenuItem("Set NFC mode") { Tag = "SetNfc" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string ExecuteManualCommand(GenesisSmartReader reader, string command)
|
||||||
|
{
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case "ReadPcb": return reader.OptoHeadTest.ReadRequest_PCB();
|
||||||
|
case "SetRfid": reader.SetRfidInterface(); return "OK";
|
||||||
|
case "SetNfc": reader.SetNfcInterface(); return "OK";
|
||||||
|
default: return "Unsupported Genesis operation";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
-25
@@ -13,10 +13,9 @@ using Sensus.iPerl.NfcHandler;
|
|||||||
using TBF.Resources;
|
using TBF.Resources;
|
||||||
using TBF.Rig.Generic;
|
using TBF.Rig.Generic;
|
||||||
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||||
using TBF.Rig.RegisterReaders.IPerlReader.implementations;
|
using TBF.Rig.RegisterReaders.iPerlASICReader.implementations;
|
||||||
using TBF.Rig.Sequences;
|
using TBF.Rig.Sequences;
|
||||||
using TBF.Rig.TestMethods.iPerlCommunication;
|
using TBF.Rig.TestMethods.iPerlCommunication;
|
||||||
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
|
||||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
using CalibrationStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStruct;
|
using CalibrationStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStruct;
|
||||||
using CalibrationStructV4 = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStructV4;
|
using CalibrationStructV4 = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStructV4;
|
||||||
@@ -24,7 +23,7 @@ using CheckBoxImage = TBF.Boxes.CheckBoxImage;
|
|||||||
using Command = TBF.Rig.RegisterReaders.CommonRR.Command;
|
using Command = TBF.Rig.RegisterReaders.CommonRR.Command;
|
||||||
using CommunicationInterface = TBF.Rig.RegisterReaders.CommonRR.CommunicationInterface;
|
using CommunicationInterface = TBF.Rig.RegisterReaders.CommonRR.CommunicationInterface;
|
||||||
using ConfigStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.ConfigStruct;
|
using ConfigStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.ConfigStruct;
|
||||||
using Factory = TBF.Rig.RegisterReaders.iPerlReaderUNI.Factory;
|
using Factory = TBF.Rig.RegisterReaders.iPerlASICReader.Factory;
|
||||||
using MessageID = TBF.Rig.RegisterReaders.CommonRR.MessageID;
|
using MessageID = TBF.Rig.RegisterReaders.CommonRR.MessageID;
|
||||||
using MeterState = TBF.Rig.RegisterReaders.CommonRR.MeterState;
|
using MeterState = TBF.Rig.RegisterReaders.CommonRR.MeterState;
|
||||||
using MeterType = TBF.Rig.RegisterReaders.CommonRR.MeterType;
|
using MeterType = TBF.Rig.RegisterReaders.CommonRR.MeterType;
|
||||||
@@ -228,7 +227,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
|
|
||||||
for (int wmNr0 = 0; wmNr0 < iperlHeads.Count; wmNr0++)
|
for (int wmNr0 = 0; wmNr0 < iperlHeads.Count; wmNr0++)
|
||||||
{
|
{
|
||||||
IperlHead ihead = iperlHeads[wmNr0] as IperlHead;
|
SmartReader ihead = iperlHeads[wmNr0] as SmartReader;
|
||||||
if ((ihead.Group == group) && (threadIx < muxBrdOrGroup14Nrs.Count) &&
|
if ((ihead.Group == group) && (threadIx < muxBrdOrGroup14Nrs.Count) &&
|
||||||
(ihead.MuxBoardNrOrGroup14 == muxBrdOrGroup14Nrs[threadIx]))
|
(ihead.MuxBoardNrOrGroup14 == muxBrdOrGroup14Nrs[threadIx]))
|
||||||
{
|
{
|
||||||
@@ -306,14 +305,13 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
activityLabel.Text = menuItem.Text;
|
activityLabel.Text = menuItem.Text;
|
||||||
|
|
||||||
List<Task> tasks = new List<Task>();
|
List<Task> tasks = new List<Task>();
|
||||||
foreach (var iSmartReader in ProcessData.SmartHeadsUni)
|
for (int position = 0; position < iperlHeads.Count; position++)
|
||||||
{
|
{
|
||||||
if (!(iSmartReader is IperlHead iHead))
|
if (!(iperlHeads[position] is SmartReader iHead))
|
||||||
{
|
{
|
||||||
continue;//ignore different types of heads
|
continue;//ignore different types of heads
|
||||||
}
|
}
|
||||||
|
|
||||||
int position = iHead.Position - 1;
|
|
||||||
if (position < 0 || position >= checkBoxes.Length || position >= messages.Length)
|
if (position < 0 || position >= checkBoxes.Length || position >= messages.Length)
|
||||||
{
|
{
|
||||||
continue; // Skip this head if position is out of range
|
continue; // Skip this head if position is out of range
|
||||||
@@ -342,37 +340,39 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ProcessTask(IperlHead head, object tag)
|
private async Task<string> ProcessTask(SmartReader head, object tag)
|
||||||
{
|
{
|
||||||
string txt = "";
|
string txt = "";
|
||||||
switch (tag)
|
switch (tag)
|
||||||
{
|
{
|
||||||
case "ReadPCB":
|
case "ReadPCB":
|
||||||
txt = OpticalHeadTest.ReadRequest_PCB(head);
|
txt = IPerlASICOpticalHeadTest.ReadPcb(head);
|
||||||
break;
|
break;
|
||||||
case "WriteRequestPort_u8_Customer_Text":
|
case "WriteRequestPort_u8_Customer_Text":
|
||||||
txt = OpticalHeadTest.WriteRequestPort_u8_Customer_Text(head);
|
txt = "Unsupported ASIC operation";
|
||||||
break;
|
break;
|
||||||
case "OpenSealing":
|
case "OpenSealing":
|
||||||
txt = OpticalHeadTest.OpenSealing(head);
|
txt = "Unsupported ASIC operation";
|
||||||
break;
|
break;
|
||||||
case "StartTestMode":
|
case "StartTestMode":
|
||||||
txt = OpticalHeadTest.SetTestMode(head);
|
txt = IPerlASICOpticalHeadTest.SetTestMode(head);
|
||||||
break;
|
break;
|
||||||
case "TurnOffTestMode":
|
case "TurnOffTestMode":
|
||||||
txt = OpticalHeadTest.SetActiveMode(head);
|
txt = IPerlASICOpticalHeadTest.SetActiveMode(head);
|
||||||
break;
|
break;
|
||||||
case "TurnOffRadio":
|
case "TurnOffRadio":
|
||||||
txt = OpticalHeadTest.TurnOffRadio(head);
|
txt = IPerlASICOpticalHeadTest.TurnOffRadio(head);
|
||||||
break;
|
break;
|
||||||
case "SetProductionMode":
|
case "SetProductionMode":
|
||||||
txt = OpticalHeadTest.SetProductionMode(head);
|
txt = IPerlASICOpticalHeadTest.SetProductionMode(head);
|
||||||
break;
|
break;
|
||||||
case "SetRFID":
|
case "SetRFID":
|
||||||
txt = OpticalHeadTest.SetRfidMode(head);
|
head.SetRfidInterface();
|
||||||
|
txt = "OK";
|
||||||
break;
|
break;
|
||||||
case "SetNFC":
|
case "SetNFC":
|
||||||
txt = OpticalHeadTest.SetNfcMode(head);
|
head.SetNfcInterface();
|
||||||
|
txt = "OK";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,10 +399,12 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
///
|
///
|
||||||
for (int i = 0; i < textBoxesCount; i++)
|
for (int i = 0; i < textBoxesCount; i++)
|
||||||
{
|
{
|
||||||
labels[i].Visible = counters[i].Visible = messages[i].Visible = checkBoxes[i].Visible = true;
|
bool hasHead = i < iperlHeads.Count;
|
||||||
|
labels[i].Visible = counters[i].Visible = messages[i].Visible = checkBoxes[i].Visible = hasHead;
|
||||||
|
if (!hasHead) continue;
|
||||||
|
|
||||||
|
|
||||||
IperlHead iperlHead = iperlHeads[i] as IperlHead;
|
SmartReader iperlHead = iperlHeads[i] as SmartReader;
|
||||||
if (!checkBoxesEditMode && (iperlHead == null || iperlHead.Disabled))
|
if (!checkBoxesEditMode && (iperlHead == null || iperlHead.Disabled))
|
||||||
{
|
{
|
||||||
/// iPerl position i+1 is disabled
|
/// iPerl position i+1 is disabled
|
||||||
@@ -441,7 +443,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var iPerl = iSmartReader as IperlHead;
|
var iPerl = iSmartReader as SmartReader;
|
||||||
if (iPerl != null && iPerl.Group > lastGroup) lastGroup = iPerl.Group;
|
if (iPerl != null && iPerl.Group > lastGroup) lastGroup = iPerl.Group;
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
@@ -468,7 +470,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
{
|
{
|
||||||
foreach (ISmartReader iPerl in iperlHeads)
|
foreach (ISmartReader iPerl in iperlHeads)
|
||||||
{
|
{
|
||||||
if (iPerl is IperlHead ihead)
|
if (iPerl is SmartReader ihead)
|
||||||
{
|
{
|
||||||
if (!muxBrdOrGroup14Nrs.Contains(ihead.MuxBoardNrOrGroup14))
|
if (!muxBrdOrGroup14Nrs.Contains(ihead.MuxBoardNrOrGroup14))
|
||||||
muxBrdOrGroup14Nrs.Add(ihead.MuxBoardNrOrGroup14);
|
muxBrdOrGroup14Nrs.Add(ihead.MuxBoardNrOrGroup14);
|
||||||
@@ -2591,8 +2593,8 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
|
|
||||||
private static readonly List<Type> SupportedReaders = new List<Type>()
|
private static readonly List<Type> SupportedReaders = new List<Type>()
|
||||||
{
|
{
|
||||||
typeof(IperlHead),
|
typeof(SmartReader),
|
||||||
typeof(TestMethods.iPerlCommunication.iPerlHead.Factory)
|
typeof(TBF.Rig.RegisterReaders.iPerlASICReader.Factory)
|
||||||
};
|
};
|
||||||
public bool IsFamilyOfSmartReader(ISmartReader smartHead)
|
public bool IsFamilyOfSmartReader(ISmartReader smartHead)
|
||||||
{
|
{
|
||||||
@@ -2814,7 +2816,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IperlHead iperlHead = (iperlHeads[i] as IperlHead);
|
SmartReader iperlHead = (iperlHeads[i] as SmartReader);
|
||||||
|
|
||||||
OptoHeadState checkFlowDirection =
|
OptoHeadState checkFlowDirection =
|
||||||
((iperlHead == null) ? OptoHeadState.Disabled : iperlHead.CheckFlowDirection());
|
((iperlHead == null) ? OptoHeadState.Disabled : iperlHead.CheckFlowDirection());
|
||||||
@@ -2875,4 +2877,4 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using Config.Resources;
|
||||||
|
using log4net;
|
||||||
|
using Sensus.iPerl.RfidCom.Helper;
|
||||||
|
using TBF.Rig.RegisterReaders.CommonRR;
|
||||||
|
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||||
|
using TBF.Rig.RegisterReaders.iPerlASICReader.implementations;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using static Sensus.iPerl.NfcHandler.MCI_Protocol;
|
||||||
|
|
||||||
|
namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manual commands for ASIC readers. They intentionally use the ASIC correction
|
||||||
|
/// transport path and never fall back to the old Sensus iPerl implementation.
|
||||||
|
/// </summary>
|
||||||
|
internal static class IPerlASICOpticalHeadTest
|
||||||
|
{
|
||||||
|
private static readonly ILog RfidDataLogger = LogManager.GetLogger("RfidData");
|
||||||
|
|
||||||
|
internal static string ReadPcb(SmartReader reader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] pcb;
|
||||||
|
int result = IPerlASICCorrections.ReadRequestPort(
|
||||||
|
SmartCommunicationForm.TestMethodCfg,
|
||||||
|
reader,
|
||||||
|
MessageID.Configuration,
|
||||||
|
StructName.Configuration,
|
||||||
|
16,
|
||||||
|
5,
|
||||||
|
out pcb);
|
||||||
|
|
||||||
|
if (result == 0 && pcb != null)
|
||||||
|
{
|
||||||
|
return RfidHelper.HexLiteral2Unsigned(
|
||||||
|
RfidHelper.SwapHexcode(BitConverter.ToString(pcb).Replace("-", string.Empty))).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
RfidDataLogger.ErrorFormat("COM{0}: ASIC Read PCB failed ({1}).", reader.RfidComPortNr, result);
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string SetActiveMode(SmartReader reader)
|
||||||
|
{
|
||||||
|
return WriteCommand(reader, Command.SetActiveMode, "Error Set Active Mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string SetTestMode(SmartReader reader)
|
||||||
|
{
|
||||||
|
return WriteCommand(reader, Command.SetTestMode, "Error Set Test Mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string TurnOffRadio(SmartReader reader)
|
||||||
|
{
|
||||||
|
return WriteRadioValue(reader, MessageID.RadioPassthrough, StructName.RadioParams, 0x1898, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string SetProductionMode(SmartReader reader)
|
||||||
|
{
|
||||||
|
return WriteRadioValue(reader, MessageID.RadioPassthrough, StructName.RadioInfo, 0x1804, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string WriteCommand(SmartReader reader, Command command, string errorText)
|
||||||
|
{
|
||||||
|
int result = IPerlASICCorrections.WriteRequestPort(
|
||||||
|
SmartCommunicationForm.TestMethodCfg,
|
||||||
|
reader,
|
||||||
|
MessageID.Command,
|
||||||
|
StructName.Command,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
new[] { (byte)command });
|
||||||
|
return result == 0 ? "OK" : errorText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string WriteRadioValue(SmartReader reader, MessageID messageId,
|
||||||
|
StructName structName, int offset, byte value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int result = IPerlASICCorrections.WriteRequestPort(
|
||||||
|
SmartCommunicationForm.TestMethodCfg,
|
||||||
|
reader,
|
||||||
|
messageId,
|
||||||
|
structName,
|
||||||
|
offset,
|
||||||
|
1,
|
||||||
|
new[] { value });
|
||||||
|
return result == 0 ? "OK" : "Error";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
-25
@@ -14,9 +14,9 @@ using TBF.Resources;
|
|||||||
using TBF.Rig.Generic;
|
using TBF.Rig.Generic;
|
||||||
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||||
using TBF.Rig.RegisterReaders.IPerlReader.implementations;
|
using TBF.Rig.RegisterReaders.IPerlReader.implementations;
|
||||||
|
using LegacyOpticalHeadTest = TBF.Rig.RegisterReaders.iPerlReaderUNI.test.OpticalHeadTest;
|
||||||
using TBF.Rig.Sequences;
|
using TBF.Rig.Sequences;
|
||||||
using TBF.Rig.TestMethods.iPerlCommunication;
|
using TBF.Rig.TestMethods.iPerlCommunication;
|
||||||
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
|
||||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
using CalibrationStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStruct;
|
using CalibrationStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStruct;
|
||||||
using CalibrationStructV4 = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStructV4;
|
using CalibrationStructV4 = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStructV4;
|
||||||
@@ -24,7 +24,7 @@ using CheckBoxImage = TBF.Boxes.CheckBoxImage;
|
|||||||
using Command = TBF.Rig.RegisterReaders.CommonRR.Command;
|
using Command = TBF.Rig.RegisterReaders.CommonRR.Command;
|
||||||
using CommunicationInterface = TBF.Rig.RegisterReaders.CommonRR.CommunicationInterface;
|
using CommunicationInterface = TBF.Rig.RegisterReaders.CommonRR.CommunicationInterface;
|
||||||
using ConfigStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.ConfigStruct;
|
using ConfigStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.ConfigStruct;
|
||||||
using Factory = TBF.Rig.RegisterReaders.iPerlReaderUNI.Factory;
|
using Factory = TBF.Rig.RegisterReaders.IPerlReader.Factory;
|
||||||
using MessageID = TBF.Rig.RegisterReaders.CommonRR.MessageID;
|
using MessageID = TBF.Rig.RegisterReaders.CommonRR.MessageID;
|
||||||
using MeterState = TBF.Rig.RegisterReaders.CommonRR.MeterState;
|
using MeterState = TBF.Rig.RegisterReaders.CommonRR.MeterState;
|
||||||
using MeterType = TBF.Rig.RegisterReaders.CommonRR.MeterType;
|
using MeterType = TBF.Rig.RegisterReaders.CommonRR.MeterType;
|
||||||
@@ -139,7 +139,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
public IPerlCorrections(SmartCommunicationForm parent)
|
public IPerlCorrections(SmartCommunicationForm parent)
|
||||||
{
|
{
|
||||||
this.ParentFrom = parent;
|
this.ParentFrom = parent;
|
||||||
TBF.Rig.RegisterReaders.iPerlReaderUNI.Factory factory = new Factory();
|
Factory factory = new Factory();
|
||||||
TestMethod method = new TestMethod(factory.DefaultConfig());
|
TestMethod method = new TestMethod(factory.DefaultConfig());
|
||||||
this.testMethod = method;
|
this.testMethod = method;
|
||||||
this.Cfg = method.testMethodCfg;
|
this.Cfg = method.testMethodCfg;
|
||||||
@@ -228,7 +228,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
|
|
||||||
for (int wmNr0 = 0; wmNr0 < iperlHeads.Count; wmNr0++)
|
for (int wmNr0 = 0; wmNr0 < iperlHeads.Count; wmNr0++)
|
||||||
{
|
{
|
||||||
IperlHead ihead = iperlHeads[wmNr0] as IperlHead;
|
SmartReader ihead = iperlHeads[wmNr0] as SmartReader;
|
||||||
if ((ihead.Group == group) && (threadIx < muxBrdOrGroup14Nrs.Count) &&
|
if ((ihead.Group == group) && (threadIx < muxBrdOrGroup14Nrs.Count) &&
|
||||||
(ihead.MuxBoardNrOrGroup14 == muxBrdOrGroup14Nrs[threadIx]))
|
(ihead.MuxBoardNrOrGroup14 == muxBrdOrGroup14Nrs[threadIx]))
|
||||||
{
|
{
|
||||||
@@ -306,14 +306,13 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
activityLabel.Text = menuItem.Text;
|
activityLabel.Text = menuItem.Text;
|
||||||
|
|
||||||
List<Task> tasks = new List<Task>();
|
List<Task> tasks = new List<Task>();
|
||||||
foreach (var iSmartReader in ProcessData.SmartHeadsUni)
|
for (int position = 0; position < iperlHeads.Count; position++)
|
||||||
{
|
{
|
||||||
if (!(iSmartReader is IperlHead iHead))
|
if (!(iperlHeads[position] is SmartReader iHead))
|
||||||
{
|
{
|
||||||
continue;//ignore different types of heads
|
continue;//ignore different types of heads
|
||||||
}
|
}
|
||||||
|
|
||||||
int position = iHead.Position - 1;
|
|
||||||
if (position < 0 || position >= checkBoxes.Length || position >= messages.Length)
|
if (position < 0 || position >= checkBoxes.Length || position >= messages.Length)
|
||||||
{
|
{
|
||||||
continue; // Skip this head if position is out of range
|
continue; // Skip this head if position is out of range
|
||||||
@@ -342,37 +341,39 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ProcessTask(IperlHead head, object tag)
|
private async Task<string> ProcessTask(SmartReader head, object tag)
|
||||||
{
|
{
|
||||||
string txt = "";
|
string txt = "";
|
||||||
switch (tag)
|
switch (tag)
|
||||||
{
|
{
|
||||||
case "ReadPCB":
|
case "ReadPCB":
|
||||||
txt = OpticalHeadTest.ReadRequest_PCB(head);
|
txt = LegacyOpticalHeadTest.ReadRequest_PCB(head);
|
||||||
break;
|
break;
|
||||||
case "WriteRequestPort_u8_Customer_Text":
|
case "WriteRequestPort_u8_Customer_Text":
|
||||||
txt = OpticalHeadTest.WriteRequestPort_u8_Customer_Text(head);
|
txt = "Unsupported old iPerl operation";
|
||||||
break;
|
break;
|
||||||
case "OpenSealing":
|
case "OpenSealing":
|
||||||
txt = OpticalHeadTest.OpenSealing(head);
|
txt = LegacyOpticalHeadTest.OpenSealing(head);
|
||||||
break;
|
break;
|
||||||
case "StartTestMode":
|
case "StartTestMode":
|
||||||
txt = OpticalHeadTest.SetTestMode(head);
|
txt = LegacyOpticalHeadTest.SetTestMode(head);
|
||||||
break;
|
break;
|
||||||
case "TurnOffTestMode":
|
case "TurnOffTestMode":
|
||||||
txt = OpticalHeadTest.SetActiveMode(head);
|
txt = LegacyOpticalHeadTest.SetActiveMode(head);
|
||||||
break;
|
break;
|
||||||
case "TurnOffRadio":
|
case "TurnOffRadio":
|
||||||
txt = OpticalHeadTest.TurnOffRadio(head);
|
txt = LegacyOpticalHeadTest.TurnOffRadio(head);
|
||||||
break;
|
break;
|
||||||
case "SetProductionMode":
|
case "SetProductionMode":
|
||||||
txt = OpticalHeadTest.SetProductionMode(head);
|
txt = LegacyOpticalHeadTest.SetProductionMode(head);
|
||||||
break;
|
break;
|
||||||
case "SetRFID":
|
case "SetRFID":
|
||||||
txt = OpticalHeadTest.SetRfidMode(head);
|
head.SetRfidInterface();
|
||||||
|
txt = "OK";
|
||||||
break;
|
break;
|
||||||
case "SetNFC":
|
case "SetNFC":
|
||||||
txt = OpticalHeadTest.SetNfcMode(head);
|
head.SetNfcInterface();
|
||||||
|
txt = "OK";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,10 +400,12 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
///
|
///
|
||||||
for (int i = 0; i < textBoxesCount; i++)
|
for (int i = 0; i < textBoxesCount; i++)
|
||||||
{
|
{
|
||||||
labels[i].Visible = counters[i].Visible = messages[i].Visible = checkBoxes[i].Visible = true;
|
bool hasHead = i < iperlHeads.Count;
|
||||||
|
labels[i].Visible = counters[i].Visible = messages[i].Visible = checkBoxes[i].Visible = hasHead;
|
||||||
|
if (!hasHead) continue;
|
||||||
|
|
||||||
|
|
||||||
IperlHead iperlHead = iperlHeads[i] as IperlHead;
|
SmartReader iperlHead = iperlHeads[i] as SmartReader;
|
||||||
if (!checkBoxesEditMode && (iperlHead == null || iperlHead.Disabled))
|
if (!checkBoxesEditMode && (iperlHead == null || iperlHead.Disabled))
|
||||||
{
|
{
|
||||||
/// iPerl position i+1 is disabled
|
/// iPerl position i+1 is disabled
|
||||||
@@ -441,7 +444,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var iPerl = iSmartReader as IperlHead;
|
var iPerl = iSmartReader as SmartReader;
|
||||||
if (iPerl != null && iPerl.Group > lastGroup) lastGroup = iPerl.Group;
|
if (iPerl != null && iPerl.Group > lastGroup) lastGroup = iPerl.Group;
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
@@ -468,7 +471,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
{
|
{
|
||||||
foreach (ISmartReader iPerl in iperlHeads)
|
foreach (ISmartReader iPerl in iperlHeads)
|
||||||
{
|
{
|
||||||
if (iPerl is IperlHead ihead)
|
if (iPerl is SmartReader ihead)
|
||||||
{
|
{
|
||||||
if (!muxBrdOrGroup14Nrs.Contains(ihead.MuxBoardNrOrGroup14))
|
if (!muxBrdOrGroup14Nrs.Contains(ihead.MuxBoardNrOrGroup14))
|
||||||
muxBrdOrGroup14Nrs.Add(ihead.MuxBoardNrOrGroup14);
|
muxBrdOrGroup14Nrs.Add(ihead.MuxBoardNrOrGroup14);
|
||||||
@@ -2591,8 +2594,8 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
|
|
||||||
private static readonly List<Type> SupportedReaders = new List<Type>()
|
private static readonly List<Type> SupportedReaders = new List<Type>()
|
||||||
{
|
{
|
||||||
typeof(IperlHead),
|
typeof(SmartReader),
|
||||||
typeof(TestMethods.iPerlCommunication.iPerlHead.Factory)
|
typeof(TBF.Rig.RegisterReaders.IPerlReader.Factory)
|
||||||
};
|
};
|
||||||
public bool IsFamilyOfSmartReader(ISmartReader smartHead)
|
public bool IsFamilyOfSmartReader(ISmartReader smartHead)
|
||||||
{
|
{
|
||||||
@@ -2814,7 +2817,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IperlHead iperlHead = (iperlHeads[i] as IperlHead);
|
SmartReader iperlHead = (iperlHeads[i] as SmartReader);
|
||||||
|
|
||||||
OptoHeadState checkFlowDirection =
|
OptoHeadState checkFlowDirection =
|
||||||
((iperlHead == null) ? OptoHeadState.Disabled : iperlHead.CheckFlowDirection());
|
((iperlHead == null) ? OptoHeadState.Disabled : iperlHead.CheckFlowDirection());
|
||||||
@@ -2875,4 +2878,4 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+141
@@ -0,0 +1,141 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Config.Entities;
|
||||||
|
using Results.Entities;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.RegisterReaders.CommonRR.IPerl;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using CheckBoxImage = TBF.Boxes.CheckBoxImage;
|
||||||
|
|
||||||
|
namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common manual-only adapter for smart-meter reader families.
|
||||||
|
/// Test execution is deliberately not implemented here; each protocol must provide
|
||||||
|
/// its own test adapter before it is enabled for SmartCommunication test activities.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract class ManualSmartCorrectionsBase<TReader> : ICorrections
|
||||||
|
where TReader : class, ISmartReader
|
||||||
|
{
|
||||||
|
protected ManualSmartCorrectionsBase(SmartCommunicationForm parent)
|
||||||
|
{
|
||||||
|
ParentFrom = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract string TypeIdentificatorName();
|
||||||
|
public abstract bool IsFamilyOfSmartReader(ISmartReader smartHead);
|
||||||
|
protected abstract IEnumerable<MenuItem> CreateMenuItems();
|
||||||
|
protected abstract string ExecuteManualCommand(TReader reader, string command);
|
||||||
|
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
public int StartTimeSec { get; set; }
|
||||||
|
public SmartCommunicationForm ParentFrom { get; set; }
|
||||||
|
public ITestMethodCfg Cfg { get; set; }
|
||||||
|
public IList<ITestParams> MultiTestParams { get; private set; }
|
||||||
|
public IList<Test> Tests { get; set; }
|
||||||
|
public IList<ISmartReader> iperlHeads => ParentFrom.Heads;
|
||||||
|
|
||||||
|
public ContextMenu GetContextMenu()
|
||||||
|
{
|
||||||
|
ContextMenu menu = new ContextMenu();
|
||||||
|
foreach (MenuItem item in CreateMenuItems())
|
||||||
|
{
|
||||||
|
item.Click += OnManualCommand;
|
||||||
|
menu.MenuItems.Add(item);
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnManualCommand(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
MenuItem item = sender as MenuItem;
|
||||||
|
if (item == null || item.Tag == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ParentFrom.ActivityLabel.Text = item.Text;
|
||||||
|
string command = item.Tag.ToString();
|
||||||
|
IList<ISmartReader> heads = ParentFrom.Heads;
|
||||||
|
ParentFrom.Log.InfoFormat(
|
||||||
|
"SMART_COMM_MANUAL_COMMAND_START: family='{0}', command='{1}', caption='{2}', heads={3}",
|
||||||
|
TypeIdentificatorName(),
|
||||||
|
command,
|
||||||
|
item.Text,
|
||||||
|
heads == null ? 0 : heads.Count);
|
||||||
|
List<Task<string>> operations = heads
|
||||||
|
.Select(head => head as TReader)
|
||||||
|
.Select(reader => reader == null
|
||||||
|
? Task.FromResult("Unsupported reader")
|
||||||
|
: Task.Run(() => ExecuteSafely(reader, command)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
string[] results = await Task.WhenAll(operations);
|
||||||
|
for (int index = 0; index < results.Length && index < ParentFrom.Messages.Length; index++)
|
||||||
|
{
|
||||||
|
ParentFrom.Messages[index].Text = results[index];
|
||||||
|
ParentFrom.Log.InfoFormat(
|
||||||
|
"SMART_COMM_MANUAL_COMMAND_RESULT: family='{0}', command='{1}', displayedRow={2}, result='{3}'",
|
||||||
|
TypeIdentificatorName(),
|
||||||
|
command,
|
||||||
|
index + 1,
|
||||||
|
results[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ExecuteSafely(TReader reader, string command)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ExecuteManualCommand(reader, command) ?? "OK";
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
ParentFrom.Log.Error($"{TypeIdentificatorName()} manual command '{command}' failed for {reader.Name}.", exception);
|
||||||
|
return $"Error: {exception.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(Label[] labels, PictureBox[] counters, TextBox[] messages, CheckBoxImage[] checkBoxes,
|
||||||
|
int[] ckbIndex, bool[] ckbState, IList<ISmartReader> heads, int textBoxesCount, bool checkBoxesEditMode)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < textBoxesCount; index++)
|
||||||
|
{
|
||||||
|
bool visible = heads != null && index < heads.Count;
|
||||||
|
labels[index].Visible = counters[index].Visible = messages[index].Visible = checkBoxes[index].Visible = visible;
|
||||||
|
if (!visible)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ISmartReader reader = heads[index];
|
||||||
|
bool enabled = reader != null && (!reader.Disabled || checkBoxesEditMode);
|
||||||
|
checkBoxes[index].Enabled = checkBoxes[index].Checked = ckbState[index] = enabled;
|
||||||
|
counters[index].BackColor = enabled ? SystemColors.Control : iPerlCommunicationConstants.DisabledColor;
|
||||||
|
messages[index].Text = enabled ? "---" : "Disabled by user";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHeadsCount() => ParentFrom?.Heads?.Count ?? 0;
|
||||||
|
public void PrepareForTestsActivities(int waterMeterPositions0) { }
|
||||||
|
public void StartDataStreamProcessingForActiveMeters(int iMultiTestParamsItem) { }
|
||||||
|
public void StopWorkerThreads(bool bStopAllThreads) { }
|
||||||
|
public bool GetStopWorkerThreads() => false;
|
||||||
|
public IList<Thread> GetAllThreads() => new List<Thread>();
|
||||||
|
public ICorrections GetNewCorrection() => throw new NotSupportedException("Manual adapter cannot create a test adapter.");
|
||||||
|
public void GetGroup() { }
|
||||||
|
public void Worker(object threadData) => throw new NotSupportedException($"{TypeIdentificatorName()} test communication is not implemented.");
|
||||||
|
public bool WorkerActivity(string currentActivity, ISmartReader iHead, WaterMeter wm, Test currentTest,
|
||||||
|
int wmNr0, ref CommErr error, ref string resultStr, bool[] ckbState, int threadID, int currentActivityStep)
|
||||||
|
{
|
||||||
|
error = CommErr.WrongIPerlType;
|
||||||
|
resultStr = $"{TypeIdentificatorName()} test communication is not implemented.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public void ProcessResultOfWorkerActivity(int iMultiTestParamsItem, string currentActivity, int currentGroup,
|
||||||
|
ISmartReader iHead, WaterMeter wm, int wmNr0, CommErr error, string resultStr, bool[] ckbState, int threadID) { }
|
||||||
|
public void DoOnCommCompleted(object sender, CommCompletedEventArgs data, IList<int> waterMeterPositions0) { }
|
||||||
|
public void NormalClose(IList<int> waterMeterPositions0) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-5
@@ -294,7 +294,9 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
///
|
///
|
||||||
for (int i = 0; i < textBoxesCount; i++)
|
for (int i = 0; i < textBoxesCount; i++)
|
||||||
{
|
{
|
||||||
labels[i].Visible = counters[i].Visible = messages[i].Visible = checkBoxes[i].Visible = true;
|
bool hasHead = i < iperlHeads.Count;
|
||||||
|
labels[i].Visible = counters[i].Visible = messages[i].Visible = checkBoxes[i].Visible = hasHead;
|
||||||
|
if (!hasHead) continue;
|
||||||
|
|
||||||
|
|
||||||
ISmartReader iperlHead = iperlHeads[i];
|
ISmartReader iperlHead = iperlHeads[i];
|
||||||
@@ -516,14 +518,13 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
activityLabel.Text = menuItem.Text;
|
activityLabel.Text = menuItem.Text;
|
||||||
|
|
||||||
List<Task> tasks = new List<Task>();
|
List<Task> tasks = new List<Task>();
|
||||||
foreach (var iSmartReader in ProcessData.SmartHeadsUni)
|
for (int position = 0; position < iperlHeads.Count; position++)
|
||||||
{
|
{
|
||||||
if (!(iSmartReader is SmartReader iHead))
|
if (!(iperlHeads[position] is SmartReader iHead))
|
||||||
{
|
{
|
||||||
continue;//ignore different types of heads
|
continue;//ignore different types of heads
|
||||||
}
|
}
|
||||||
|
|
||||||
int position = iHead.Position;
|
|
||||||
if (position < 0 || position >= checkBoxes.Length || position >= messages.Length)
|
if (position < 0 || position >= checkBoxes.Length || position >= messages.Length)
|
||||||
{
|
{
|
||||||
continue; // Skip this head if position is out of range
|
continue; // Skip this head if position is out of range
|
||||||
@@ -617,4 +618,4 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TBF
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// User selection of smart register readers. The selection is deliberately
|
||||||
|
/// kept per reader family because reader positions are only meaningful inside
|
||||||
|
/// one configured family.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SmartReaderSelectionSettings
|
||||||
|
{
|
||||||
|
public bool LegacyOptoHeadsEnabledMigrated;
|
||||||
|
public List<SmartReaderFamilySelection> Families = new List<SmartReaderFamilySelection>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SmartReaderFamilySelection
|
||||||
|
{
|
||||||
|
public string FamilyKey;
|
||||||
|
public List<SmartReaderSelectionItem> Readers = new List<SmartReaderSelectionItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SmartReaderSelectionItem
|
||||||
|
{
|
||||||
|
/// <summary>Stable configured component name; not a visual row index.</summary>
|
||||||
|
public string ReaderKey;
|
||||||
|
public bool Enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1633,7 +1633,20 @@
|
|||||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyMeterSize.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyMeterSize.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalSample.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalSample.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderCfg.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderCfg.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderCfgCtrl.cs">
|
||||||
|
<SubType>UserControl</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderCfgCtrl.Designer.cs">
|
||||||
|
<DependentUpon>AllyReaderCfgCtrl.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderManualTestCtrl.cs">
|
||||||
|
<SubType>UserControl</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderManualTestCtrl.Designer.cs">
|
||||||
|
<DependentUpon>AllyReaderManualTestCtrl.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyCommandService.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyCommandService.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFactoryUnsealData.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrame.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrame.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrameBuilder.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrameBuilder.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrameParser.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrameParser.cs" />
|
||||||
@@ -1643,6 +1656,9 @@
|
|||||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyVersionInfo.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyVersionInfo.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\IAllyTransport.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\IAllyTransport.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\Factory.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\Factory.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\AsicReader\AsicReader.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\AsicReader\AsicReaderCfg.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\AsicReader\Factory.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\GenesisCfgCtrl.designer.cs">
|
<Compile Include="Rig\RegisterReaders\GenesisRegReader\GenesisCfgCtrl.designer.cs">
|
||||||
<DependentUpon>GenesisCfgCtrl.cs</DependentUpon>
|
<DependentUpon>GenesisCfgCtrl.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -1746,6 +1762,7 @@
|
|||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\JsonDataFromPoseidon.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\JsonDataFromPoseidon.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonCfg.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonCfg.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonProcParams.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonProcParams.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReadCycle.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReader.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReader.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\ProcessExtensions.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\ProcessExtensions.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\ScopedLoggerFactory.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\ScopedLoggerFactory.cs" />
|
||||||
@@ -1969,10 +1986,13 @@
|
|||||||
<Compile Include="Rig\TestMethods\FlyingStartMassCollection\Single\TestParams.cs" />
|
<Compile Include="Rig\TestMethods\FlyingStartMassCollection\Single\TestParams.cs" />
|
||||||
<Compile Include="Rig\TestMethods\AllyCalibration\AllyCalibrationActivity.cs" />
|
<Compile Include="Rig\TestMethods\AllyCalibration\AllyCalibrationActivity.cs" />
|
||||||
<Compile Include="Rig\TestMethods\AllyCalibration\AllyCalibrationSeq.cs" />
|
<Compile Include="Rig\TestMethods\AllyCalibration\AllyCalibrationSeq.cs" />
|
||||||
|
<Compile Include="Rig\TestMethods\AllyCalibration\AllyCommunicationSeq.cs" />
|
||||||
<Compile Include="Rig\TestMethods\AllyCalibration\Factory.cs" />
|
<Compile Include="Rig\TestMethods\AllyCalibration\Factory.cs" />
|
||||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethod.cs" />
|
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethod.cs" />
|
||||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodCfg.cs" />
|
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodCfg.cs" />
|
||||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodParams.cs" />
|
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodParams.cs" />
|
||||||
|
<Compile Include="Rig\TestMethods\AsicTest\Factory.cs" />
|
||||||
|
<Compile Include="Rig\TestMethods\AsicTest\TestMethod.cs" />
|
||||||
<Compile Include="Rig\TestMethods\GenesisCommunication\Factory.cs" />
|
<Compile Include="Rig\TestMethods\GenesisCommunication\Factory.cs" />
|
||||||
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisCommunicationSeq.cs" />
|
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisCommunicationSeq.cs" />
|
||||||
<Compile Include="Rig\TestMethods\GenesisCommunication\iPerlCommunicationParams.cs" />
|
<Compile Include="Rig\TestMethods\GenesisCommunication\iPerlCommunicationParams.cs" />
|
||||||
@@ -2558,7 +2578,13 @@
|
|||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\common\ICorrections.cs" />
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\common\ICorrections.cs" />
|
||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\common\ISmartReader.cs" />
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\common\ISmartReader.cs" />
|
||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\EnumExtensions.cs" />
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\EnumExtensions.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\ManualSmartCorrectionsBase.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\GenesisCorrections.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\AllyCorrections.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\AllyTestCorrections.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\AsicCorrections.cs" />
|
||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\IPerlASICCorrections.cs" />
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\IPerlASICCorrections.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\IPerlASICOpticalHeadTest.cs" />
|
||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\IPerlCorrections.cs" />
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\IPerlCorrections.cs" />
|
||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\PoseidonCorrections.cs" />
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\PoseidonCorrections.cs" />
|
||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\SmartComponentBase.cs" />
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\SmartComponentBase.cs" />
|
||||||
@@ -2567,6 +2593,7 @@
|
|||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\SmartCommunicationForm.cs">
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\SmartCommunicationForm.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\SmartReaderSelection.cs" />
|
||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\SmartCommunicationForm.designer.cs">
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\SmartCommunicationForm.designer.cs">
|
||||||
<DependentUpon>SmartCommunicationForm.cs</DependentUpon>
|
<DependentUpon>SmartCommunicationForm.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -2817,6 +2844,7 @@
|
|||||||
<Compile Include="Rig\State.cs" />
|
<Compile Include="Rig\State.cs" />
|
||||||
<Compile Include="Rig\Transition.cs" />
|
<Compile Include="Rig\Transition.cs" />
|
||||||
<Compile Include="LocalSettings.cs" />
|
<Compile Include="LocalSettings.cs" />
|
||||||
|
<Compile Include="SmartReaderSelectionSettings.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Rig\Generic\IDevice.cs" />
|
<Compile Include="Rig\Generic\IDevice.cs" />
|
||||||
@@ -3854,6 +3882,12 @@
|
|||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="Rig\RegisterReaders\iPerlASICReader\IperlASICUniHeadTestCtrl.resx" />
|
<EmbeddedResource Include="Rig\RegisterReaders\iPerlASICReader\IperlASICUniHeadTestCtrl.resx" />
|
||||||
<EmbeddedResource Include="Rig\RegisterReaders\iPerlASICReader\IPerlUniCfgCtrl.resx" />
|
<EmbeddedResource Include="Rig\RegisterReaders\iPerlASICReader\IPerlUniCfgCtrl.resx" />
|
||||||
|
<EmbeddedResource Include="Rig\RegisterReaders\AllyReader\AllyReaderCfgCtrl.resx">
|
||||||
|
<DependentUpon>AllyReaderCfgCtrl.cs</DependentUpon>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="Rig\RegisterReaders\AllyReader\AllyReaderManualTestCtrl.resx">
|
||||||
|
<DependentUpon>AllyReaderManualTestCtrl.cs</DependentUpon>
|
||||||
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="Rig\RegisterReaders\iPerlReaderUNI\IPerlUniCfgCtrl.resx">
|
<EmbeddedResource Include="Rig\RegisterReaders\iPerlReaderUNI\IPerlUniCfgCtrl.resx">
|
||||||
<DependentUpon>IPerlUniCfgCtrl.cs</DependentUpon>
|
<DependentUpon>IPerlUniCfgCtrl.cs</DependentUpon>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -4523,6 +4557,7 @@
|
|||||||
<Content Include="Rig\Modbus\TempControl\Pictures\TControl-XL-cooling.png" />
|
<Content Include="Rig\Modbus\TempControl\Pictures\TControl-XL-cooling.png" />
|
||||||
<Content Include="Rig\Modbus\TempControl\Pictures\TControl-XL-heating.png" />
|
<Content Include="Rig\Modbus\TempControl\Pictures\TControl-XL-heating.png" />
|
||||||
<Content Include="Rig\Modbus\TempControl\Pictures\TControl-XL-idle.png" />
|
<Content Include="Rig\Modbus\TempControl\Pictures\TControl-XL-idle.png" />
|
||||||
|
<Content Include="Rig\TestMethods\AllyCalibration\ALLY_OPTICAL_STREAM_SEQUENCE.md" />
|
||||||
<Content Include="SampleConfig\config.xml">
|
<Content Include="SampleConfig\config.xml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
|||||||
+1
-1
@@ -1199,7 +1199,7 @@ namespace TBF.UI
|
|||||||
|
|
||||||
private void optoHeadsToolStripMenuItem_Click(object sender, EventArgs e)
|
private void optoHeadsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
new iPerlCommunicationForm(true).ShowDialog();
|
new SmartCommunicationForm(true).ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void statusStrip1_DoubleClick(object sender, EventArgs e)
|
private void statusStrip1_DoubleClick(object sender, EventArgs e)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ using System.Collections.Generic;
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Results.Entities;
|
using Results.Entities;
|
||||||
|
using TBF;
|
||||||
using TBF.Rig.DataEntry.Uni;
|
using TBF.Rig.DataEntry.Uni;
|
||||||
using TBF.Rig.GenericDevices;
|
using TBF.Rig.GenericDevices;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
|
||||||
namespace TBFTests.Rig.DataEntry.Uni
|
namespace TBFTests.Rig.DataEntry.Uni
|
||||||
{
|
{
|
||||||
@@ -83,12 +85,42 @@ namespace TBFTests.Rig.DataEntry.Uni
|
|||||||
Assert.IsFalse(selected);
|
Assert.IsFalse(selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Mock<IRegReader> CreateReader(int position, bool smart = false)
|
[TestMethod]
|
||||||
|
public void SmartReader_PerFamilySelection_IsUsedInsteadOfLegacyPositionMask()
|
||||||
|
{
|
||||||
|
var reader = CreateReader(1, true, "iPerl01");
|
||||||
|
var settings = new LocalSettings
|
||||||
|
{
|
||||||
|
SmartReaderSelections = new SmartReaderSelectionSettings
|
||||||
|
{
|
||||||
|
LegacyOptoHeadsEnabledMigrated = true,
|
||||||
|
Families = new List<SmartReaderFamilySelection>
|
||||||
|
{
|
||||||
|
new SmartReaderFamilySelection
|
||||||
|
{
|
||||||
|
FamilyKey = "iperl",
|
||||||
|
Readers = new List<SmartReaderSelectionItem>
|
||||||
|
{
|
||||||
|
new SmartReaderSelectionItem { ReaderKey = "iPerl01", Enabled = false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool selected = RegisterReaderSelection.IsSelectedForDataEntry(
|
||||||
|
reader.Object, null, settings, long.MaxValue);
|
||||||
|
|
||||||
|
Assert.IsFalse(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Mock<IRegReader> CreateReader(int position, bool smart = false, string name = null)
|
||||||
{
|
{
|
||||||
var reader = new Mock<IRegReader>();
|
var reader = new Mock<IRegReader>();
|
||||||
if (smart)
|
if (smart)
|
||||||
reader.As<ISmartMeterReader>();
|
reader.As<ISmartMeterReader>();
|
||||||
reader.SetupGet(item => item.Position).Returns(position);
|
reader.SetupGet(item => item.Position).Returns(position);
|
||||||
|
reader.SetupGet(item => item.Name).Returns(name);
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
using Common;
|
using Common;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
@@ -97,24 +96,75 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void ProcessOpticalText_VolumeAndTimestampRollover_ProducesContinuousMeasurement()
|
public void ProcessOpticalText_32BitVolumeRollover_ProducesContinuousMeasurementUsingHostElapsedTime()
|
||||||
{
|
{
|
||||||
AllyMeterReader reader = CreateReader(AllyMeterSize.FiveEighths);
|
AllyMeterReader reader = CreateReader(AllyMeterSize.FiveEighths);
|
||||||
reader.Initialize();
|
reader.Initialize();
|
||||||
reader.Start();
|
reader.Start();
|
||||||
|
|
||||||
string input =
|
string input =
|
||||||
AllyOpticalTelegramFactory.Create(10, 0xFFFFF0, 0xFFFFFF00) +
|
AllyOpticalTelegramFactory.CreateC6(10, 0, 0, 10, 0xFFFFFFF0, 0, 0, 0, 0, 0, 0, 0) +
|
||||||
AllyOpticalTelegramFactory.Create(11, 0x000010, 0x00000100);
|
AllyOpticalTelegramFactory.CreateC6(11, 0, 0, 11, 0x00000010, 0, 0, 0, 0, 0, 0, 0);
|
||||||
InvokeProcessOpticalText(reader, input);
|
reader.ProcessOpticalTextForTest(
|
||||||
|
input,
|
||||||
|
new DateTime(2026, 9, 16, 8, 0, 0, DateTimeKind.Utc));
|
||||||
reader.Stop();
|
reader.Stop();
|
||||||
|
|
||||||
Assert.AreEqual(2, reader.OpticalSamples.Count);
|
Assert.AreEqual(2, reader.OpticalSamples.Count);
|
||||||
Assert.IsFalse(reader.NoSamples);
|
Assert.IsFalse(reader.NoSamples);
|
||||||
Assert.AreEqual(32D / 16000D, reader.WMVolume, 1E-9);
|
Assert.AreEqual(32D / 4000D, reader.WMVolume, 1E-9);
|
||||||
Assert.AreEqual(512D / 8192D,
|
// ALLY C6 does not carry an ASIC timestamp; elapsed time is based on the host receipt time.
|
||||||
reader.TimestampSecEnd - reader.TimestampSecStart, 1E-9);
|
Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart);
|
||||||
Assert.AreEqual(2, reader.WMPulses);
|
Assert.AreEqual(8, reader.WMPulses);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProcessOpticalText_AutoDetect_TransfersDecodedStartAndEndVolumes()
|
||||||
|
{
|
||||||
|
AllyMeterReader reader = CreateReader(AllyMeterSize.AutoDetect);
|
||||||
|
reader.Initialize();
|
||||||
|
reader.Start();
|
||||||
|
|
||||||
|
DateTime start = new DateTime(2026, 9, 15, 10, 40, 0, DateTimeKind.Utc);
|
||||||
|
reader.ProcessOpticalTextForTest(
|
||||||
|
AllyOpticalTelegramFactory.CreateC6(1, 0, 0, 8, 100U, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
start);
|
||||||
|
reader.ProcessOpticalTextForTest(
|
||||||
|
AllyOpticalTelegramFactory.CreateC6(2, 0, 0, 8, 140U, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
start.AddSeconds(3));
|
||||||
|
reader.Stop();
|
||||||
|
|
||||||
|
Assert.IsTrue(reader.IsOpticalVolumeConversionConfigured);
|
||||||
|
Assert.IsFalse(reader.NoSamples);
|
||||||
|
Assert.AreEqual(100D / 4000D, reader.BeginWMState, 1E-12);
|
||||||
|
Assert.AreEqual(140D / 4000D, reader.EndWMState, 1E-12);
|
||||||
|
Assert.AreEqual(40D / 4000D, reader.WMVolume, 1E-12);
|
||||||
|
Assert.AreEqual(3D, reader.TimestampSecEnd - reader.TimestampSecStart, 1E-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ProcessOpticalText_VolumeAndSequenceRollover_KeepMeasurementAndHostTimeContinuous()
|
||||||
|
{
|
||||||
|
AllyMeterReader reader = CreateReader(AllyMeterSize.AutoDetect);
|
||||||
|
reader.Initialize();
|
||||||
|
reader.Start();
|
||||||
|
|
||||||
|
DateTime beforeMidnight = new DateTime(2026, 12, 31, 23, 59, 59, 900, DateTimeKind.Utc);
|
||||||
|
reader.ProcessOpticalTextForTest(
|
||||||
|
AllyOpticalTelegramFactory.CreateC6(0xFF, 0, 0, 4, 0xFFFFFFFEU, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
beforeMidnight);
|
||||||
|
reader.ProcessOpticalTextForTest(
|
||||||
|
AllyOpticalTelegramFactory.CreateC6(0x00, 0, 0, 4, 0x00000002U, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
beforeMidnight.AddMilliseconds(200));
|
||||||
|
reader.Stop();
|
||||||
|
|
||||||
|
Assert.AreEqual((byte)0xFF, reader.OpticalSamples[0].Sequence);
|
||||||
|
Assert.AreEqual((byte)0x00, reader.OpticalSamples[1].Sequence);
|
||||||
|
// The delta is calculated from two ~1,073,741 l double values after
|
||||||
|
// 32-bit rollover. Allow binary floating-point cancellation far below
|
||||||
|
// a single C6 quarter-millilitre unit (0.00025 l).
|
||||||
|
Assert.AreEqual(4D / 4000D, reader.WMVolume, 1E-9);
|
||||||
|
Assert.AreEqual(0.2D, reader.TimestampSecEnd - reader.TimestampSecStart, 1E-9);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -172,14 +222,5 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InvokeProcessOpticalText(AllyMeterReader reader, string text)
|
|
||||||
{
|
|
||||||
MethodInfo method = typeof(AllyMeterReader).GetMethod(
|
|
||||||
"ProcessOpticalText",
|
|
||||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
||||||
|
|
||||||
Assert.IsNotNull(method, "ProcessOpticalText method was not found.");
|
|
||||||
method.Invoke(reader, new object[] { text });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Common;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.RegisterReaders.AllyReader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Regression coverage for C6 telegrams captured from an ALLY optical sensor.
|
||||||
|
/// The test output deliberately documents the decoded values and their units.
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
[TestSubject(typeof(AllyOpticalSample))]
|
||||||
|
public class AllyOpticalRealExamplesTest
|
||||||
|
{
|
||||||
|
public TestContext TestContext { get; set; }
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TryParse_RealC6Examples_ParsesValuesAndDocumentsUnits()
|
||||||
|
{
|
||||||
|
AllyOpticalSample first = Parse("17\tC6\taQcAABgQAABgEgAA8AAAALbo//8A6AMAAA==\t8513\r\n");
|
||||||
|
Assert.AreEqual((byte)0x17, first.Sequence);
|
||||||
|
Assert.AreEqual(1897, first.RawAdc);
|
||||||
|
Assert.AreEqual((short)4120, first.LastField);
|
||||||
|
Assert.AreEqual((short)0, first.RawFlow);
|
||||||
|
Assert.AreEqual(0D, first.FlowMillilitersPerSecond, 1E-12);
|
||||||
|
Assert.AreEqual(4704U, first.RawVolume);
|
||||||
|
Assert.AreEqual(1.176D, first.VolumeLiters, 1E-12);
|
||||||
|
Assert.AreEqual((ushort)240, first.FlipPeriod);
|
||||||
|
Assert.AreEqual((ushort)0, first.VinfStart);
|
||||||
|
Assert.AreEqual((ushort)59574, first.VinfEnd);
|
||||||
|
Assert.AreEqual((short)-1, first.ElectrodeDelta);
|
||||||
|
Assert.AreEqual((ushort)59392, first.Impedance);
|
||||||
|
Assert.AreEqual((byte)3, first.FieldDriveTime);
|
||||||
|
Assert.AreEqual((byte)0, first.Flags);
|
||||||
|
Assert.IsFalse(first.IsEmptyPipe);
|
||||||
|
Assert.IsFalse(first.IsFastHptc);
|
||||||
|
Assert.AreEqual(0x8513, first.PacketChecksum);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0 }, first.ExtensionBytes);
|
||||||
|
|
||||||
|
AllyOpticalSample second = Parse("13\tC6\tSAoAAJ4OAABOEgAA8AAAAMby//8A6AMAAA==\t4A31\r\n");
|
||||||
|
Assert.AreEqual((byte)0x13, second.Sequence);
|
||||||
|
Assert.AreEqual(2632, second.RawAdc);
|
||||||
|
Assert.AreEqual((short)3742, second.LastField);
|
||||||
|
Assert.AreEqual((short)0, second.RawFlow);
|
||||||
|
Assert.AreEqual(0D, second.FlowMillilitersPerSecond, 1E-12);
|
||||||
|
Assert.AreEqual(4686U, second.RawVolume);
|
||||||
|
Assert.AreEqual(1.1715D, second.VolumeLiters, 1E-12);
|
||||||
|
Assert.AreEqual((ushort)240, second.FlipPeriod);
|
||||||
|
Assert.AreEqual((ushort)62150, second.VinfEnd);
|
||||||
|
Assert.AreEqual((short)-1, second.ElectrodeDelta);
|
||||||
|
Assert.AreEqual((ushort)59392, second.Impedance);
|
||||||
|
Assert.AreEqual((byte)3, second.FieldDriveTime);
|
||||||
|
Assert.AreEqual((byte)0, second.Flags);
|
||||||
|
Assert.AreEqual(0x4A31, second.PacketChecksum);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0 }, second.ExtensionBytes);
|
||||||
|
|
||||||
|
WriteDecodedValues("example 1", first);
|
||||||
|
WriteDecodedValues("example 2", second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Decode_TenC6Samples_AcrossSequenceTimeAndVolumeRollover_ReportsStartEndAndDelta()
|
||||||
|
{
|
||||||
|
AllyReaderCfg cfg = new AllyReaderCfg(new TBF.Rig.RegisterReaders.AllyReader.Factory())
|
||||||
|
{
|
||||||
|
DebugLevel = DebugMode.Simulate,
|
||||||
|
ConfiguredMeterSize = AllyMeterSize.AutoDetect,
|
||||||
|
Name = "AllyRolloverTest"
|
||||||
|
};
|
||||||
|
AllyMeterReader reader = new AllyMeterReader(cfg);
|
||||||
|
reader.Initialize();
|
||||||
|
reader.Start();
|
||||||
|
|
||||||
|
// ALLY C6 has no device timestamp. The time rollover therefore means
|
||||||
|
// the host receipt time crossing midnight, while the 32-bit C6 volume
|
||||||
|
// accumulator wraps from FFFFFFFF to 00000000.
|
||||||
|
DateTime firstReceivedAt = new DateTime(2026, 12, 31, 23, 59, 57, 750, DateTimeKind.Utc);
|
||||||
|
const uint firstRawVolume = 0xFFFFFFF0U;
|
||||||
|
const uint rawIncrement = 4U; // one millilitre per sample
|
||||||
|
|
||||||
|
for (int index = 0; index < 10; index++)
|
||||||
|
{
|
||||||
|
uint rawVolume = unchecked(firstRawVolume + rawIncrement * (uint)index);
|
||||||
|
byte sequence = unchecked((byte)(0xF8 + index));
|
||||||
|
DateTime receivedAt = firstReceivedAt.AddMilliseconds(500 * index);
|
||||||
|
string telegram = AllyOpticalTelegramFactory.CreateC6(
|
||||||
|
sequence, 1000 + index, 0, 4, rawVolume, 240, 0, 0, -1, 59392, 3, 0);
|
||||||
|
|
||||||
|
reader.ProcessOpticalTextForTest(telegram, receivedAt);
|
||||||
|
AllyOpticalSample decoded = reader.OpticalSamples[index];
|
||||||
|
WriteDecodedValues("rollover sample " + (index + 1), decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Stop();
|
||||||
|
|
||||||
|
Assert.AreEqual(10, reader.OpticalSamples.Count);
|
||||||
|
Assert.AreEqual((byte)0xF8, reader.OpticalSamples[0].Sequence);
|
||||||
|
Assert.AreEqual((byte)0x01, reader.OpticalSamples[9].Sequence);
|
||||||
|
Assert.AreEqual(firstRawVolume, reader.OpticalSamples[0].RawVolume);
|
||||||
|
Assert.AreEqual(0x00000014U, reader.OpticalSamples[9].RawVolume);
|
||||||
|
Assert.AreEqual(9D * rawIncrement / 4000D, reader.WMVolume, 1E-9);
|
||||||
|
Assert.AreEqual(4.5D, reader.TimestampSecEnd - reader.TimestampSecStart, 1E-9);
|
||||||
|
Assert.IsTrue(reader.OpticalSamples[5].ReceivedAtUtc.Date > firstReceivedAt.Date);
|
||||||
|
|
||||||
|
if (TestContext != null)
|
||||||
|
{
|
||||||
|
TestContext.WriteLine(
|
||||||
|
"ALLY rollover result | samples={0}; start={1:F9} l; end={2:F9} l; delta={3:F9} l; elapsed={4:F3} s",
|
||||||
|
reader.OpticalSamples.Count,
|
||||||
|
reader.BeginWMState,
|
||||||
|
reader.EndWMState,
|
||||||
|
reader.WMVolume,
|
||||||
|
reader.TimestampSecEnd - reader.TimestampSecStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AllyOpticalSample Parse(string telegram)
|
||||||
|
{
|
||||||
|
AllyOpticalSample sample;
|
||||||
|
bool parsed = AllyOpticalSample.TryParse(
|
||||||
|
telegram,
|
||||||
|
new DateTime(2026, 9, 10, 8, 55, 52, DateTimeKind.Utc),
|
||||||
|
out sample);
|
||||||
|
Assert.IsTrue(parsed, "The captured C6 optical telegram must parse.");
|
||||||
|
Assert.IsNotNull(sample);
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteDecodedValues(string name, AllyOpticalSample sample)
|
||||||
|
{
|
||||||
|
string output = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"ALLY C6 {0} | sequence=0x{1:X2}; ADC={2} raw counts; field={3} raw counts; flow={4} quarter-mL/s ({5:F3} mL/s); accumulator={6} quarter-mL ({7:F6} l); flipPeriod={8} raw ticks; VinfStart={9} raw; VinfEnd={10} raw; electrodeDelta={11} mV; impedance={12} raw; fieldDriveTime={13} us; flags=0x{14:X2}; emptyPipe={15}; fastHptc={16}; checksum=0x{17:X4} (preserved, not CRC-validated); extension={18}",
|
||||||
|
name,
|
||||||
|
sample.Sequence,
|
||||||
|
sample.RawAdc,
|
||||||
|
sample.LastField,
|
||||||
|
sample.RawFlow,
|
||||||
|
sample.FlowMillilitersPerSecond,
|
||||||
|
sample.RawVolume,
|
||||||
|
sample.VolumeLiters,
|
||||||
|
sample.FlipPeriod,
|
||||||
|
sample.VinfStart,
|
||||||
|
sample.VinfEnd,
|
||||||
|
sample.ElectrodeDelta,
|
||||||
|
sample.Impedance,
|
||||||
|
sample.FieldDriveTime,
|
||||||
|
sample.Flags,
|
||||||
|
sample.IsEmptyPipe,
|
||||||
|
sample.IsFastHptc,
|
||||||
|
sample.PacketChecksum,
|
||||||
|
BitConverter.ToString(sample.ExtensionBytes));
|
||||||
|
|
||||||
|
if (TestContext != null)
|
||||||
|
TestContext.WriteLine(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,56 +10,59 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader
|
|||||||
public class AllyOpticalSampleTest
|
public class AllyOpticalSampleTest
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TryParse_ValidTelegram_ParsesFlowVolumeAndTimestamp()
|
public void TryParse_RealC6Telegram_ParsesAllyMetrologyFields()
|
||||||
{
|
{
|
||||||
string telegram = AllyOpticalTelegramFactory.Create(-2, 0x123456, 0x89ABCDEF);
|
const string telegram = "17\tC6\taQcAABgQAABgEgAA8AAAALbo//8A6AMAAA==\t8513\r\n";
|
||||||
DateTime receivedAt = new DateTime(2026, 8, 14, 10, 0, 0, DateTimeKind.Utc);
|
DateTime receivedAt = new DateTime(2026, 9, 10, 8, 55, 52, DateTimeKind.Utc);
|
||||||
|
|
||||||
AllyOpticalSample sample;
|
AllyOpticalSample sample;
|
||||||
bool result = AllyOpticalSample.TryParse(telegram, receivedAt, out sample);
|
bool result = AllyOpticalSample.TryParse(telegram, receivedAt, out sample);
|
||||||
|
|
||||||
Assert.IsTrue(result);
|
Assert.IsTrue(result);
|
||||||
Assert.IsNotNull(sample);
|
Assert.IsNotNull(sample);
|
||||||
Assert.AreEqual((short)-2, sample.RawFlow);
|
Assert.AreEqual((byte)0x17, sample.Sequence);
|
||||||
Assert.AreEqual(0x123456U, sample.RawVolume);
|
Assert.AreEqual((byte)0xC6, sample.PacketType);
|
||||||
Assert.AreEqual(0x89ABCDEFU, sample.RawTimestamp);
|
Assert.AreEqual(0x8513, sample.PacketChecksum);
|
||||||
|
Assert.AreEqual(1897, sample.RawAdc);
|
||||||
|
Assert.AreEqual((short)0x1018, sample.LastField);
|
||||||
|
Assert.AreEqual(0x1260U, sample.RawVolume);
|
||||||
|
Assert.AreEqual((ushort)0x00F0, sample.FlipPeriod);
|
||||||
|
Assert.AreEqual((short)-1, sample.ElectrodeDelta);
|
||||||
|
Assert.AreEqual(1, sample.ExtensionBytes.Length);
|
||||||
Assert.AreEqual(receivedAt, sample.ReceivedAtUtc);
|
Assert.AreEqual(receivedAt, sample.ReceivedAtUtc);
|
||||||
Assert.AreEqual(telegram, sample.RawLine);
|
Assert.AreEqual(telegram, sample.RawLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TryParse_PrefixedValidTelegram_UsesLastCompleteTelegram()
|
public void TryParse_C6Telegram_ParsesFlags()
|
||||||
{
|
{
|
||||||
string telegram = AllyOpticalTelegramFactory.Create(1, 2, 3);
|
string telegram = AllyOpticalTelegramFactory.CreateC6(1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0x07);
|
||||||
|
|
||||||
AllyOpticalSample sample;
|
AllyOpticalSample sample;
|
||||||
bool result = AllyOpticalSample.TryParse("noise" + telegram, DateTime.UtcNow, out sample);
|
bool result = AllyOpticalSample.TryParse(telegram, DateTime.UtcNow, out sample);
|
||||||
|
|
||||||
Assert.IsTrue(result);
|
Assert.IsTrue(result);
|
||||||
Assert.AreEqual(2U, sample.RawVolume);
|
Assert.IsTrue(sample.IsLowFlow);
|
||||||
Assert.AreEqual(3U, sample.RawTimestamp);
|
Assert.IsTrue(sample.IsEmptyPipe);
|
||||||
Assert.AreEqual(telegram, sample.RawLine);
|
Assert.IsTrue(sample.IsFastHptc);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TryParse_CorruptChecksum_ReturnsFalse()
|
public void TryParse_NonMetrologyPacket_ReturnsFalse()
|
||||||
{
|
{
|
||||||
string telegram = AllyOpticalTelegramFactory.Create(1, 2, 3);
|
|
||||||
string corrupt = (telegram[0] == '0' ? "1" : "0") + telegram.Substring(1);
|
|
||||||
|
|
||||||
AllyOpticalSample sample;
|
AllyOpticalSample sample;
|
||||||
bool result = AllyOpticalSample.TryParse(corrupt, DateTime.UtcNow, out sample);
|
bool result = AllyOpticalSample.TryParse("12\tC2\tOwIAAFAiAAAAAAAAACBYDgwOAACXylAS\t7947\r\n", DateTime.UtcNow, out sample);
|
||||||
|
|
||||||
Assert.IsFalse(result);
|
Assert.IsFalse(result);
|
||||||
Assert.IsNull(sample);
|
Assert.IsNull(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TryParse_InvalidStructure_ReturnsFalse()
|
public void TryParse_InvalidBase64_ReturnsFalse()
|
||||||
{
|
{
|
||||||
AllyOpticalSample sample;
|
AllyOpticalSample sample;
|
||||||
bool result = AllyOpticalSample.TryParse(
|
bool result = AllyOpticalSample.TryParse(
|
||||||
"000000 0000 0000 000001 0000 00000001 00\r\n",
|
"17\tC6\tinvalid\t8513\r\n",
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
out sample);
|
out sample);
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
using System.Globalization;
|
using System;
|
||||||
|
|
||||||
namespace TBFTests.Rig.RegisterReaders.AllyReader
|
namespace TBFTests.Rig.RegisterReaders.AllyReader
|
||||||
{
|
{
|
||||||
internal static class AllyOpticalTelegramFactory
|
internal static class AllyOpticalTelegramFactory
|
||||||
{
|
{
|
||||||
public static string Create(short rawFlow, uint rawVolume, uint rawTimestamp)
|
public static string CreateC6(byte sequence, int rawAdc, short lastField, short rawFlow, uint rawVolume,
|
||||||
|
ushort flipPeriod, ushort vinfStart, ushort vinfEnd, short electrodeDelta, ushort impedance,
|
||||||
|
byte fieldDriveTime, byte flags, byte extension = 0)
|
||||||
{
|
{
|
||||||
string prefix = string.Join("\t", new[]
|
byte[] payload = new byte[25];
|
||||||
{
|
Buffer.BlockCopy(BitConverter.GetBytes(rawAdc), 0, payload, 0, 4);
|
||||||
"000000",
|
Buffer.BlockCopy(BitConverter.GetBytes(lastField), 0, payload, 4, 2);
|
||||||
"0000",
|
Buffer.BlockCopy(BitConverter.GetBytes(rawFlow), 0, payload, 6, 2);
|
||||||
unchecked((ushort)rawFlow).ToString("X4", CultureInfo.InvariantCulture),
|
Buffer.BlockCopy(BitConverter.GetBytes(rawVolume), 0, payload, 8, 4);
|
||||||
rawVolume.ToString("X6", CultureInfo.InvariantCulture),
|
Buffer.BlockCopy(BitConverter.GetBytes(flipPeriod), 0, payload, 12, 2);
|
||||||
"0000",
|
Buffer.BlockCopy(BitConverter.GetBytes(vinfStart), 0, payload, 14, 2);
|
||||||
rawTimestamp.ToString("X8", CultureInfo.InvariantCulture),
|
Buffer.BlockCopy(BitConverter.GetBytes(vinfEnd), 0, payload, 16, 2);
|
||||||
string.Empty
|
Buffer.BlockCopy(BitConverter.GetBytes(electrodeDelta), 0, payload, 18, 2);
|
||||||
});
|
Buffer.BlockCopy(BitConverter.GetBytes(impedance), 0, payload, 20, 2);
|
||||||
|
payload[22] = fieldDriveTime;
|
||||||
byte checksum = 0;
|
payload[23] = flags;
|
||||||
for (int i = 0; i < prefix.Length; i++)
|
payload[24] = extension;
|
||||||
checksum += (byte)prefix[i];
|
return sequence.ToString("X2") + "\tC6\t" + Convert.ToBase64String(payload) + "\t0000\r\n";
|
||||||
|
|
||||||
return prefix + checksum.ToString("X2", CultureInfo.InvariantCulture) + "\r\n";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,93 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
|||||||
AssertRequest(0x53, 0x57, 0x07, 0xFD, 0x60, 0xC2, 0x0D);
|
AssertRequest(0x53, 0x57, 0x07, 0xFD, 0x60, 0xC2, 0x0D);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OpticalVerificationOutput_UnsealedMeter_UsesDocumentedStartAndStopSequence()
|
||||||
|
{
|
||||||
|
transport.QueueResponse(AllyResponseFactory.CreateSuccess(0x00));
|
||||||
|
|
||||||
|
service.StartOpticalVerificationOutput(TimeoutMs);
|
||||||
|
|
||||||
|
Assert.AreEqual(5, transport.Requests.Count);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x06, 0xFD, 0x63, 0x0D }, transport.Requests[0]);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x07, 0x1E, 0x00, 0x02, 0x0D }, transport.Requests[1]);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x08, 0xFD, 0x15, 0x06, 0x01, 0x0D }, transport.Requests[2]);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x06, 0x1A, 0x09, 0x0D }, transport.Requests[3]);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x07, 0xFD, 0x60, 0xC2, 0x0D }, transport.Requests[4]);
|
||||||
|
|
||||||
|
transport.Requests.Clear();
|
||||||
|
service.StopOpticalVerificationOutput(TimeoutMs);
|
||||||
|
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x07, 0xFD, 0x60, 0x00, 0x0D }, transport.Requests[0]);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x06, 0x1A, 0x02, 0x0D }, transport.Requests[1]);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x08, 0xFD, 0x15, 0x06, 0x00, 0x0D }, transport.Requests[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OpticalVerificationOutput_SealedMeter_StopsBeforeAnyStateChangingCommand()
|
||||||
|
{
|
||||||
|
transport.Response = AllyResponseFactory.CreateSuccess(0x01);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
service.StartOpticalVerificationOutput(TimeoutMs);
|
||||||
|
Assert.Fail("Expected the factory-seal guard to reject optical output activation.");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException exception)
|
||||||
|
{
|
||||||
|
StringAssert.Contains(exception.Message, "factory sealed");
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(1, transport.Requests.Count);
|
||||||
|
CollectionAssert.AreEqual(new byte[] { 0x53, 0x57, 0x06, 0xFD, 0x63, 0x0D }, transport.Requests[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void FactorySealCommands_ReadMeterSpecificInputsAndBuildProvidedVerificationBenchFrame()
|
||||||
|
{
|
||||||
|
transport.QueueResponse(AllyResponseFactory.CreateSuccess(0x01));
|
||||||
|
transport.QueueResponse(AllyResponseFactory.CreateAsciiSuccess("Customer Text 123456"));
|
||||||
|
transport.QueueResponse(AllyResponseFactory.CreateAsciiSuccess("RB252601C144"));
|
||||||
|
transport.QueueResponse(AllyResponseFactory.CreateAsciiSuccess("00001000"));
|
||||||
|
transport.QueueResponse(AllyResponseFactory.CreateSuccess(0xD0, 0xC5, 0x5D, 0x00));
|
||||||
|
|
||||||
|
AllyFactoryUnsealData data = service.ReadFactoryUnsealData(TimeoutMs);
|
||||||
|
|
||||||
|
Assert.IsTrue(data.IsSealed);
|
||||||
|
Assert.AreEqual("RB252601C144", data.FactoryId);
|
||||||
|
Assert.AreEqual("Customer Text 123456", data.ProgrammableText);
|
||||||
|
Assert.AreEqual("00001000", data.ReadingPreset);
|
||||||
|
Assert.AreEqual((uint)6145488, data.SecondsActive);
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new byte[] { 0xC5, 0x5D, 0xF1, 0xF8, 0x53, 0x45, 0x09, 0x00 },
|
||||||
|
data.Credential);
|
||||||
|
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new byte[] { 0x53, 0x57, 0x06, 0xFD, 0x63, 0x0D },
|
||||||
|
transport.Requests[0]);
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new byte[] { 0x53, 0x57, 0x05, 0x07, 0x0D },
|
||||||
|
transport.Requests[1]);
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new byte[] { 0x53, 0x57, 0x05, 0x01, 0x0D },
|
||||||
|
transport.Requests[2]);
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new byte[] { 0x53, 0x57, 0x05, 0x13, 0x0D },
|
||||||
|
transport.Requests[3]);
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new byte[] { 0x53, 0x57, 0x06, 0xFD, 0x3D, 0x0D },
|
||||||
|
transport.Requests[4]);
|
||||||
|
|
||||||
|
service.UnsealFactory(data, TimeoutMs);
|
||||||
|
AssertRequest(
|
||||||
|
0x53, 0x57, 0x0F, 0xFD, 0x64,
|
||||||
|
0x00, 0xC5, 0x5D, 0xF1, 0xF8, 0x53, 0x45, 0x09, 0x00,
|
||||||
|
0x0D);
|
||||||
|
|
||||||
|
service.SealFactory(TimeoutMs);
|
||||||
|
AssertRequest(0x53, 0x57, 0x07, 0xFD, 0x64, 0x01, 0x0D);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CalibrationFactor_WriteAndRead_UsesFactorTimes40Point96Encoding()
|
public void CalibrationFactor_WriteAndRead_UsesFactorTimes40Point96Encoding()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,25 +12,27 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Direct hardware smoke test modelled after IperlHatIntegrationTests.
|
/// Direct hardware smoke test modelled after IperlHatIntegrationTests.
|
||||||
/// COM3 is the ALLY Touch-Read connection and COM4 is the optical source.
|
/// COM12 is the ALLY Touch-Read connection and COM13 is the optical source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestClass]
|
[TestClass]
|
||||||
|
[TestCategory("HardwareIntegration")]
|
||||||
[DoNotParallelize]
|
[DoNotParallelize]
|
||||||
public class AllyIntegrationTests
|
public class AllyIntegrationTests
|
||||||
{
|
{
|
||||||
private const string CommandComPort = "COM3";
|
private const string CommandComPort = "COM12";
|
||||||
private const string OpticalComPort = "COM4";
|
private const string OpticalComPort = "COM13";
|
||||||
private const int CommandBaudRate = 2400;
|
private const int CommandBaudRate = 2400;
|
||||||
private const int OpticalBaudRate = 9600;
|
private const int OpticalBaudRate = 38400;
|
||||||
private const int ReadTimeoutMs = 5000;
|
private const int ReadTimeoutMs = 5000;
|
||||||
private const int OpticalReadSeconds = 10;
|
private const int OpticalReadSeconds = 10;
|
||||||
private const byte ActiveMeterMode = 0x02;
|
private const byte ActiveMeterMode = 0x02;
|
||||||
private const byte InitialMeterMode = 0x09;
|
private const byte InitialMeterMode = 0x09;
|
||||||
|
private const byte DiagnosticLedCalibrationMode = 0xC2;
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[TestCategory("Hardware")]
|
[TestCategory("Hardware")]
|
||||||
[TestCategory("Serial")]
|
[TestCategory("Serial")]
|
||||||
public void Serial_ReadFactoryId_SetActive_ReadOptical_SetInitial()
|
public void Integration_Serial_ReadFactoryId_PrepareOptical_ReadOptical_SetActive()
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
"ALLY integration test: command={0}/{1}, optical={2}/{3}",
|
"ALLY integration test: command={0}/{1}, optical={2}/{3}",
|
||||||
@@ -52,7 +54,9 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
|||||||
|
|
||||||
Console.WriteLine("OPEN command port " + CommandComPort);
|
Console.WriteLine("OPEN command port " + CommandComPort);
|
||||||
commandPort.Open();
|
commandPort.Open();
|
||||||
bool activeModeCommandAttempted = false;
|
bool initialModeCommandAttempted = false;
|
||||||
|
bool diagnosticLedCommandAttempted = false;
|
||||||
|
bool spreadSpectrumDisableAttempted = false;
|
||||||
Exception testFailure = null;
|
Exception testFailure = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -71,13 +75,38 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
|||||||
string.IsNullOrWhiteSpace(serialNumber),
|
string.IsNullOrWhiteSpace(serialNumber),
|
||||||
"ViewFactoryId returned an empty manufacturing serial number.");
|
"ViewFactoryId returned an empty manufacturing serial number.");
|
||||||
|
|
||||||
byte[] activeModeRequest = new AllyFrameBuilder()
|
SendRequest(
|
||||||
|
commandPort,
|
||||||
|
new AllyFrameBuilder()
|
||||||
|
.WithCommand(AllyCommand.SetValvePosition)
|
||||||
|
.WithBytes(0x00, 0x02)
|
||||||
|
.BuildBytes(),
|
||||||
|
"Open valve for calibration");
|
||||||
|
|
||||||
|
spreadSpectrumDisableAttempted = true;
|
||||||
|
SendRequest(
|
||||||
|
commandPort,
|
||||||
|
new AllyFrameBuilder()
|
||||||
|
.WithDeviceCommand(AllyDeviceCommand.Configuration)
|
||||||
|
.WithBytes(0x06, 0x01)
|
||||||
|
.BuildBytes(),
|
||||||
|
"Disable Spread Spectrum");
|
||||||
|
|
||||||
|
byte[] initialModeRequest = new AllyFrameBuilder()
|
||||||
.WithCommand(AllyCommand.SetMeterMode)
|
.WithCommand(AllyCommand.SetMeterMode)
|
||||||
.WithByte(ActiveMeterMode)
|
.WithByte(InitialMeterMode)
|
||||||
.BuildBytes();
|
.BuildBytes();
|
||||||
activeModeCommandAttempted = true;
|
initialModeCommandAttempted = true;
|
||||||
SendRequest(commandPort, activeModeRequest, "SetMeterMode Active 0x02");
|
SendRequest(commandPort, initialModeRequest, "SetMeterMode Initial calibration 0x09");
|
||||||
Console.WriteLine("STEP PASS Active meter mode 0x02 is acknowledged.");
|
|
||||||
|
diagnosticLedCommandAttempted = true;
|
||||||
|
SendRequest(
|
||||||
|
commandPort,
|
||||||
|
new AllyFrameBuilder()
|
||||||
|
.WithDeviceCommand(AllyDeviceCommand.SetDiagnosticLed)
|
||||||
|
.WithByte(DiagnosticLedCalibrationMode)
|
||||||
|
.BuildBytes(),
|
||||||
|
"Set diagnostic LED calibration 0xC2");
|
||||||
|
|
||||||
int parsedSamples = ReadOpticalSamples();
|
int parsedSamples = ReadOpticalSamples();
|
||||||
Assert.IsTrue(
|
Assert.IsTrue(
|
||||||
@@ -93,22 +122,44 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Attempt the safe state even when the active command timed out: the
|
if (commandPort.IsOpen)
|
||||||
// meter may have accepted it while its response was lost.
|
|
||||||
if (activeModeCommandAttempted && commandPort.IsOpen)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] initialModeRequest = new AllyFrameBuilder()
|
if (diagnosticLedCommandAttempted)
|
||||||
.WithCommand(AllyCommand.SetMeterMode)
|
{
|
||||||
.WithByte(InitialMeterMode)
|
SendRequest(
|
||||||
.BuildBytes();
|
commandPort,
|
||||||
SendRequest(commandPort, initialModeRequest, "SetMeterMode Initial 0x09");
|
new AllyFrameBuilder()
|
||||||
Console.WriteLine("STEP PASS Initial meter mode 0x09 is acknowledged.");
|
.WithDeviceCommand(AllyDeviceCommand.SetDiagnosticLed)
|
||||||
|
.WithByte(0x00)
|
||||||
|
.BuildBytes(),
|
||||||
|
"Set diagnostic LED off 0x00");
|
||||||
|
}
|
||||||
|
if (initialModeCommandAttempted)
|
||||||
|
{
|
||||||
|
SendRequest(
|
||||||
|
commandPort,
|
||||||
|
new AllyFrameBuilder()
|
||||||
|
.WithCommand(AllyCommand.SetMeterMode)
|
||||||
|
.WithByte(ActiveMeterMode)
|
||||||
|
.BuildBytes(),
|
||||||
|
"SetMeterMode Active 0x02");
|
||||||
|
}
|
||||||
|
if (spreadSpectrumDisableAttempted)
|
||||||
|
{
|
||||||
|
SendRequest(
|
||||||
|
commandPort,
|
||||||
|
new AllyFrameBuilder()
|
||||||
|
.WithDeviceCommand(AllyDeviceCommand.Configuration)
|
||||||
|
.WithBytes(0x06, 0x00)
|
||||||
|
.BuildBytes(),
|
||||||
|
"Enable Spread Spectrum");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception restoreException)
|
catch (Exception restoreException)
|
||||||
{
|
{
|
||||||
Console.WriteLine("RESTORE FAIL Initial mode 0x09: " + restoreException);
|
Console.WriteLine("RESTORE FAIL calibration cleanup: " + restoreException);
|
||||||
if (testFailure == null)
|
if (testFailure == null)
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -238,11 +289,14 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
|||||||
|
|
||||||
parsedSamples++;
|
parsedSamples++;
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
"OPTO PARSE PASS: sample={0}, flow={1}, rawVolume={2}, rawTimestamp={3}",
|
"OPTO PARSE PASS: sample={0}, type=0x{1:X2}, sequence=0x{2:X2}, flow={3}, rawVolume={4}, emptyPipe={5}, fastHptc={6}",
|
||||||
parsedSamples,
|
parsedSamples,
|
||||||
|
sample.PacketType,
|
||||||
|
sample.Sequence,
|
||||||
sample.RawFlow,
|
sample.RawFlow,
|
||||||
sample.RawVolume,
|
sample.RawVolume,
|
||||||
sample.RawTimestamp);
|
sample.IsEmptyPipe,
|
||||||
|
sample.IsFastHptc);
|
||||||
}
|
}
|
||||||
catch (TimeoutException)
|
catch (TimeoutException)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
||||||
{
|
{
|
||||||
@@ -11,6 +12,13 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
|||||||
public byte[] Response { get; set; }
|
public byte[] Response { get; set; }
|
||||||
public byte[] LastRequest { get; private set; }
|
public byte[] LastRequest { get; private set; }
|
||||||
public int LastTimeoutMs { get; private set; }
|
public int LastTimeoutMs { get; private set; }
|
||||||
|
public IList<byte[]> Requests { get; private set; } = new List<byte[]>();
|
||||||
|
private readonly Queue<byte[]> responses = new Queue<byte[]>();
|
||||||
|
|
||||||
|
public void QueueResponse(byte[] response)
|
||||||
|
{
|
||||||
|
responses.Enqueue(response);
|
||||||
|
}
|
||||||
|
|
||||||
public void Open()
|
public void Open()
|
||||||
{
|
{
|
||||||
@@ -23,7 +31,8 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
|||||||
SendAndWaitCallCount++;
|
SendAndWaitCallCount++;
|
||||||
LastRequest = request;
|
LastRequest = request;
|
||||||
LastTimeoutMs = timeoutMs;
|
LastTimeoutMs = timeoutMs;
|
||||||
return Response;
|
Requests.Add(request);
|
||||||
|
return responses.Count > 0 ? responses.Dequeue() : Response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
+5
-3
@@ -15,6 +15,7 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
public int OpticalCaptureSeconds { get; private set; }
|
public int OpticalCaptureSeconds { get; private set; }
|
||||||
public int ActiveModeSettleMs { get; private set; }
|
public int ActiveModeSettleMs { get; private set; }
|
||||||
public AllyMeterSize MeterSize { get; private set; }
|
public AllyMeterSize MeterSize { get; private set; }
|
||||||
|
public bool UnsealMeterBeforeOpticalTest { get; private set; }
|
||||||
|
|
||||||
public string CommandPortName { get { return "COM" + CommandPortNumber; } }
|
public string CommandPortName { get { return "COM" + CommandPortNumber; } }
|
||||||
public string OpticalPortName { get { return "COM" + OpticalPortNumber; } }
|
public string OpticalPortName { get { return "COM" + OpticalPortNumber; } }
|
||||||
@@ -24,13 +25,14 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
return new AllyHardwareIntegrationSettings
|
return new AllyHardwareIntegrationSettings
|
||||||
{
|
{
|
||||||
Enabled = ParseEnabled(Environment.GetEnvironmentVariable("ALLY_HW_TESTS")),
|
Enabled = ParseEnabled(Environment.GetEnvironmentVariable("ALLY_HW_TESTS")),
|
||||||
CommandPortNumber = ParsePort("ALLY_COMMAND_PORT", "COM3"),
|
CommandPortNumber = ParsePort("ALLY_COMMAND_PORT", "COM12"),
|
||||||
OpticalPortNumber = ParsePort("ALLY_OPTICAL_PORT", "COM4"),
|
OpticalPortNumber = ParsePort("ALLY_OPTICAL_PORT", "COM13"),
|
||||||
CommandBaudRate = ParsePositiveInt("ALLY_COMMAND_BAUD", 2400),
|
CommandBaudRate = ParsePositiveInt("ALLY_COMMAND_BAUD", 2400),
|
||||||
OpticalBaudRate = ParsePositiveInt("ALLY_OPTICAL_BAUD", 9600),
|
OpticalBaudRate = ParsePositiveInt("ALLY_OPTICAL_BAUD", 38400),
|
||||||
CommandTimeoutMs = ParsePositiveInt("ALLY_COMMAND_TIMEOUT_MS", 5000),
|
CommandTimeoutMs = ParsePositiveInt("ALLY_COMMAND_TIMEOUT_MS", 5000),
|
||||||
OpticalCaptureSeconds = ParsePositiveInt("ALLY_OPTICAL_CAPTURE_SECONDS", 10),
|
OpticalCaptureSeconds = ParsePositiveInt("ALLY_OPTICAL_CAPTURE_SECONDS", 10),
|
||||||
ActiveModeSettleMs = ParsePositiveInt("ALLY_ACTIVE_SETTLE_MS", 1000),
|
ActiveModeSettleMs = ParsePositiveInt("ALLY_ACTIVE_SETTLE_MS", 1000),
|
||||||
|
UnsealMeterBeforeOpticalTest = ParseEnabled(Environment.GetEnvironmentVariable("ALLY_UNSEAL_METER")),
|
||||||
MeterSize = ParseMeterSize(Environment.GetEnvironmentVariable("ALLY_METER_SIZE"))
|
MeterSize = ParseMeterSize(Environment.GetEnvironmentVariable("ALLY_METER_SIZE"))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+123
-143
@@ -4,7 +4,6 @@ using System.Diagnostics;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using TBF.Rig.RegisterReaders.AllyReader;
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
@@ -17,9 +16,6 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
[DoNotParallelize]
|
[DoNotParallelize]
|
||||||
public class AllyHardwareIntegrationTest
|
public class AllyHardwareIntegrationTest
|
||||||
{
|
{
|
||||||
private const byte ActiveMeterMode = 0x02;
|
|
||||||
private const byte InitialMeterMode = 0x09;
|
|
||||||
|
|
||||||
private AllyHardwareIntegrationSettings settings;
|
private AllyHardwareIntegrationSettings settings;
|
||||||
private AllyIntegrationTestLogger logger;
|
private AllyIntegrationTestLogger logger;
|
||||||
|
|
||||||
@@ -32,14 +28,15 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
logger = new AllyIntegrationTestLogger(TestContext);
|
logger = new AllyIntegrationTestLogger(TestContext);
|
||||||
logger.Log(string.Format(
|
logger.Log(string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"Configuration | command={0}/{1}, optical={2}/{3}, size={4}, timeout={5} ms, capture={6} s",
|
"Configuration | command={0}/{1}, optical={2}/{3}, size={4}, timeout={5} ms, capture={6} s, unseal={7}",
|
||||||
settings.CommandPortName,
|
settings.CommandPortName,
|
||||||
settings.CommandBaudRate,
|
settings.CommandBaudRate,
|
||||||
settings.OpticalPortName,
|
settings.OpticalPortName,
|
||||||
settings.OpticalBaudRate,
|
settings.OpticalBaudRate,
|
||||||
settings.MeterSize,
|
settings.MeterSize,
|
||||||
settings.CommandTimeoutMs,
|
settings.CommandTimeoutMs,
|
||||||
settings.OpticalCaptureSeconds));
|
settings.OpticalCaptureSeconds,
|
||||||
|
settings.UnsealMeterBeforeOpticalTest));
|
||||||
|
|
||||||
if (!settings.Enabled)
|
if (!settings.Enabled)
|
||||||
{
|
{
|
||||||
@@ -78,7 +75,10 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
value.TouchReadVersion,
|
value.TouchReadVersion,
|
||||||
value.DeviceType,
|
value.DeviceType,
|
||||||
value.FirmwareVersion));
|
value.FirmwareVersion));
|
||||||
Assert.AreEqual("SWM003", version.DeviceType, "The connected device is not an ALLY meter.");
|
Assert.AreEqual<string>(
|
||||||
|
"SWM003",
|
||||||
|
version.DeviceType,
|
||||||
|
"The connected device is not an ALLY meter.");
|
||||||
|
|
||||||
logger.Step("Read meter system time (UTC)", () => reader.ReadSystemTimeUtc(settings.CommandTimeoutMs),
|
logger.Step("Read meter system time (UTC)", () => reader.ReadSystemTimeUtc(settings.CommandTimeoutMs),
|
||||||
value => value.ToString("O", CultureInfo.InvariantCulture) + "; parse=PASS");
|
value => value.ToString("O", CultureInfo.InvariantCulture) + "; parse=PASS");
|
||||||
@@ -92,46 +92,8 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void CommandPort_ActiveModeRoundTrip_AlwaysRestoresInitialMode()
|
|
||||||
{
|
|
||||||
RequirePorts(settings.CommandPortName);
|
|
||||||
AllyMeterReader reader = CreateReader();
|
|
||||||
reader.StartSession();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
logger.Step(
|
|
||||||
"Set active meter mode 0x02",
|
|
||||||
() => reader.SetMeterMode(ActiveMeterMode, settings.CommandTimeoutMs));
|
|
||||||
logger.Log("WAIT | active-mode settling for " + settings.ActiveModeSettleMs + " ms");
|
|
||||||
Thread.Sleep(settings.ActiveModeSettleMs);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
logger.Step(
|
|
||||||
"Restore initial meter mode 0x09",
|
|
||||||
() => reader.SetMeterMode(InitialMeterMode, settings.CommandTimeoutMs));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
reader.EndSession();
|
|
||||||
logger.Log("SESSION | command port closed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void OpticalPort_CaptureAndParseTelegrams_LogsRawAndParseResult()
|
public void OpticalPort_CaptureAndParseTelegrams_LogsRawAndParseResult()
|
||||||
{
|
|
||||||
RequirePorts(settings.OpticalPortName);
|
|
||||||
IList<AllyOpticalSample> samples = CaptureOpticalTelegramsDirectly();
|
|
||||||
Assert.IsTrue(samples.Count > 0, "No valid ALLY optical telegram was parsed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Complex_ReadSerialActivateCaptureParseAndDeactivate_LogsWholeWorkflow()
|
|
||||||
{
|
{
|
||||||
RequirePorts(settings.CommandPortName, settings.OpticalPortName);
|
RequirePorts(settings.CommandPortName, settings.OpticalPortName);
|
||||||
AllyMeterReader reader = CreateReader();
|
AllyMeterReader reader = CreateReader();
|
||||||
@@ -139,21 +101,50 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
reader.StartSession();
|
reader.StartSession();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string serialNumber = logger.Step(
|
ReadSerialNumberAndFirmwareRevision(reader);
|
||||||
"Read manufacturing serial number",
|
EnsureMeterIsUnsealed(reader);
|
||||||
() => reader.ReadSerialNumber(settings.CommandTimeoutMs));
|
logger.Step("Start ALLY optical verification stream and open " + settings.OpticalPortName,
|
||||||
Assert.IsFalse(string.IsNullOrWhiteSpace(serialNumber), "The manufacturing serial number is empty.");
|
() => reader.StartOpticalVerificationStream(settings.CommandTimeoutMs));
|
||||||
|
|
||||||
logger.Step(
|
|
||||||
"Set active meter mode 0x02",
|
|
||||||
() => reader.SetMeterMode(ActiveMeterMode, settings.CommandTimeoutMs));
|
|
||||||
logger.Log("WAIT | active-mode settling for " + settings.ActiveModeSettleMs + " ms");
|
|
||||||
Thread.Sleep(settings.ActiveModeSettleMs);
|
|
||||||
|
|
||||||
logger.Step("Open optical stream and start measurement", reader.Start);
|
|
||||||
streamStarted = true;
|
streamStarted = true;
|
||||||
CaptureThroughReader(reader);
|
CaptureThroughReader(reader);
|
||||||
|
|
||||||
|
IReadOnlyList<AllyOpticalSample> samples = reader.OpticalSamples;
|
||||||
|
Assert.IsTrue(samples.Count > 0, "No valid ALLY optical telegram was parsed.");
|
||||||
|
logger.Log("OPTO CAPTURE | PASS | parsed samples=" + samples.Count);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (streamStarted)
|
||||||
|
logger.Step("Stop ALLY optical verification stream and close " + settings.OpticalPortName,
|
||||||
|
() => reader.StopOpticalVerificationStream(settings.CommandTimeoutMs));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
reader.EndSession();
|
||||||
|
logger.Log("SESSION | optical port closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Complex_ReadSerialCaptureParseAndStop_LogsWholeWorkflow()
|
||||||
|
{
|
||||||
|
RequirePorts(settings.CommandPortName, settings.OpticalPortName);
|
||||||
|
AllyMeterReader reader = CreateReader();
|
||||||
|
bool streamStarted = false;
|
||||||
|
reader.StartSession();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string serialNumber = ReadSerialNumberAndFirmwareRevision(reader);
|
||||||
|
EnsureMeterIsUnsealed(reader);
|
||||||
|
logger.Step("Start ALLY optical verification stream and open " + settings.OpticalPortName,
|
||||||
|
() => reader.StartOpticalVerificationStream(settings.CommandTimeoutMs));
|
||||||
|
streamStarted = true;
|
||||||
|
logger.Step("Start optical measurement interval", reader.Start);
|
||||||
|
CaptureThroughReader(reader);
|
||||||
|
|
||||||
IReadOnlyList<AllyOpticalSample> samples = reader.OpticalSamples;
|
IReadOnlyList<AllyOpticalSample> samples = reader.OpticalSamples;
|
||||||
Assert.IsTrue(samples.Count >= 2, "At least two valid optical samples are required for a measurement.");
|
Assert.IsTrue(samples.Count >= 2, "At least two valid optical samples are required for a measurement.");
|
||||||
Assert.IsFalse(reader.NoSamples, "The reader did not establish a valid optical measurement interval.");
|
Assert.IsFalse(reader.NoSamples, "The reader did not establish a valid optical measurement interval.");
|
||||||
@@ -170,21 +161,13 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (streamStarted)
|
if (streamStarted)
|
||||||
logger.Step("Stop optical measurement and close COM4", reader.Stop);
|
logger.Step("Stop ALLY optical verification stream and close " + settings.OpticalPortName,
|
||||||
|
() => reader.StopOpticalVerificationStream(settings.CommandTimeoutMs));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
try
|
reader.EndSession();
|
||||||
{
|
logger.Log("SESSION | all ports closed");
|
||||||
logger.Step(
|
|
||||||
"Restore initial meter mode 0x09",
|
|
||||||
() => reader.SetMeterMode(InitialMeterMode, settings.CommandTimeoutMs));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
reader.EndSession();
|
|
||||||
logger.Log("SESSION | all ports closed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,69 +179,62 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IList<AllyOpticalSample> CaptureOpticalTelegramsDirectly()
|
private string ReadSerialNumberAndFirmwareRevision(AllyMeterReader reader)
|
||||||
{
|
{
|
||||||
List<AllyOpticalSample> samples = new List<AllyOpticalSample>();
|
string serialNumber = logger.Step(
|
||||||
StringBuilder buffer = new StringBuilder();
|
"Read manufacturing serial number",
|
||||||
int parsedLines = 0;
|
() => reader.ReadSerialNumber(settings.CommandTimeoutMs));
|
||||||
int rejectedLines = 0;
|
Assert.IsFalse(string.IsNullOrWhiteSpace(serialNumber), "The manufacturing serial number is empty.");
|
||||||
|
|
||||||
using (SerialPort port = new SerialPort(
|
logger.Step(
|
||||||
settings.OpticalPortName,
|
"Read firmware revision",
|
||||||
settings.OpticalBaudRate,
|
() => reader.ReadVersionAndType(settings.CommandTimeoutMs),
|
||||||
Parity.None,
|
value => string.Format(
|
||||||
8,
|
CultureInfo.InvariantCulture,
|
||||||
StopBits.One))
|
"raw={0}; type={1}; firmware={2}; parse=PASS",
|
||||||
|
value.RawValue,
|
||||||
|
value.DeviceType,
|
||||||
|
value.FirmwareVersion));
|
||||||
|
return serialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureMeterIsUnsealed(AllyMeterReader reader)
|
||||||
|
{
|
||||||
|
bool isSealed = logger.Step("View factory seal", () => reader.IsFactorySealed(settings.CommandTimeoutMs),
|
||||||
|
value => value ? "sealed" : "unsealed");
|
||||||
|
if (!isSealed)
|
||||||
{
|
{
|
||||||
logger.Step("Open optical source " + settings.OpticalPortName, port.Open);
|
logger.Log("FACTORY SEAL | meter is already unsealed.");
|
||||||
try
|
return;
|
||||||
{
|
|
||||||
port.DiscardInBuffer();
|
|
||||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
|
||||||
while (stopwatch.Elapsed < TimeSpan.FromSeconds(settings.OpticalCaptureSeconds))
|
|
||||||
{
|
|
||||||
string text = port.ReadExisting();
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
|
||||||
buffer.Append(text);
|
|
||||||
|
|
||||||
string line;
|
|
||||||
while (TryTakeLine(buffer, out line))
|
|
||||||
{
|
|
||||||
AllyOpticalSample sample;
|
|
||||||
bool parsed = AllyOpticalSample.TryParse(line, DateTime.UtcNow, out sample);
|
|
||||||
logger.OpticalParse(line, parsed, sample);
|
|
||||||
if (parsed)
|
|
||||||
{
|
|
||||||
parsedLines++;
|
|
||||||
samples.Add(sample);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rejectedLines++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Thread.Sleep(50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
port.Close();
|
|
||||||
logger.Log("PORT | optical source closed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer.Length > 0)
|
if (!settings.UnsealMeterBeforeOpticalTest)
|
||||||
{
|
{
|
||||||
logger.Log("OPTO PARTIAL | trailing incomplete data: " + buffer.ToString()
|
const string message =
|
||||||
.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"));
|
"ALLY meter is factory sealed. Set ALLY_UNSEAL_METER=1 to explicitly authorize " +
|
||||||
|
"the integration test to unseal this meter, then rerun the test.";
|
||||||
|
logger.Log("BLOCKED | " + message);
|
||||||
|
Assert.Inconclusive(message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
logger.Log(string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
AllyFactoryUnsealData data = logger.Step(
|
||||||
"OPTO SUMMARY | valid={0}, rejected={1}, partialChars={2}",
|
"Read ALLY factory-unseal inputs",
|
||||||
parsedLines,
|
() => reader.ReadFactoryUnsealData(settings.CommandTimeoutMs),
|
||||||
rejectedLines,
|
value => string.Format(
|
||||||
buffer.Length));
|
CultureInfo.InvariantCulture,
|
||||||
return samples;
|
"factoryId={0}; programmableText={1}; readingPreset={2}; secondsActive={3}; credential=<redacted>",
|
||||||
|
value.FactoryId,
|
||||||
|
value.ProgrammableText,
|
||||||
|
value.ReadingPreset,
|
||||||
|
value.SecondsActive));
|
||||||
|
|
||||||
|
logger.Step("Unseal ALLY meter", () => reader.UnsealFactory(data, settings.CommandTimeoutMs));
|
||||||
|
bool isSealedAfterUnseal = logger.Step("Verify factory seal after unseal",
|
||||||
|
() => reader.IsFactorySealed(settings.CommandTimeoutMs),
|
||||||
|
value => value ? "sealed" : "unsealed");
|
||||||
|
Assert.IsFalse(isSealedAfterUnseal, "ALLY unseal command completed but the meter still reports sealed.");
|
||||||
|
logger.Log("FACTORY SEAL | unseal completed and verified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CaptureThroughReader(AllyMeterReader reader)
|
private void CaptureThroughReader(AllyMeterReader reader)
|
||||||
@@ -275,7 +251,10 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
{
|
{
|
||||||
AllyOpticalSample parsedSample;
|
AllyOpticalSample parsedSample;
|
||||||
bool parsed = AllyOpticalSample.TryParse(rawLine, DateTime.UtcNow, out parsedSample);
|
bool parsed = AllyOpticalSample.TryParse(rawLine, DateTime.UtcNow, out parsedSample);
|
||||||
logger.OpticalParse(rawLine, parsed, parsedSample);
|
if (parsed || AllyOpticalSample.IsMetrologyPacket(rawLine))
|
||||||
|
logger.OpticalParse(rawLine, parsed, parsedSample);
|
||||||
|
else
|
||||||
|
logger.Log("OPTO IGNORE | non-metrology telegram | " + rawLine.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"));
|
||||||
lastRawLine = rawLine;
|
lastRawLine = rawLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,13 +264,28 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
AllyOpticalSample sample = currentSamples[loggedSampleCount++];
|
AllyOpticalSample sample = currentSamples[loggedSampleCount++];
|
||||||
logger.Log(string.Format(
|
logger.Log(string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"OPTO SAMPLE | index={0}, flow={1}, rawVolume={2}, rawTimestamp={3}, liters={4:R}, seconds={5:R}",
|
"OPTO SAMPLE | index={0}; type=0x{1:X2}; sequence=0x{2:X2}; ADC={3} raw counts; field={4} raw counts; flow={5} quarter-mL/s ({6:F3} mL/s); accumulator={7} quarter-mL ({8:F6} l); resultVolume={9:R} l; elapsed={10:F3} s; flipPeriod={11} raw ticks; VinfStart={12} raw; VinfEnd={13} raw; electrodeDelta={14} mV; impedance={15} raw; fieldDriveTime={16} us; flags=0x{17:X2}; emptyPipe={18}; fastHptc={19}; checksum=0x{20:X4} (not CRC-validated)",
|
||||||
loggedSampleCount,
|
loggedSampleCount,
|
||||||
|
sample.PacketType,
|
||||||
|
sample.Sequence,
|
||||||
|
sample.RawAdc,
|
||||||
|
sample.LastField,
|
||||||
sample.RawFlow,
|
sample.RawFlow,
|
||||||
|
sample.FlowMillilitersPerSecond,
|
||||||
sample.RawVolume,
|
sample.RawVolume,
|
||||||
sample.RawTimestamp,
|
sample.VolumeLiters,
|
||||||
sample.ExtendedVolumeLiters,
|
sample.ExtendedVolumeLiters,
|
||||||
sample.ElapsedSeconds));
|
sample.ElapsedSeconds,
|
||||||
|
sample.FlipPeriod,
|
||||||
|
sample.VinfStart,
|
||||||
|
sample.VinfEnd,
|
||||||
|
sample.ElectrodeDelta,
|
||||||
|
sample.Impedance,
|
||||||
|
sample.FieldDriveTime,
|
||||||
|
sample.Flags,
|
||||||
|
sample.IsEmptyPipe,
|
||||||
|
sample.IsFastHptc,
|
||||||
|
sample.PacketChecksum));
|
||||||
}
|
}
|
||||||
Thread.Sleep(50);
|
Thread.Sleep(50);
|
||||||
}
|
}
|
||||||
@@ -313,19 +307,5 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
Assert.Inconclusive(message);
|
Assert.Inconclusive(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryTakeLine(StringBuilder buffer, out string line)
|
|
||||||
{
|
|
||||||
string text = buffer.ToString();
|
|
||||||
int lineEnd = text.IndexOf('\n');
|
|
||||||
if (lineEnd < 0)
|
|
||||||
{
|
|
||||||
line = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
line = text.Substring(0, lineEnd + 1);
|
|
||||||
buffer.Remove(0, lineEnd + 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,25 @@ namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log(string.Format( CultureInfo.InvariantCulture, "OPTO PARSE | PASS | flow={0}, volume=0x{1:X6} ({1}), timestamp=0x{2:X8} ({2})",
|
Log(string.Format( CultureInfo.InvariantCulture, "OPTO PARSE | PASS | type=0x{0:X2}, sequence=0x{1:X2}; ADC={2} raw counts; field={3} raw counts; flow={4} quarter-mL/s ({5:F3} mL/s); accumulator={6} quarter-mL ({7:F6} l); flipPeriod={8} raw ticks; VinfStart={9} raw; VinfEnd={10} raw; electrodeDelta={11} mV; impedance={12} raw; fieldDriveTime={13} us; flags=0x{14:X2}; emptyPipe={15}; fastHptc={16}; checksum=0x{17:X4} (not CRC-validated)",
|
||||||
|
sample.PacketType,
|
||||||
|
sample.Sequence,
|
||||||
|
sample.RawAdc,
|
||||||
|
sample.LastField,
|
||||||
sample.RawFlow,
|
sample.RawFlow,
|
||||||
|
sample.FlowMillilitersPerSecond,
|
||||||
sample.RawVolume,
|
sample.RawVolume,
|
||||||
sample.RawTimestamp));
|
sample.VolumeLiters,
|
||||||
|
sample.FlipPeriod,
|
||||||
|
sample.VinfStart,
|
||||||
|
sample.VinfEnd,
|
||||||
|
sample.ElectrodeDelta,
|
||||||
|
sample.Impedance,
|
||||||
|
sample.FieldDriveTime,
|
||||||
|
sample.Flags,
|
||||||
|
sample.IsEmptyPipe,
|
||||||
|
sample.IsFastHptc,
|
||||||
|
sample.PacketChecksum));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.RegisterReaders.AsicReader;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.RegisterReaders.AsicReader
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class AsicCorrectionsTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Adapter_IdentifiesOnlyExtractedAsicReaderFamily()
|
||||||
|
{
|
||||||
|
AsicCorrections corrections = new AsicCorrections(null);
|
||||||
|
ISmartReader asicReader = new TBF.Rig.RegisterReaders.AsicReader.AsicReader(new Factory().DefaultConfig());
|
||||||
|
|
||||||
|
Assert.AreEqual("ASIC", corrections.TypeIdentificatorName());
|
||||||
|
Assert.IsTrue(corrections.IsFamilyOfSmartReader(asicReader));
|
||||||
|
Assert.AreEqual("asic", SmartReaderSelection.GetFamilyKey(asicReader));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void UnsupportedActivity_FailsExplicitlyWithoutSendingAnyCommand()
|
||||||
|
{
|
||||||
|
AsicCorrections corrections = new AsicCorrections(null);
|
||||||
|
ISmartReader asicReader = new TBF.Rig.RegisterReaders.AsicReader.AsicReader(new Factory().DefaultConfig());
|
||||||
|
CommErr error = CommErr.None;
|
||||||
|
string result = null;
|
||||||
|
|
||||||
|
bool passed = corrections.WorkerActivity("Unsupported ASIC activity", asicReader, null, null, 0,
|
||||||
|
ref error, ref result, new[] { true }, 0, 0);
|
||||||
|
|
||||||
|
Assert.IsFalse(passed);
|
||||||
|
Assert.AreEqual(CommErr.WrongArguments, error);
|
||||||
|
StringAssert.Contains(result, "not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ManualAdapter_ExposesAllSupportedAsicCommands()
|
||||||
|
{
|
||||||
|
AsicCorrections corrections = new AsicCorrections(null);
|
||||||
|
|
||||||
|
System.Windows.Forms.ContextMenu menu = corrections.GetContextMenu();
|
||||||
|
Assert.AreEqual(9, menu.MenuItems.Count);
|
||||||
|
Assert.AreEqual("Read PCB number", menu.MenuItems[0].Text);
|
||||||
|
Assert.AreEqual(iPerlCommunicationConstants.ReadAdditionalCommonParametersStr, menu.MenuItems[2].Text);
|
||||||
|
Assert.AreEqual(iPerlCommunicationConstants.SetFlipModeConstantStr, menu.MenuItems[3].Text);
|
||||||
|
Assert.AreEqual(iPerlCommunicationConstants.SetFlipModeRandomizedStr, menu.MenuItems[4].Text);
|
||||||
|
Assert.AreEqual("Stop optical stream", menu.MenuItems[8].Text);
|
||||||
|
Assert.AreEqual(0, corrections.GetAllThreads().Count);
|
||||||
|
Assert.IsFalse(corrections.GetStopWorkerThreads());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OpticalStreamLifecycle_StartsOnlyAfterSupportedTestMode()
|
||||||
|
{
|
||||||
|
Assert.IsTrue(AsicCorrections.ShouldStartDataStreamForActivity("Set Test mode"));
|
||||||
|
Assert.IsTrue(AsicCorrections.ShouldStartDataStreamForActivity("Set Test mode Optho 7"));
|
||||||
|
Assert.IsFalse(AsicCorrections.ShouldStartDataStreamForActivity("Set Test mode 80"));
|
||||||
|
Assert.IsFalse(AsicCorrections.ShouldStartDataStreamForActivity("Set Active mode"));
|
||||||
|
Assert.IsFalse(AsicCorrections.ShouldStartDataStreamForActivity(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using Config.Entities;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Reflection;
|
||||||
|
using TBF.Rig;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.RegisterReaders.AsicReader;
|
||||||
|
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using AsicReaderComponent = TBF.Rig.RegisterReaders.AsicReader.AsicReader;
|
||||||
|
using Factory = TBF.Rig.RegisterReaders.AsicReader.Factory;
|
||||||
|
using AsicTestFactory = TBF.Rig.TestMethods.AsicTest.Factory;
|
||||||
|
using AsicTestMethod = TBF.Rig.TestMethods.AsicTest.TestMethod;
|
||||||
|
using LegacyTestMethod = TBF.Rig.TestMethods.iPerlCommunication.TestMethod;
|
||||||
|
using LegacyTestMethodCfg = TBF.Rig.TestMethods.iPerlCommunication.TestMethodCfg;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.RegisterReaders.AsicReader
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class AsicReaderFactoryTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Factory_CreatesAsicReaderWithLegacyIperlConfiguration()
|
||||||
|
{
|
||||||
|
Factory factory = new Factory();
|
||||||
|
IComponentCfg cfg = factory.DefaultConfig();
|
||||||
|
|
||||||
|
IComponent component = factory.GetComponent(cfg, null);
|
||||||
|
|
||||||
|
Assert.AreEqual("RegisterReaders.AsicReader", factory.ClassName);
|
||||||
|
Assert.IsInstanceOfType(cfg, typeof(AsicReaderCfg));
|
||||||
|
Assert.IsInstanceOfType(cfg, typeof(IperlHeadCfg));
|
||||||
|
Assert.IsInstanceOfType(component, typeof(AsicReaderComponent));
|
||||||
|
Assert.IsInstanceOfType(component, typeof(IperlHead));
|
||||||
|
Assert.IsInstanceOfType(component, typeof(ISmartReader));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Factory_ReadsBothAsicAndLegacyIperlConfigurationXml()
|
||||||
|
{
|
||||||
|
Factory factory = new Factory();
|
||||||
|
|
||||||
|
Component asicEntity = new AsicReaderCfg(factory).CreateDbEntity();
|
||||||
|
IComponentCfg asicCfg = factory.CmpntCfgFromCmpntEntity(asicEntity);
|
||||||
|
|
||||||
|
Component legacyEntity = new IperlHeadCfg(factory).CreateDbEntity();
|
||||||
|
IComponentCfg legacyCfg = factory.CmpntCfgFromCmpntEntity(legacyEntity);
|
||||||
|
|
||||||
|
Assert.IsInstanceOfType(asicCfg, typeof(AsicReaderCfg));
|
||||||
|
Assert.IsInstanceOfType(legacyCfg, typeof(IperlHeadCfg));
|
||||||
|
Assert.IsInstanceOfType(factory.GetComponent(asicCfg, null), typeof(AsicReaderComponent));
|
||||||
|
Assert.IsInstanceOfType(factory.GetComponent(legacyCfg, null), typeof(AsicReaderComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ReaderStateAndFamilyMapping_PreserveAsicSpecificState()
|
||||||
|
{
|
||||||
|
AsicReaderComponent component = new AsicReaderComponent(new Factory().DefaultConfig());
|
||||||
|
|
||||||
|
component.BeginWMState = 12.345;
|
||||||
|
component.EndWMState = 67.89;
|
||||||
|
component.Disabled = true;
|
||||||
|
component.CommFailed = true;
|
||||||
|
|
||||||
|
Assert.AreEqual(12.345, component.BeginWMState, 0.000001);
|
||||||
|
Assert.AreEqual(67.89, component.EndWMState, 0.000001);
|
||||||
|
Assert.IsTrue(component.Disabled);
|
||||||
|
Assert.IsTrue(component.CommFailed);
|
||||||
|
Assert.AreEqual("asic", SmartReaderSelection.GetFamilyKey(component));
|
||||||
|
Assert.IsFalse(string.IsNullOrEmpty(component.CommInterface.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void AsicTestFactory_CreatesExplicitAsicTestWithLegacyConfiguration()
|
||||||
|
{
|
||||||
|
AsicTestFactory factory = new AsicTestFactory();
|
||||||
|
IComponentCfg cfg = factory.DefaultConfig();
|
||||||
|
IComponent component = factory.GetComponent(cfg, null);
|
||||||
|
|
||||||
|
Assert.AreEqual("TestMethods.AsicTest", factory.ClassName);
|
||||||
|
Assert.IsInstanceOfType(cfg, typeof(LegacyTestMethodCfg));
|
||||||
|
Assert.IsInstanceOfType(component, typeof(AsicTestMethod));
|
||||||
|
Assert.IsInstanceOfType(component, typeof(LegacyTestMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void AsicTest_OverridesLegacyExecuteToUseSmartCommunicationRoute()
|
||||||
|
{
|
||||||
|
MethodInfo execute = typeof(AsicTestMethod).GetMethod("Execute");
|
||||||
|
|
||||||
|
Assert.IsNotNull(execute);
|
||||||
|
Assert.AreEqual(typeof(AsicTestMethod), execute.DeclaringType);
|
||||||
|
Assert.IsTrue(execute.GetBaseDefinition().DeclaringType == typeof(LegacyTestMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Factories_ResolveExplicitAsicReaderWithoutChangingLegacyIperlAlias()
|
||||||
|
{
|
||||||
|
Assert.IsInstanceOfType(TbfComponents.CmpntFactoryFromClassName("RegisterReaders.AsicReader"), typeof(Factory));
|
||||||
|
Assert.IsInstanceOfType(TbfComponents.CmpntFactoryFromClassName("TestMethods.AsicTest"), typeof(AsicTestFactory));
|
||||||
|
Assert.IsNotNull(TbfComponents.CmpntFactoryFromClassName("RegisterReader for iPerl"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||||
@@ -14,13 +15,30 @@ using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
|
|||||||
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.protocol
|
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.protocol
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class CordonelProtocolTests
|
[TestCategory("HardwareIntegration")]
|
||||||
|
public class CordonelProtocolIntegrationTests
|
||||||
{
|
{
|
||||||
private const string ComPort = "COM3"; // CHANGE THIS
|
private const string ComPort = "COM3"; // CHANGE THIS
|
||||||
private const int BaudRate = 9600;
|
private const int BaudRate = 9600;
|
||||||
private const int BaudRateOpto = 38400;
|
private const int BaudRateOpto = 38400;
|
||||||
private const int ReadTimeoutMs = 2000;
|
private const int ReadTimeoutMs = 2000;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void RequireExplicitHardwareConfiguration()
|
||||||
|
{
|
||||||
|
if (!string.Equals(Environment.GetEnvironmentVariable("GENESIS_HW_TESTS"), "1",
|
||||||
|
StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
Assert.Inconclusive("Set GENESIS_HW_TESTS=1 to run Genesis serial hardware integration tests.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SerialPort.GetPortNames().Any(port =>
|
||||||
|
string.Equals(port, ComPort, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
Assert.Inconclusive($"Genesis hardware integration requires configured port {ComPort}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//...MF
|
//...MF
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -590,4 +608,4 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.protocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,5 +245,34 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
"Invalid JSON should not produce a valid DeviceId.");
|
"Invalid JSON should not produce a valid DeviceId.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SerialPortData_ZeroMeterType_UsesPoseidonCliDefault()
|
||||||
|
{
|
||||||
|
var serialPort = new SerialPortData("COM3", "HatCliDemo.exe", 0);
|
||||||
|
|
||||||
|
Assert.AreEqual(SerialPortData.DefaultPoseidonMeterType, serialPort.MeterType);
|
||||||
|
Assert.AreEqual(
|
||||||
|
"-p COM3 -m 74 --operation readall",
|
||||||
|
serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task CliProcess_ExitErrorWithoutJson_RecordsDiagnosticsAndReturnsNoReading()
|
||||||
|
{
|
||||||
|
string cmdExe = 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);
|
||||||
|
Assert.AreEqual(17, info.ExitCode);
|
||||||
|
StringAssert.Contains(info.FailureReason, "exit code 17");
|
||||||
|
StringAssert.Contains(info.StandardError, "log4net");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||||
|
using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Regression fixtures extracted from PT50 customer CliRunner responses.
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public class PoseidonCustomerCliResponseTest
|
||||||
|
{
|
||||||
|
// Independently calculated fixtures can differ by insignificant
|
||||||
|
// IEEE-754 rounding during gallons-to-litres conversion.
|
||||||
|
private const double DialogValueToleranceLitres = 0.0001;
|
||||||
|
|
||||||
|
private const string Com43InitialResponse =
|
||||||
|
"{\"Reading\":\"002780.99\",\"DeviceId\":\"1000000219\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
|
||||||
|
private const string Com45InitialResponse =
|
||||||
|
"{\"Reading\":\"002316.63\",\"DeviceId\":\"1000000279\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
|
||||||
|
private const string Com43LaterResponse =
|
||||||
|
"{\"Reading\":\"002898.60\",\"DeviceId\":\"1000000219\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
|
||||||
|
private const string Com45LaterResponse =
|
||||||
|
"{\"Reading\":\"002433.50\",\"DeviceId\":\"1000000279\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomerCliResponses_AreDeserializedAndConvertedToDialogValues()
|
||||||
|
{
|
||||||
|
AssertDialogValue(Com43InitialResponse, "1000000219", "002780.99", 10527.1923171862);
|
||||||
|
AssertDialogValue(Com45InitialResponse, "1000000279", "002316.63", 8769.39850116792);
|
||||||
|
AssertDialogValue(Com43LaterResponse, "1000000219", "002898.60", 10972.3945971024);
|
||||||
|
AssertDialogValue(Com45LaterResponse, "1000000279", "002433.50", 9211.799576364);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertDialogValue(string json, string expectedDeviceId,
|
||||||
|
string expectedReading, double expectedLitres)
|
||||||
|
{
|
||||||
|
var cliRunner = new CliRunner(false);
|
||||||
|
JsonDataFromPoseidon response;
|
||||||
|
|
||||||
|
Assert.IsTrue(cliRunner.TryJsonStringDeserialize(json, out response));
|
||||||
|
Assert.IsNotNull(response);
|
||||||
|
Assert.AreEqual(expectedDeviceId, response.DeviceId);
|
||||||
|
Assert.AreEqual(expectedReading, response.Reading);
|
||||||
|
|
||||||
|
double dialogValue;
|
||||||
|
string failureReason;
|
||||||
|
Assert.IsTrue(CmdPoseidonReader.TryGetDialogValue(response, out dialogValue, out failureReason), failureReason);
|
||||||
|
Assert.AreEqual(expectedLitres, dialogValue, DialogValueToleranceLitres,
|
||||||
|
"The value assigned to the START/END dialog must come from the CLI response.");
|
||||||
|
Assert.AreNotEqual(0d, dialogValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.DataEntry.PoseidonCmd;
|
||||||
|
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||||
|
using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class PoseidonDialogTransferTest
|
||||||
|
{
|
||||||
|
private const string CustomerCliResponse =
|
||||||
|
"{\"Reading\":\"002433.50\",\"DeviceId\":\"1000000279\",\"ProductType\":74,\"ReadingComplete\":true,\"NfcTagDetected\":true,\"CliVersion\":\"2.0.0.2\"}";
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomerCliValue_IsPrefilledConfirmedAndTransferred_ForStartAndEnd()
|
||||||
|
{
|
||||||
|
Exception failure = null;
|
||||||
|
var staThread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try { RunDialogTransferScenario(); }
|
||||||
|
catch (Exception exception) { failure = exception; }
|
||||||
|
});
|
||||||
|
staThread.SetApartmentState(ApartmentState.STA);
|
||||||
|
staThread.Start();
|
||||||
|
staThread.Join(TimeSpan.FromSeconds(15));
|
||||||
|
|
||||||
|
Assert.IsFalse(staThread.IsAlive, "The Poseidon dialog test did not finish.");
|
||||||
|
if (failure != null) throw failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunDialogTransferScenario()
|
||||||
|
{
|
||||||
|
var cliRunner = new CliRunner(false);
|
||||||
|
JsonDataFromPoseidon response;
|
||||||
|
Assert.IsTrue(cliRunner.TryJsonStringDeserialize(CustomerCliResponse, out response));
|
||||||
|
|
||||||
|
double cliValueLitres;
|
||||||
|
string failureReason;
|
||||||
|
Assert.IsTrue(CmdPoseidonReader.TryGetDialogValue(response, out cliValueLitres, out failureReason), failureReason);
|
||||||
|
VerifyStartDialogTransfer(cliValueLitres);
|
||||||
|
VerifyEndDialogTransfer(cliValueLitres);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void VerifyStartDialogTransfer(double expectedValue)
|
||||||
|
{
|
||||||
|
using (var dialog = new TestStartEndForm(1, null, new bool[1]))
|
||||||
|
{
|
||||||
|
dialog.CreateControl();
|
||||||
|
dialog.WMStartState[0] = expectedValue;
|
||||||
|
dialog.WMStartStateStr[0] = expectedValue.ToString();
|
||||||
|
dialog.UpdateValues(true, true);
|
||||||
|
Assert.AreEqual(dialog.WMStartStateStr[0], FindTextBox(dialog, "startTextBox1").Text);
|
||||||
|
|
||||||
|
ConfirmDialog(dialog);
|
||||||
|
Assert.IsTrue(dialog.Completed);
|
||||||
|
Assert.AreEqual(expectedValue, dialog.WMStartState[0], 0.0001);
|
||||||
|
|
||||||
|
var entryForm = CreateEntryFormForTransfer(EntryForm.CurrentOp.ReadDatastream_StartStates);
|
||||||
|
TransferAcceptedDialogValues(entryForm, dialog);
|
||||||
|
Assert.AreEqual(expectedValue, entryForm.WMStartState(0), 0.0001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void VerifyEndDialogTransfer(double expectedValue)
|
||||||
|
{
|
||||||
|
using (var dialog = new TestStartEndForm(1, null, new[] { expectedValue.ToString() }, new bool[1], 0, 0, 0))
|
||||||
|
{
|
||||||
|
dialog.CreateControl();
|
||||||
|
dialog.WMEndState[0] = expectedValue;
|
||||||
|
dialog.UpdateValues(false, true);
|
||||||
|
Assert.AreEqual(expectedValue.ToString(), FindTextBox(dialog, "endTextBox1").Text);
|
||||||
|
|
||||||
|
ConfirmDialog(dialog);
|
||||||
|
Assert.IsTrue(dialog.Completed);
|
||||||
|
Assert.AreEqual(expectedValue, dialog.WMEndState[0], 0.0001);
|
||||||
|
|
||||||
|
var entryForm = CreateEntryFormForTransfer(EntryForm.CurrentOp.ReadDatastream_EndStates);
|
||||||
|
TransferAcceptedDialogValues(entryForm, dialog);
|
||||||
|
Assert.AreEqual(expectedValue, entryForm.WMEndState(0), 0.0001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextBox FindTextBox(Control dialog, string name)
|
||||||
|
{
|
||||||
|
TextBox textBox = dialog.Controls.Find(name, true).OfType<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 method = typeof(EntryForm).GetMethod("StoreAcceptedDialogValues",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
Assert.IsNotNull(method);
|
||||||
|
method.Invoke(entryForm, new object[] { dialog });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetPrivateField(object instance, string name, object value)
|
||||||
|
{
|
||||||
|
FieldInfo field = instance.GetType().GetField(name,
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
Assert.IsNotNull(field, "Expected field was not found: " + name);
|
||||||
|
field.SetValue(instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig;
|
||||||
|
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||||
|
using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
[TestSubject(typeof(PoseidonReadCycle))]
|
||||||
|
public class PoseidonReadCycleTest
|
||||||
|
{
|
||||||
|
private sealed class FakeReader : IPoseidonReadOperation
|
||||||
|
{
|
||||||
|
private readonly int iterationsToComplete;
|
||||||
|
private int iterations;
|
||||||
|
public FakeReader(string name, int iterationsToComplete) { Name = name; this.iterationsToComplete = iterationsToComplete; }
|
||||||
|
public string Name { get; private set; }
|
||||||
|
public int StartCount { get; private set; }
|
||||||
|
public CmdPoseidonReader.CurrentPoseidonOp CurrentOp { get; private set; }
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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 && ++iterations >= iterationsToComplete)
|
||||||
|
{ CurrentOp = CmdPoseidonReader.CurrentPoseidonOp.Done; return Event.Done; }
|
||||||
|
return Event.Busy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void RunIteration_FullBench_ArmsEachReaderOnce()
|
||||||
|
{
|
||||||
|
var readers = new List<IPoseidonReadOperation>();
|
||||||
|
for (int i = 1; i <= 48; i++) readers.Add(new FakeReader("PoseidonPos" + i, 1 + i % 3));
|
||||||
|
for (int i = 0; i < 5; i++) PoseidonReadCycle.RunIteration(readers, true);
|
||||||
|
foreach (FakeReader reader in readers) Assert.AreEqual(1, reader.StartCount, reader.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void RunIteration_ReducedBench_DoesNotRestartCompletedReader()
|
||||||
|
{
|
||||||
|
var readers = new List<IPoseidonReadOperation> { new FakeReader("Pos1", 1), new FakeReader("Pos2", 3) };
|
||||||
|
for (int i = 0; i < 5; i++) PoseidonReadCycle.RunIteration(readers, false);
|
||||||
|
foreach (FakeReader reader in readers) Assert.AreEqual(1, reader.StartCount, reader.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void StartThenStop_PhasesArmTheSameReaderOncePerPhase()
|
||||||
|
{
|
||||||
|
var reader = new FakeReader("Pos1", 1);
|
||||||
|
var readers = new List<IPoseidonReadOperation> { reader };
|
||||||
|
var startPhase = new PoseidonReadPhaseRunner(true);
|
||||||
|
|
||||||
|
// START phase completes.
|
||||||
|
startPhase.RunIteration(readers);
|
||||||
|
startPhase.RunIteration(readers);
|
||||||
|
Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
|
||||||
|
|
||||||
|
// A new STOP phase re-arms the terminal reader exactly once.
|
||||||
|
var stopPhase = new PoseidonReadPhaseRunner(false);
|
||||||
|
stopPhase.RunIteration(readers);
|
||||||
|
stopPhase.RunIteration(readers);
|
||||||
|
|
||||||
|
Assert.AreEqual(2, reader.StartCount);
|
||||||
|
Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void InputJson_ZeroReading_WithDecimalCommaOrDot_IsValid()
|
||||||
|
{
|
||||||
|
foreach (string reading in new[] { "00000,0", "00000.0" })
|
||||||
|
{
|
||||||
|
double value;
|
||||||
|
Assert.IsTrue(CmdPoseidonReader.TryParseCliReading(reading, out value));
|
||||||
|
Assert.AreEqual(0d, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void InputJson_NonZeroReading_WithDecimalCommaOrDot_IsParsedCorrectly()
|
||||||
|
{
|
||||||
|
foreach (string reading in new[] { "002433,50", "002433.50", "002898,60", "002898.60" })
|
||||||
|
{
|
||||||
|
double value;
|
||||||
|
Assert.IsTrue(CmdPoseidonReader.TryParseCliReading(reading, out value));
|
||||||
|
Assert.IsTrue(value > 0, "The non-zero CLI reading must remain non-zero: " + reading);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SimulationMode_AlwaysUsesCmdSleepTestInsteadOfConfiguredHatCli()
|
||||||
|
{
|
||||||
|
Assert.AreEqual("cmdSleepTest.exe", CmdPoseidonReader.GetCliFileNameForMode(
|
||||||
|
Common.DebugMode.Simulate, "HatCliDemo.exe"));
|
||||||
|
Assert.AreEqual("HatCliDemo.exe", CmdPoseidonReader.GetCliFileNameForMode(
|
||||||
|
Common.DebugMode.Normal, "HatCliDemo.exe"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CliReadResponse_RequiresNfcAndCompletedReading()
|
||||||
|
{
|
||||||
|
string reason;
|
||||||
|
var data = new JsonDataFromPoseidon { NfcTagDetected = true, ReadingComplete = true, Reading = "00000.0" };
|
||||||
|
Assert.IsTrue(CmdPoseidonReader.TryValidateCliReadResponse(data, out reason));
|
||||||
|
data.ReadingComplete = false;
|
||||||
|
Assert.IsFalse(CmdPoseidonReader.TryValidateCliReadResponse(data, out reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,5 +37,20 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
|||||||
Assert.IsTrue(tryGetDeviceId);
|
Assert.IsTrue(tryGetDeviceId);
|
||||||
Assert.AreEqual("1000000267", strOut);
|
Assert.AreEqual("1000000267", strOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TryParseCliReading_ShouldAcceptZeroWithDotAndComma()
|
||||||
|
{
|
||||||
|
double dotValue;
|
||||||
|
double commaValue;
|
||||||
|
|
||||||
|
Assert.IsTrue(TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader
|
||||||
|
.TryParseCliReading("00000.0", out dotValue));
|
||||||
|
Assert.IsTrue(TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader
|
||||||
|
.TryParseCliReading("00000,0", out commaValue));
|
||||||
|
|
||||||
|
Assert.AreEqual(0d, dotValue);
|
||||||
|
Assert.AreEqual(0d, commaValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||||
|
using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
[TestCategory("Integration")]
|
||||||
|
public class PoseidonSingleMeterIntegrationTest
|
||||||
|
{
|
||||||
|
// Set this only when a real Poseidon meter is connected to the selected hat.
|
||||||
|
private const int RealPoseidonComPort = 0;
|
||||||
|
private const string RealPoseidonCliFile = "HatCliDemo.exe";
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[TestCategory("Manual")]
|
||||||
|
public void RealMeter_ReadStartValueThroughCli()
|
||||||
|
{
|
||||||
|
if (RealPoseidonComPort <= 0)
|
||||||
|
Assert.Inconclusive("Set RealPoseidonComPort before running this manual Poseidon integration test.");
|
||||||
|
|
||||||
|
string cliPath = Path.Combine(SerialPortData.CliDirectory, RealPoseidonCliFile);
|
||||||
|
if (!File.Exists(cliPath))
|
||||||
|
Assert.Inconclusive("Poseidon CLI was not found: " + cliPath);
|
||||||
|
|
||||||
|
var cfg = new PoseidonCfg("PoseidonIntegration", new Factory())
|
||||||
|
{
|
||||||
|
ComPortNr = RealPoseidonComPort,
|
||||||
|
CliFileName = RealPoseidonCliFile
|
||||||
|
};
|
||||||
|
var reader = new CmdPoseidonReader(cfg, null);
|
||||||
|
reader.DebugLevel = Common.DebugMode.Normal;
|
||||||
|
reader.Initialize();
|
||||||
|
reader.SetCurrentOp(CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_Start);
|
||||||
|
|
||||||
|
for (int iteration = 0; iteration < 3500 &&
|
||||||
|
reader.CurrentOp != CmdPoseidonReader.CurrentPoseidonOp.Done &&
|
||||||
|
reader.CurrentOp != CmdPoseidonReader.CurrentPoseidonOp.Error; iteration++)
|
||||||
|
{
|
||||||
|
reader.Run();
|
||||||
|
Thread.Sleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
|
||||||
|
Assert.IsTrue(reader.LastCliReadingParsed,
|
||||||
|
"CLI returned no parseable reading. Inspect the Poseidon CLI and TBF logs.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -119,6 +119,7 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonReader
|
|||||||
Name = "PoseidonCfg",
|
Name = "PoseidonCfg",
|
||||||
OptoComPortNr = 6,
|
OptoComPortNr = 6,
|
||||||
RfidComPortNr = 5,
|
RfidComPortNr = 5,
|
||||||
|
DebugLevel = Common.DebugMode.Simulate,
|
||||||
};
|
};
|
||||||
|
|
||||||
config = poseidonCfg;
|
config = poseidonCfg;
|
||||||
@@ -156,7 +157,8 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonReader
|
|||||||
string allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
string allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
||||||
|
|
||||||
Console.WriteLine(allText);
|
Console.WriteLine(allText);
|
||||||
Assert.IsTrue(allText.Contains("ReadSerialNo: 1000000267"));
|
Assert.IsTrue(allText.Contains("ReadSerialNo: 1111"),
|
||||||
|
"The UI unit test must use the deterministic simulated serial number, not physical hardware.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -215,10 +217,6 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonReader
|
|||||||
string allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
string allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
||||||
Console.WriteLine(allText);
|
Console.WriteLine(allText);
|
||||||
Assert.IsTrue(allText.Contains("SetTestMode: OK"));
|
Assert.IsTrue(allText.Contains("SetTestMode: OK"));
|
||||||
//some input from the meter
|
|
||||||
|
|
||||||
|
|
||||||
Thread.Sleep(50000);
|
|
||||||
//SET OPTOTEST MODE OFF
|
//SET OPTOTEST MODE OFF
|
||||||
comboBox.SelectedIndex = (int)PoseidonImplHeadTestCtrl.Operations.SetTestModeOff;
|
comboBox.SelectedIndex = (int)PoseidonImplHeadTestCtrl.Operations.SetTestModeOff;
|
||||||
uniHeadTestCtrl.CommandTestButtonClick(commandTestButton, eventArgs);
|
uniHeadTestCtrl.CommandTestButtonClick(commandTestButton, eventArgs);
|
||||||
@@ -226,22 +224,6 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonReader
|
|||||||
allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
||||||
Console.WriteLine(allText);
|
Console.WriteLine(allText);
|
||||||
Assert.IsTrue(allText.Contains("SetActiveMode: OK"));
|
Assert.IsTrue(allText.Contains("SetActiveMode: OK"));
|
||||||
|
|
||||||
|
|
||||||
/////////////// results of OptoHead
|
|
||||||
// Get the private ListBox field via reflection
|
|
||||||
var fieldOpto = uniHeadTestCtrl.GetType()
|
|
||||||
.GetField("optoListBox", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
||||||
// Read its value from the control instance (not Text!)
|
|
||||||
var listBoxOpto = fieldOpto?.GetValue(uniHeadTestCtrl) as System.Windows.Forms.ListBox;
|
|
||||||
// Verify we found it
|
|
||||||
Assert.IsNotNull(listBoxOpto, "optoListBox not found in UniHeadTestCtrl");
|
|
||||||
|
|
||||||
//check if the test mode is set OFF
|
|
||||||
allText = string.Join(", ", listBoxOpto.Items.Cast<object>().Select(i => i.ToString()));
|
|
||||||
Console.WriteLine(allText);
|
|
||||||
Assert.IsTrue(allText.Contains("Status: OptoHeadC7"));
|
|
||||||
Assert.IsTrue(allText.Contains("C7Data: C7"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-3
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||||
@@ -14,6 +15,7 @@ using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
|
|||||||
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHatProtocol
|
namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHatProtocol
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
|
[TestCategory("HardwareIntegration")]
|
||||||
public class IperlHatIntegrationTests
|
public class IperlHatIntegrationTests
|
||||||
{
|
{
|
||||||
private const string ComPort = "COM12"; // CHANGE THIS
|
private const string ComPort = "COM12"; // CHANGE THIS
|
||||||
@@ -22,6 +24,23 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHat
|
|||||||
private const int BaudRateOpto = 38400;
|
private const int BaudRateOpto = 38400;
|
||||||
private const int ReadTimeoutMs = 2000;
|
private const int ReadTimeoutMs = 2000;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void RequireExplicitHardwareConfiguration()
|
||||||
|
{
|
||||||
|
if (!string.Equals(Environment.GetEnvironmentVariable("IPERL_ASIC_HW_TESTS"), "1",
|
||||||
|
StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
Assert.Inconclusive("Set IPERL_ASIC_HW_TESTS=1 to run iPerl ASIC serial hardware integration tests.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] ports = SerialPort.GetPortNames();
|
||||||
|
if (!ports.Any(port => string.Equals(port, ComPort, StringComparison.OrdinalIgnoreCase)) ||
|
||||||
|
!ports.Any(port => string.Equals(port, ComPortOptho, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
Assert.Inconclusive($"iPerl ASIC hardware integration requires configured ports {ComPort} and {ComPortOptho}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[TestCategory("Hardware")]
|
[TestCategory("Hardware")]
|
||||||
[TestCategory("Serial")]
|
[TestCategory("Serial")]
|
||||||
@@ -454,7 +473,7 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHat
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[TestCategory("Hardware")]
|
[TestCategory("Hardware")]
|
||||||
public void Serial_OptoRawSniff_Standalone()
|
public void Integration_Serial_OptoRawSniff_Standalone()
|
||||||
{
|
{
|
||||||
Serial_OptoRawSniff();
|
Serial_OptoRawSniff();
|
||||||
}
|
}
|
||||||
@@ -503,7 +522,7 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHat
|
|||||||
}
|
}
|
||||||
catch (TimeoutException)
|
catch (TimeoutException)
|
||||||
{
|
{
|
||||||
Assert.Fail("Serial read timeout");
|
Assert.Inconclusive("No optical packet was received within 10 seconds. Enable optical test mode before running this sniff integration test.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,4 +1016,4 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHat
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
|
using TBF.Rig.TestMethods.AllyCalibration;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.TestMethods.AllyCalibration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Component-level integration coverage of Read PCB, start optical output,
|
||||||
|
/// C6 parsing and cleanup without serial hardware.
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
[TestSubject(typeof(AllyCalibrationSeq))]
|
||||||
|
public class AllyOpticalWorkflowIntegrationTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void ConfiguredWorkflow_FakeAllyReader_ReadPcbStartReadOpticalAndStop_TransfersMeterStates()
|
||||||
|
{
|
||||||
|
AllyMeterReader reader = CreateFakeReader();
|
||||||
|
TestMethodCfg cfg = new TestMethodCfg(new TBF.Rig.TestMethods.AllyCalibration.Factory());
|
||||||
|
TestMethodParams parameters = new TestMethodParams(true);
|
||||||
|
string message;
|
||||||
|
|
||||||
|
parameters.Activity = AllyCalibrationActivityNames.ReadSerialNumber;
|
||||||
|
Assert.IsTrue(AllyCalibrationSeq.ExecuteWithRetries(reader, cfg, parameters, out message), message);
|
||||||
|
Assert.AreEqual("ALLY-SIMULATED", reader.SerialNr);
|
||||||
|
|
||||||
|
parameters.Activity = AllyCalibrationActivityNames.UnsealAndStartOpticalStream;
|
||||||
|
Assert.IsTrue(AllyCalibrationSeq.ExecuteWithRetries(reader, cfg, parameters, out message), message);
|
||||||
|
|
||||||
|
reader.Start();
|
||||||
|
DateTime start = new DateTime(2026, 9, 15, 10, 40, 0, DateTimeKind.Utc);
|
||||||
|
reader.ProcessOpticalTextForTest(
|
||||||
|
TBFTests.Rig.RegisterReaders.AllyReader.AllyOpticalTelegramFactory.CreateC6(0x10, 0, 0, 12, 8000U, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
start);
|
||||||
|
reader.ProcessOpticalTextForTest(
|
||||||
|
TBFTests.Rig.RegisterReaders.AllyReader.AllyOpticalTelegramFactory.CreateC6(0x12, 0, 0, 12, 8120U, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
start.AddSeconds(5));
|
||||||
|
reader.Stop();
|
||||||
|
|
||||||
|
parameters.Activity = AllyCalibrationActivityNames.StopOpticalStream;
|
||||||
|
Assert.IsTrue(AllyCalibrationSeq.ExecuteWithRetries(reader, cfg, parameters, out message), message);
|
||||||
|
|
||||||
|
Assert.IsFalse(reader.NoSamples);
|
||||||
|
Assert.AreEqual(2D, reader.BeginWMState, 1E-12);
|
||||||
|
Assert.AreEqual(2.03D, reader.EndWMState, 1E-12);
|
||||||
|
Assert.AreEqual(0.03D, reader.WMVolume, 1E-12);
|
||||||
|
Assert.AreEqual(5D, reader.TimestampSecEnd - reader.TimestampSecStart, 1E-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AllyMeterReader CreateFakeReader()
|
||||||
|
{
|
||||||
|
AllyReaderCfg cfg = new AllyReaderCfg(new TBF.Rig.RegisterReaders.AllyReader.Factory())
|
||||||
|
{
|
||||||
|
DebugLevel = Common.DebugMode.Simulate,
|
||||||
|
ConfiguredMeterSize = AllyMeterSize.AutoDetect,
|
||||||
|
Name = "FakeAlly1"
|
||||||
|
};
|
||||||
|
AllyMeterReader reader = new AllyMeterReader(cfg);
|
||||||
|
reader.Initialize();
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,8 +28,10 @@ namespace TBFTests.Rig.TestMethods.AllyCalibration
|
|||||||
TestMethodParams parameters = new TestMethodParams(true);
|
TestMethodParams parameters = new TestMethodParams(true);
|
||||||
ICollection<string> activities = parameters.ParamValues(0);
|
ICollection<string> activities = parameters.ParamValues(0);
|
||||||
|
|
||||||
Assert.AreEqual(19, activities.Count);
|
Assert.AreEqual(21, activities.Count);
|
||||||
CollectionAssert.Contains((System.Collections.ICollection)activities, "Read serial number");
|
CollectionAssert.Contains((System.Collections.ICollection)activities, "Read serial number");
|
||||||
|
CollectionAssert.Contains((System.Collections.ICollection)activities, "Unseal meter and start optical stream");
|
||||||
|
CollectionAssert.Contains((System.Collections.ICollection)activities, "Stop optical stream");
|
||||||
CollectionAssert.Contains((System.Collections.ICollection)activities, "Start offset learning");
|
CollectionAssert.Contains((System.Collections.ICollection)activities, "Start offset learning");
|
||||||
CollectionAssert.DoesNotContain((System.Collections.ICollection)activities, "Q2 correction");
|
CollectionAssert.DoesNotContain((System.Collections.ICollection)activities, "Q2 correction");
|
||||||
|
|
||||||
|
|||||||
+241
@@ -0,0 +1,241 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using TBF;
|
||||||
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
|
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||||
|
using TBF.Rig.Sequences;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class SmartCommunicationFormTests
|
||||||
|
{
|
||||||
|
private static readonly object ProcessDataLock = new object();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SharedCommunicationParams_ActivityIsRetainedThroughITestParams()
|
||||||
|
{
|
||||||
|
var parameters = new iPerlCommunicationParams
|
||||||
|
{
|
||||||
|
Activity = "Set Test mode Optho 7"
|
||||||
|
};
|
||||||
|
|
||||||
|
TBF.Rig.Generic.ITestParams asInterface = parameters;
|
||||||
|
|
||||||
|
Assert.AreEqual("Set Test mode Optho 7", asInterface.Activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ManualForm_PopulatesAvailableFamiliesFromRegisterReaders()
|
||||||
|
{
|
||||||
|
RunInSta(() =>
|
||||||
|
{
|
||||||
|
WithSmartReaders(new ISmartReader[] { CreateGenesisReader(), CreateAllyReader() }, () =>
|
||||||
|
{
|
||||||
|
using (var form = new SmartCommunicationForm(true))
|
||||||
|
{
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new[] { "Genesis", "ALLY" },
|
||||||
|
SmartCommunicationForm.IdentifyReaderTypes());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ManualForm_SelectedFamilyPopulatesOnlyMatchingReaderAndCorrection()
|
||||||
|
{
|
||||||
|
RunInSta(() =>
|
||||||
|
{
|
||||||
|
GenesisSmartReader genesis = CreateGenesisReader();
|
||||||
|
AllyMeterReader ally = CreateAllyReader();
|
||||||
|
WithSmartReaders(new ISmartReader[] { genesis, ally }, () =>
|
||||||
|
{
|
||||||
|
using (var form = new SmartCommunicationForm(true))
|
||||||
|
{
|
||||||
|
SelectFamily(form, "ALLY");
|
||||||
|
Assert.AreEqual(1, form.Heads.Count);
|
||||||
|
Assert.AreSame(ally, form.Heads[0]);
|
||||||
|
Assert.IsInstanceOfType(CurrentCorrection(), typeof(AllyCorrections));
|
||||||
|
|
||||||
|
SelectFamily(form, "Genesis");
|
||||||
|
Assert.AreEqual(1, form.Heads.Count);
|
||||||
|
Assert.AreSame(genesis, form.Heads[0]);
|
||||||
|
Assert.IsInstanceOfType(CurrentCorrection(), typeof(GenesisCorrections));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ManualForm_LoadsReaderRowsAndCheckboxTogglesSelectionState()
|
||||||
|
{
|
||||||
|
RunInSta(() =>
|
||||||
|
{
|
||||||
|
AllyMeterReader first = CreateAllyReader();
|
||||||
|
AllyMeterReader second = CreateAllyReader();
|
||||||
|
WithSmartReaders(new ISmartReader[] { first, second }, () =>
|
||||||
|
{
|
||||||
|
using (var form = new SmartCommunicationForm(true))
|
||||||
|
{
|
||||||
|
SelectFamily(form, "ALLY");
|
||||||
|
form.CkbIndex[0] = 0;
|
||||||
|
form.CkbIndex[1] = 1;
|
||||||
|
ICorrections correction = CurrentCorrection();
|
||||||
|
correction.Load(form.Labels, form.Counters, form.Messages, form.CheckBoxes,
|
||||||
|
form.CkbIndex, form.CkbState, form.Heads, form.CheckBoxes.Length, true);
|
||||||
|
|
||||||
|
Assert.AreEqual(2, form.Heads.Count);
|
||||||
|
Assert.AreSame(first, form.Heads[0]);
|
||||||
|
Assert.AreSame(second, form.Heads[1]);
|
||||||
|
Assert.IsTrue(form.CheckBoxes[0].Checked);
|
||||||
|
Assert.IsTrue(form.CheckBoxes[1].Checked);
|
||||||
|
Assert.AreEqual("---", form.Messages[0].Text);
|
||||||
|
Assert.AreEqual("---", form.Messages[1].Text);
|
||||||
|
|
||||||
|
form.CheckBoxes[0].Checked = false;
|
||||||
|
InvokePrivate(form, "checkBoxImage1_Click", form.CheckBoxes[0], EventArgs.Empty);
|
||||||
|
Assert.IsFalse(form.CkbState[0]);
|
||||||
|
|
||||||
|
form.CheckBoxes[0].Checked = true;
|
||||||
|
InvokePrivate(form, "checkBoxImage1_Click", form.CheckBoxes[0], EventArgs.Empty);
|
||||||
|
Assert.IsTrue(form.CkbState[0]);
|
||||||
|
Assert.IsTrue((form.GetCheckBoxStates() & 1L) != 0L);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SelectedCorrection_ProvidesCommandsForTheSelectedRegisterReaderFamily()
|
||||||
|
{
|
||||||
|
RunInSta(() =>
|
||||||
|
{
|
||||||
|
WithSmartReaders(new ISmartReader[] { CreateGenesisReader(), CreateAllyReader() }, () =>
|
||||||
|
{
|
||||||
|
using (var form = new SmartCommunicationForm(true))
|
||||||
|
{
|
||||||
|
SelectFamily(form, "ALLY");
|
||||||
|
CollectionAssert.Contains(
|
||||||
|
CurrentCorrection().GetContextMenu().MenuItems.Cast<System.Windows.Forms.MenuItem>()
|
||||||
|
.Select(item => item.Text).ToArray(),
|
||||||
|
"Read Serial Number");
|
||||||
|
|
||||||
|
SelectFamily(form, "Genesis");
|
||||||
|
CollectionAssert.Contains(
|
||||||
|
CurrentCorrection().GetContextMenu().MenuItems.Cast<System.Windows.Forms.MenuItem>()
|
||||||
|
.Select(item => item.Text).ToArray(),
|
||||||
|
"Read PCB Number");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SelectionSettings_MigratesLegacyMaskToConfiguredIPerlFamily()
|
||||||
|
{
|
||||||
|
var first = new Mock<ISmartReader>();
|
||||||
|
first.SetupGet(reader => reader.Name).Returns("iPerl01");
|
||||||
|
first.SetupGet(reader => reader.Position).Returns(1);
|
||||||
|
var second = new Mock<ISmartReader>();
|
||||||
|
second.SetupGet(reader => reader.Name).Returns("iPerl02");
|
||||||
|
second.SetupGet(reader => reader.Position).Returns(2);
|
||||||
|
var settings = new LocalSettings { OptoHeadsEnabled = 1L << 1 };
|
||||||
|
|
||||||
|
SmartReaderSelection.EnsureConfiguredReaders(
|
||||||
|
settings, new ISmartReader[] { first.Object, second.Object });
|
||||||
|
|
||||||
|
SmartReaderFamilySelection family = settings.SmartReaderSelections.Families.Single();
|
||||||
|
Assert.AreEqual("iperl", family.FamilyKey);
|
||||||
|
Assert.IsFalse(family.Readers.Single(item => item.ReaderKey == SmartReaderSelection.GetReaderKey(first.Object)).Enabled);
|
||||||
|
Assert.IsTrue(family.Readers.Single(item => item.ReaderKey == SmartReaderSelection.GetReaderKey(second.Object)).Enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SelectionSettings_MigratesLegacyMaskToFirstFamilyWhenIPerlIsNotConfigured()
|
||||||
|
{
|
||||||
|
AllyMeterReader ally = CreateAllyReader();
|
||||||
|
var settings = new LocalSettings { OptoHeadsEnabled = 1L };
|
||||||
|
|
||||||
|
SmartReaderSelection.EnsureConfiguredReaders(settings, new ISmartReader[] { ally });
|
||||||
|
|
||||||
|
SmartReaderFamilySelection family = settings.SmartReaderSelections.Families.Single();
|
||||||
|
Assert.AreEqual("ally", family.FamilyKey);
|
||||||
|
Assert.IsTrue(family.Readers.Single().Enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AllyMeterReader CreateAllyReader()
|
||||||
|
{
|
||||||
|
return new AllyMeterReader(new AllyReaderCfg(new TBF.Rig.RegisterReaders.AllyReader.Factory()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GenesisSmartReader CreateGenesisReader()
|
||||||
|
{
|
||||||
|
return new GenesisSmartReader(new TBF.Rig.RegisterReaders.GenesisRegReader.GenesisCfg(
|
||||||
|
new TBF.Rig.RegisterReaders.GenesisRegReader.Factory()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SelectFamily(SmartCommunicationForm form, string family)
|
||||||
|
{
|
||||||
|
SmartCommunicationForm.SelectedTypeReader = family;
|
||||||
|
InvokePrivate(form, "UpdateHeads");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ICorrections CurrentCorrection()
|
||||||
|
{
|
||||||
|
PropertyInfo property = typeof(SmartCommunicationForm).GetProperty(
|
||||||
|
"Correction", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
|
return (ICorrections)property.GetValue(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InvokePrivate(SmartCommunicationForm form, string methodName, params object[] parameters)
|
||||||
|
{
|
||||||
|
MethodInfo method = typeof(SmartCommunicationForm).GetMethod(
|
||||||
|
methodName, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
method.Invoke(form, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WithSmartReaders(IList<ISmartReader> readers, Action action)
|
||||||
|
{
|
||||||
|
lock (ProcessDataLock)
|
||||||
|
{
|
||||||
|
IList<ISmartReader> previous = ProcessData.SmartHeadsUni;
|
||||||
|
string previousSelectedType = SmartCommunicationForm.SelectedTypeReader;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProcessData.SmartHeadsUni = readers;
|
||||||
|
SmartCommunicationForm.SelectedTypeReader = null;
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ProcessData.SmartHeadsUni = previous;
|
||||||
|
SmartCommunicationForm.SelectedTypeReader = previousSelectedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunInSta(Action action)
|
||||||
|
{
|
||||||
|
Exception failure = null;
|
||||||
|
Thread thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try { action(); }
|
||||||
|
catch (Exception exception) { failure = exception; }
|
||||||
|
});
|
||||||
|
thread.SetApartmentState(ApartmentState.STA);
|
||||||
|
thread.Start();
|
||||||
|
thread.Join();
|
||||||
|
if (failure != null)
|
||||||
|
throw new AssertFailedException(failure.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations;
|
||||||
|
using LegacyIPerlReader = TBF.Rig.RegisterReaders.IPerlReader.implementations.SmartReader;
|
||||||
|
using AsicIPerlReader = TBF.Rig.RegisterReaders.iPerlASICReader.implementations.SmartReader;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
||||||
|
{
|
||||||
|
/// <summary>Fast tests of the correction-adapter contract; no COM port is opened.</summary>
|
||||||
|
[TestClass]
|
||||||
|
public class CorrectionsContractTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void ManualAdapters_ExposeStableFamilyNames()
|
||||||
|
{
|
||||||
|
Assert.AreEqual("ALLY", new AllyCorrections(null).TypeIdentificatorName());
|
||||||
|
Assert.AreEqual("Genesis", new GenesisCorrections(null).TypeIdentificatorName());
|
||||||
|
Assert.AreEqual("iPerl", new IPerlCorrections(null).TypeIdentificatorName());
|
||||||
|
Assert.AreEqual("iPerl ASIC", new IPerlASICCorrections(null).TypeIdentificatorName());
|
||||||
|
Assert.AreEqual("Poseidon", new PoseidonCorrections(null).TypeIdentificatorName());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void AllyManualAdapter_ExposesOnlySupportedSafeOperations()
|
||||||
|
{
|
||||||
|
var menu = new AllyCorrections(null).GetContextMenu();
|
||||||
|
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new[] { "Read Serial Number", "Read Version and Type", "Set RFID mode", "Set NFC mode" },
|
||||||
|
menu.MenuItems.Cast<System.Windows.Forms.MenuItem>().Select(item => item.Text).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void GenesisManualAdapter_ExposesOnlyImplementedOperations()
|
||||||
|
{
|
||||||
|
var menu = new GenesisCorrections(null).GetContextMenu();
|
||||||
|
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new[] { "Read PCB Number", "Set RFID mode", "Set NFC mode" },
|
||||||
|
menu.MenuItems.Cast<System.Windows.Forms.MenuItem>().Select(item => item.Text).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ManualOnlyAdapters_RejectTestCommunicationExplicitly()
|
||||||
|
{
|
||||||
|
ICorrections correction = new AllyCorrections(null);
|
||||||
|
CommErr error = CommErr.None;
|
||||||
|
string result = string.Empty;
|
||||||
|
|
||||||
|
bool handled = correction.WorkerActivity("Read Serial Number", null, null, null, 0,
|
||||||
|
ref error, ref result, new bool[0], 0, 0);
|
||||||
|
|
||||||
|
Assert.IsFalse(handled);
|
||||||
|
Assert.AreEqual(CommErr.WrongIPerlType, error);
|
||||||
|
StringAssert.Contains(result, "test communication is not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IPerlCorrection_DisabledReaderStopsBusinessActivityBeforeCommunication()
|
||||||
|
{
|
||||||
|
ISmartReader reader = new LegacyIPerlReader(
|
||||||
|
new TBF.Rig.RegisterReaders.IPerlReader.Factory().DefaultConfig()) { Disabled = true };
|
||||||
|
CommErr error = CommErr.None;
|
||||||
|
string result = string.Empty;
|
||||||
|
|
||||||
|
bool handled = new IPerlCorrections(null).WorkerActivity("Unknown activity", reader, null, null, 0,
|
||||||
|
ref error, ref result, new[] { true }, 0, 0);
|
||||||
|
|
||||||
|
Assert.IsTrue(handled);
|
||||||
|
Assert.AreEqual(CommErr.HeadDisabledByUser, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IPerlAsicCorrection_DisabledReaderStopsBusinessActivityBeforeCommunication()
|
||||||
|
{
|
||||||
|
ISmartReader reader = new AsicIPerlReader(
|
||||||
|
new TBF.Rig.RegisterReaders.iPerlASICReader.Factory().DefaultConfig()) { Disabled = true };
|
||||||
|
CommErr error = CommErr.None;
|
||||||
|
string result = string.Empty;
|
||||||
|
|
||||||
|
bool handled = new IPerlASICCorrections(null).WorkerActivity("Unknown activity", reader, null, null, 0,
|
||||||
|
ref error, ref result, new[] { true }, 0, 0);
|
||||||
|
|
||||||
|
Assert.IsTrue(handled);
|
||||||
|
Assert.AreEqual(CommErr.HeadDisabledByUser, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using TBF.Rig.Generic;
|
||||||
|
using TBF.Rig.GenericDevices;
|
||||||
|
using TBF.Rig.RegisterReaders.AllyReader;
|
||||||
|
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||||
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations;
|
||||||
|
using IPerlSmartReader = TBF.Rig.RegisterReaders.IPerlReader.implementations.SmartReader;
|
||||||
|
using IPerlAsicSmartReader = TBF.Rig.RegisterReaders.iPerlASICReader.implementations.SmartReader;
|
||||||
|
using PoseidonSmartReader = TBF.Rig.RegisterReaders.PoseidonReader.SmartReader;
|
||||||
|
|
||||||
|
namespace TBFTests.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations
|
||||||
|
{
|
||||||
|
/// <summary>Reader-to-adapter wiring tests. They use real reader classes but no hardware transport.</summary>
|
||||||
|
[TestClass]
|
||||||
|
public class RegisterReaderCorrectionWiringTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void AllyReader_IsRecognizedOnlyByAllyCorrection()
|
||||||
|
{
|
||||||
|
AllyMeterReader reader = new AllyMeterReader(new AllyReaderCfg(new TBF.Rig.RegisterReaders.AllyReader.Factory()));
|
||||||
|
|
||||||
|
Assert.IsInstanceOfType(reader, typeof(ISmartReader));
|
||||||
|
Assert.IsInstanceOfType(reader, typeof(IRegReaderSmart));
|
||||||
|
Assert.IsTrue(new AllyCorrections(null).IsFamilyOfSmartReader(reader));
|
||||||
|
Assert.IsFalse(new GenesisCorrections(null).IsFamilyOfSmartReader(reader));
|
||||||
|
Assert.IsFalse(new IPerlCorrections(null).IsFamilyOfSmartReader(reader));
|
||||||
|
Assert.IsFalse(new IPerlASICCorrections(null).IsFamilyOfSmartReader(reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void GenesisReader_IsRecognizedOnlyByGenesisCorrection()
|
||||||
|
{
|
||||||
|
GenesisSmartReader reader = new GenesisSmartReader(
|
||||||
|
new TBF.Rig.RegisterReaders.GenesisRegReader.GenesisCfg(new TBF.Rig.RegisterReaders.GenesisRegReader.Factory()));
|
||||||
|
|
||||||
|
Assert.IsInstanceOfType(reader, typeof(ISmartReader));
|
||||||
|
Assert.IsInstanceOfType(reader, typeof(IRegReaderSmart));
|
||||||
|
Assert.IsTrue(new GenesisCorrections(null).IsFamilyOfSmartReader(reader));
|
||||||
|
Assert.IsFalse(new AllyCorrections(null).IsFamilyOfSmartReader(reader));
|
||||||
|
Assert.IsFalse(new IPerlCorrections(null).IsFamilyOfSmartReader(reader));
|
||||||
|
Assert.IsFalse(new IPerlASICCorrections(null).IsFamilyOfSmartReader(reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ExistingSmartReaderFamilies_AreBoundToTheirOwnCorrections()
|
||||||
|
{
|
||||||
|
ISmartReader oldIperl = new IPerlSmartReader(
|
||||||
|
new TBF.Rig.RegisterReaders.IPerlReader.Factory().DefaultConfig());
|
||||||
|
ISmartReader asic = new IPerlAsicSmartReader(
|
||||||
|
new TBF.Rig.RegisterReaders.iPerlASICReader.Factory().DefaultConfig());
|
||||||
|
ISmartReader poseidon = new PoseidonSmartReader(
|
||||||
|
new TBF.Rig.RegisterReaders.PoseidonReader.Factory().DefaultConfig());
|
||||||
|
|
||||||
|
Assert.IsTrue(new IPerlCorrections(null).IsFamilyOfSmartReader(oldIperl));
|
||||||
|
Assert.IsTrue(new IPerlASICCorrections(null).IsFamilyOfSmartReader(asic));
|
||||||
|
Assert.IsTrue(new PoseidonCorrections(null).IsFamilyOfSmartReader(poseidon));
|
||||||
|
|
||||||
|
Assert.IsFalse(new IPerlCorrections(null).IsFamilyOfSmartReader(asic));
|
||||||
|
Assert.IsFalse(new IPerlASICCorrections(null).IsFamilyOfSmartReader(oldIperl));
|
||||||
|
Assert.IsFalse(new PoseidonCorrections(null).IsFamilyOfSmartReader(oldIperl));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NonAsicFamilies_AllyPoseidonAndGenesisCordonel_UseDistinctSelectionKeysAndCorrections()
|
||||||
|
{
|
||||||
|
// GenesisSmartReader is the RegisterReaders.GenesisRegReader implementation
|
||||||
|
// backed by the Cordonel protocol. Keep this check at the reader boundary so
|
||||||
|
// a later rename cannot silently route a Cordonel reader through another
|
||||||
|
// SmartCommunicationForm correction adapter.
|
||||||
|
ISmartReader ally = new AllyMeterReader(
|
||||||
|
new AllyReaderCfg(new TBF.Rig.RegisterReaders.AllyReader.Factory()));
|
||||||
|
ISmartReader genesisCordonel = new GenesisSmartReader(
|
||||||
|
new TBF.Rig.RegisterReaders.GenesisRegReader.GenesisCfg(
|
||||||
|
new TBF.Rig.RegisterReaders.GenesisRegReader.Factory()));
|
||||||
|
ISmartReader poseidon = new PoseidonSmartReader(
|
||||||
|
new TBF.Rig.RegisterReaders.PoseidonReader.Factory().DefaultConfig());
|
||||||
|
|
||||||
|
Assert.AreEqual("ally", SmartReaderSelection.GetFamilyKey(ally));
|
||||||
|
Assert.AreEqual("genesis", SmartReaderSelection.GetFamilyKey(genesisCordonel));
|
||||||
|
Assert.AreEqual("poseidon", SmartReaderSelection.GetFamilyKey(poseidon));
|
||||||
|
|
||||||
|
AssertOnlyMatchingCorrection(ally, new AllyCorrections(null),
|
||||||
|
new GenesisCorrections(null), new PoseidonCorrections(null));
|
||||||
|
AssertOnlyMatchingCorrection(genesisCordonel, new GenesisCorrections(null),
|
||||||
|
new AllyCorrections(null), new PoseidonCorrections(null));
|
||||||
|
AssertOnlyMatchingCorrection(poseidon, new PoseidonCorrections(null),
|
||||||
|
new AllyCorrections(null), new GenesisCorrections(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SmartReaderContract_PreservesManualSelectionStateWithoutTransport()
|
||||||
|
{
|
||||||
|
ISmartReader reader = new AllyMeterReader(new AllyReaderCfg(new TBF.Rig.RegisterReaders.AllyReader.Factory()));
|
||||||
|
|
||||||
|
reader.Disabled = true;
|
||||||
|
reader.SerialNr = "ALLY-TEST";
|
||||||
|
reader.SetCommunicationInterface("RFID");
|
||||||
|
|
||||||
|
Assert.IsTrue(reader.Disabled);
|
||||||
|
Assert.AreEqual("ALLY-TEST", reader.SerialNr);
|
||||||
|
Assert.AreEqual("Touch-Read", reader.CommInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertOnlyMatchingCorrection(ISmartReader reader, ICorrections expected,
|
||||||
|
params ICorrections[] unexpected)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(expected.IsFamilyOfSmartReader(reader));
|
||||||
|
foreach (ICorrections correction in unexpected)
|
||||||
|
Assert.IsFalse(correction.IsFamilyOfSmartReader(reader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -135,11 +135,18 @@
|
|||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutTest.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutTest.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutUnitTest.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutUnitTest.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReaderTest.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\RegisterReaders\PoseidonReader\UniHeadTestCtrlTest.cs" />
|
<Compile Include="Rig\RegisterReaders\PoseidonReader\UniHeadTestCtrlTest.cs" />
|
||||||
<Compile Include="Rig\Scales\MettlerToledo\ReadStableMassOpTest.cs" />
|
<Compile Include="Rig\Scales\MettlerToledo\ReadStableMassOpTest.cs" />
|
||||||
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisHead\GenesisHeadBatchIntegrationTest.cs" />
|
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisHead\GenesisHeadBatchIntegrationTest.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyMeterReaderTest.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyMeterReaderTest.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\AsicReader\AsicReaderFactoryTest.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\AsicReader\AsicCorrectionsTest.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalSampleTest.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalSampleTest.cs" />
|
||||||
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalRealExamplesTest.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalTelegramFactory.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalTelegramFactory.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderCfgTest.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderCfgTest.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllyCommandServiceTest.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllyCommandServiceTest.cs" />
|
||||||
@@ -154,11 +161,15 @@
|
|||||||
<Compile Include="Rig\RegisterReaders\AllyReader\integration\AllyIntegrationTestLogger.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\integration\AllyIntegrationTestLogger.cs" />
|
||||||
<Compile Include="Rig\RegisterReaders\AllyReader\integration\AllyHardwareIntegrationTest.cs" />
|
<Compile Include="Rig\RegisterReaders\AllyReader\integration\AllyHardwareIntegrationTest.cs" />
|
||||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodConfigTest.cs" />
|
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodConfigTest.cs" />
|
||||||
|
<Compile Include="Rig\TestMethods\AllyCalibration\AllyOpticalWorkflowIntegrationTest.cs" />
|
||||||
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRawTest.cs" />
|
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRawTest.cs" />
|
||||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\FakeSerialDriver.cs" />
|
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\FakeSerialDriver.cs" />
|
||||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\IperlResponseFactory.cs" />
|
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\IperlResponseFactory.cs" />
|
||||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\RadioServiceTest.cs" />
|
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\RadioServiceTest.cs" />
|
||||||
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\PoseidonCorrectionsTest.cs" />
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\PoseidonCorrectionsTest.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\SmartCommunicationFormTests.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\CorrectionsContractTests.cs" />
|
||||||
|
<Compile Include="Rig\Uni\SharedDialogs\SmartMetersCommunication\implementations\RegisterReaderCorrectionWiringTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
|
|||||||
Reference in New Issue
Block a user