Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8f6af7112 | ||
|
|
684513ee59 | ||
|
|
52bd0d01ce | ||
|
|
88b8bc5534 | ||
|
|
d3c8813012 | ||
|
|
054075a341 | ||
|
|
a0d53f9ec9 | ||
|
|
dfa8693be9 | ||
|
|
1e45f48c75 | ||
|
|
d833eca584 |
@@ -189,6 +189,15 @@ namespace Results
|
||||
return null;
|
||||
}
|
||||
|
||||
public MeterTestRslt GetEachMeterTestRslt(string name, int wmNr0, CompoundMeterId meterId)
|
||||
{
|
||||
if (Batch.WaterMeters != null && Batch.WaterMeters.Count > wmNr0)
|
||||
{
|
||||
return Batch.WaterMeters[wmNr0].GetMeterTestRslt(name, meterId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when all tests were done
|
||||
|
||||
+62
-1
@@ -111,7 +111,7 @@ namespace Results
|
||||
if (SessionFactory == null)
|
||||
{
|
||||
DatabaseMigrationHelper.EnsureSchema(dbType, connectionString);
|
||||
SessionFactory = CreateSessionFactory();
|
||||
SessionFactory = CreateSessionFactory();
|
||||
}
|
||||
|
||||
return SessionFactory.OpenSession();
|
||||
@@ -273,8 +273,16 @@ namespace Results
|
||||
log.Debug(tstRslt.ToString(1));
|
||||
}
|
||||
|
||||
// Save batch, TestRslt, WaterMeter, MeterTestRslt, etc.
|
||||
session.SaveOrUpdate(batch);
|
||||
|
||||
// Important: after this, TestRslt.Id should be generated
|
||||
session.Flush();
|
||||
|
||||
// Optional table support
|
||||
SolveSaveCalibFactors(batch, session);
|
||||
|
||||
//Commit - store results
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (Exception exc)
|
||||
@@ -295,6 +303,51 @@ namespace Results
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void SolveSaveCalibFactors(Batch batch, ISession session)
|
||||
{
|
||||
bool hasCalibrationFactors = false;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
foreach (var tstRslt in batch.TestRslts)
|
||||
{
|
||||
if (tstRslt.CalibFactorResultsToSave != null &&
|
||||
tstRslt.CalibFactorResultsToSave.Count > 0)
|
||||
{
|
||||
hasCalibrationFactors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCalibrationFactors)
|
||||
{
|
||||
|
||||
foreach (var tstRslt in batch.TestRslts)
|
||||
{
|
||||
if (tstRslt.CalibFactorResultsToSave == null ||
|
||||
tstRslt.CalibFactorResultsToSave.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
foreach (var calib in tstRslt.CalibFactorResultsToSave)
|
||||
{
|
||||
calib.TestRslt = tstRslt;
|
||||
calib.ErrorStr = TestRsltCalibFactorHelper.Truncate(calib.ErrorStr, 240);
|
||||
|
||||
session.SaveOrUpdate(calib);
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(Exception exc)
|
||||
{
|
||||
log.ErrorFormat("DB - Cannot save calibration factors: {0}", exc.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Batch LoadBatch(int batchNr)
|
||||
{
|
||||
@@ -321,6 +374,14 @@ namespace Results
|
||||
batch.WaterMeters = session.QueryOver<WaterMeter>()
|
||||
.Where(x => (x.Batch.Id == batch.Id))
|
||||
.List();
|
||||
|
||||
|
||||
foreach (var tstRslt in batch.TestRslts)
|
||||
{
|
||||
tstRslt.CalibFactorResultsToSave = TestRsltCalibFactorHelper.GetByTestRsltId( session, tstRslt.Id);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return (batches.Count > 0) ? batches[0] : null;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Results.Entities
|
||||
{
|
||||
public class MeterTestCalibFactorRslt
|
||||
{
|
||||
public virtual int Id { get; protected set; }
|
||||
|
||||
public virtual MeterTestRslt MeterTestRslt { get; set; }
|
||||
|
||||
/// 1, 2, 3 - calculated calib factor index
|
||||
public virtual int CalibFactorIndex { get; set; }
|
||||
|
||||
/// Base calib factor originally set in meter
|
||||
public virtual int BaseCalibFactor { get; set; }
|
||||
|
||||
/// Newly calculated calib factor
|
||||
public virtual int CalculatedCalibFactor { get; set; }
|
||||
|
||||
/// true = calculated value was stored/written to meter
|
||||
public virtual bool Stored { get; set; }
|
||||
|
||||
public virtual string ErrorStr { get; set; } // max 240 chars
|
||||
|
||||
public virtual double TimeStart { get; set; }
|
||||
public virtual double TimeEnd { get; set; }
|
||||
|
||||
public virtual double CalibRawStart { get; set; }
|
||||
public virtual double CalibRawEnd { get; set; }
|
||||
|
||||
public virtual double Error { get; set; }
|
||||
|
||||
public MeterTestCalibFactorRslt()
|
||||
{
|
||||
Stored = false;
|
||||
ErrorStr = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,11 @@ namespace Results.Entities
|
||||
#endif
|
||||
|
||||
public virtual WaterMeter WaterMeter { get; set; } /// reference to the WaterMeter entity
|
||||
public virtual TestRslt TestRslt { get; set; } /// reference to the TestRslt entity
|
||||
public virtual TestRslt TestRslt { get; set; }
|
||||
|
||||
/// reference to the TestRslt entity
|
||||
|
||||
public virtual int Q3Channel { get; set; }
|
||||
|
||||
/// Wrappers
|
||||
public virtual string Name() { return TestRslt.Name(); }
|
||||
@@ -99,6 +103,7 @@ namespace Results.Entities
|
||||
public virtual WaterMeterData WaterMeterData() { return WaterMeter.WaterMeterData; }
|
||||
public virtual Batch Batch() { return WaterMeter.Batch; }
|
||||
|
||||
|
||||
public virtual bool IsPilotRslt()
|
||||
{
|
||||
return (CompoundMeterId == (byte)Common.CompoundMeterId.Single) ||
|
||||
|
||||
@@ -11,6 +11,32 @@ namespace Results.Entities
|
||||
{
|
||||
public class TestRslt
|
||||
{
|
||||
|
||||
/// <summary>Gets the three channel records for one physical meter, in channel order.</summary>
|
||||
public virtual IList<TestRsltCalibFactor> GetCalibrationFactors(WaterMeter meter)
|
||||
{
|
||||
if (meter == null) throw new ArgumentNullException(nameof(meter));
|
||||
lock (this)
|
||||
{
|
||||
if (CalibFactorResultsToSave == null) CalibFactorResultsToSave = new List<TestRsltCalibFactor>();
|
||||
var result = new List<TestRsltCalibFactor>(3);
|
||||
for (int channel = 1; channel <= 3; channel++)
|
||||
{
|
||||
TestRsltCalibFactor found = null;
|
||||
foreach (var row in CalibFactorResultsToSave)
|
||||
if (row.WaterMeterPosition == meter.WMPosition && row.CalibFactorIndex == channel)
|
||||
{ found = row; break; }
|
||||
if (found == null)
|
||||
{
|
||||
found = new TestRsltCalibFactor { TestRslt = this, WaterMeterPosition = meter.WMPosition, CalibFactorIndex = channel };
|
||||
CalibFactorResultsToSave.Add(found);
|
||||
}
|
||||
result.Add(found);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Identity
|
||||
public virtual int Id { get; protected set; }
|
||||
public virtual Batch Batch { get; set; }
|
||||
@@ -152,6 +178,11 @@ namespace Results.Entities
|
||||
public virtual int Counter4 { get; set; }
|
||||
public virtual int Counter5 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Not maped table - exist only in Genesis DB !!
|
||||
/// </summary>
|
||||
public virtual IList<TestRsltCalibFactor> CalibFactorResultsToSave { get; set; }
|
||||
|
||||
/// Wrappers
|
||||
public virtual string Name() { return Common.Utils.GetTestName(TestData.Name, TestData.Repeats, RepetitionNr); }
|
||||
public virtual string Key() { return string.Format("{0}~{1}~{2}~{3}", TestData.Name, Part, TestData.Repeats, RepetitionNr); } /// Unique key
|
||||
@@ -266,15 +297,27 @@ namespace Results.Entities
|
||||
|
||||
MethodClass = string.Empty;
|
||||
Remark = string.Empty;
|
||||
|
||||
CalibFactorResultsToSave = new List<TestRsltCalibFactor>();
|
||||
}
|
||||
|
||||
public TestRslt(Batch batch, TestData testData, int part, int repetitionNr)
|
||||
public TestRslt(
|
||||
Batch batch,
|
||||
TestData testData,
|
||||
int part,
|
||||
int repetitionNr)
|
||||
: this(batch, testData, part, repetitionNr, null)
|
||||
{
|
||||
}
|
||||
|
||||
public TestRslt(Batch batch, TestData testData, int part, int repetitionNr, List<TestRsltCalibFactor> calibFactor)
|
||||
: this()
|
||||
{
|
||||
Batch = batch;
|
||||
TestData = testData;
|
||||
Part = part;
|
||||
RepetitionNr = repetitionNr;
|
||||
CalibFactorResultsToSave = calibFactor != null ? new List<TestRsltCalibFactor>(calibFactor) : new List<TestRsltCalibFactor>();
|
||||
}
|
||||
|
||||
public virtual void CopyContentFrom(TestRslt src)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Results.Entities
|
||||
{
|
||||
public class TestRsltCalibFactor
|
||||
{
|
||||
public virtual int Id { get; protected set; }
|
||||
|
||||
public virtual TestRslt TestRslt { get; set; }
|
||||
|
||||
public virtual int WaterMeterPosition { get; set; }
|
||||
|
||||
public virtual int CalibFactorIndex { get; set; } // 1, 2, 3
|
||||
|
||||
public virtual bool IsCalibFactorValid { get; set; }
|
||||
|
||||
public virtual int BaseCalibFactor { get; set; }
|
||||
public virtual int CalculatedCalibFactor { get; set; }
|
||||
|
||||
public virtual bool Stored { get; set; }
|
||||
|
||||
public virtual string ErrorStr { get; set; } // varchar(240)
|
||||
|
||||
public virtual double TimeStart { get; set; }
|
||||
public virtual double TimeEnd { get; set; }
|
||||
|
||||
public virtual double CalibRawStart { get; set; }
|
||||
public virtual double CalibRawEnd { get; set; }
|
||||
|
||||
public virtual double Error { get; set; }
|
||||
public virtual double VolumeStart { get; set; }
|
||||
public virtual double VolumeEnd { get; set; }
|
||||
|
||||
public TestRsltCalibFactor()
|
||||
{
|
||||
Stored = false;
|
||||
ErrorStr = string.Empty;
|
||||
IsCalibFactorValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,19 @@ namespace Results.Entities
|
||||
#if IPERL
|
||||
public virtual double OrigCalibFactor { get; set; } /// Original iPerl calibration factor used during the test
|
||||
public virtual double CalibFactor { get; set; } /// iPerl calibration factor used during the test
|
||||
public virtual double CalibFactorNominal { get; set; } /// Raw calibration factor representing 100 % for this test result
|
||||
public virtual double CalibFactorPercentage
|
||||
{
|
||||
get
|
||||
{
|
||||
double nominalCalibFactor = CalibFactorNominal > 0.0 ? CalibFactorNominal : 4096.0;
|
||||
return CalibFactor * 100.0 / nominalCalibFactor;
|
||||
}
|
||||
}
|
||||
public virtual double CalibFactorCorrectionPercentage
|
||||
{
|
||||
get { return CalibFactorPercentage - 100.0; }
|
||||
}
|
||||
public virtual double OrigCalibFactorLNA{ get; set; } /// Original iPerl LNA calibration factor used during the test
|
||||
public virtual double CalibFactorLNA { get; set; } /// iPerl LNA calibration factor used during the test
|
||||
public virtual double Q2ErrWOCorrection { get; set; }
|
||||
@@ -111,6 +124,9 @@ namespace Results.Entities
|
||||
public virtual bool LastRecordIsNok { get; set; } /// Not mapped to DB !!! Previous record verification result
|
||||
public virtual bool PrintLabel { get; set; } /// Not mapped to DB !!!
|
||||
|
||||
/// mapped to DB !!! Q3 channel number
|
||||
public virtual int Q3Channel { get; set; }
|
||||
|
||||
public virtual WaterMeterData WaterMeterData { get; set; }
|
||||
public virtual Batch Batch { get; set; }
|
||||
public virtual IList<MeterTestRslt> MeterTestRslts { get; set; }
|
||||
@@ -493,6 +509,7 @@ namespace Results.Entities
|
||||
FWVersion = string.Empty;
|
||||
#endif
|
||||
PrintLabel = true;
|
||||
Q3Channel = 0; //No Q3 calibration by default
|
||||
}
|
||||
|
||||
|
||||
@@ -519,6 +536,7 @@ namespace Results.Entities
|
||||
#if IPERL
|
||||
OrigCalibFactor = src.OrigCalibFactor;
|
||||
CalibFactor = src.CalibFactor;
|
||||
CalibFactorNominal = src.CalibFactorNominal;
|
||||
OrigCalibFactorLNA = src.OrigCalibFactorLNA;
|
||||
CalibFactorLNA = src.CalibFactorLNA;
|
||||
Q2ErrWOCorrection = src.Q2ErrWOCorrection;
|
||||
@@ -556,6 +574,7 @@ namespace Results.Entities
|
||||
Workflow = src.Workflow; /// Not mapped to DB
|
||||
LastRecordIsNok = src.LastRecordIsNok; /// Not mapped to DB
|
||||
PrintLabel = src.PrintLabel; /// Not mapped to DB
|
||||
Q3Channel = src.Q3Channel; /// Mapped to DB
|
||||
|
||||
foreach (var mtr in MeterTestRslts)
|
||||
{
|
||||
@@ -597,9 +616,10 @@ namespace Results.Entities
|
||||
foreach (var mtr in MeterTestRslts)
|
||||
{
|
||||
if ((mtr.CompoundMeterId == (byte)CompoundMeterId.Single || mtr.CompoundMeterId == (byte)CompoundMeterId.Compound || mtr.CompoundMeterId == (byte)CompoundMeterId.HeatMeterEnergy)
|
||||
&& mtr.TestDone
|
||||
&& (mtr.Q3Channel != 0 ||
|
||||
(mtr.TestDone
|
||||
&& (mtr.Publish() != Publish.Never)
|
||||
&& (mtr.Publish() != Publish.Internal))
|
||||
&& (mtr.Publish() != Publish.Internal))))
|
||||
{
|
||||
testNames.Add(mtr.Name());
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace Results.Entities
|
||||
public virtual bool Compound { get; set; }
|
||||
public virtual bool HeatMeter { get; set; }
|
||||
public virtual int WMTypeId { get; set; } /// Mapped to DB only when ORACLE_DB is defined
|
||||
public virtual int Q3Channel { get; set; } /// Q3 Channel = 0 no set > 0 is Q3 calibration
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor, safe values
|
||||
@@ -59,6 +60,7 @@ namespace Results.Entities
|
||||
{
|
||||
PulsesPerLtr = 1;
|
||||
PulsesPerLtrAux = 1;
|
||||
Q3Channel = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -101,6 +103,7 @@ namespace Results.Entities
|
||||
Compound = oriWMData.Compound;
|
||||
HeatMeter = oriWMData.HeatMeter;
|
||||
WMTypeId = oriWMData.WMTypeId;
|
||||
Q3Channel = oriWMData.Q3Channel;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +149,7 @@ namespace Results.Entities
|
||||
if (Compound != wmd.Compound) return false;
|
||||
if (HeatMeter != wmd.HeatMeter) return false;
|
||||
if (WMTypeId != wmd.WMTypeId) return false;
|
||||
if (Q3Channel != wmd.Q3Channel) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Common;
|
||||
using log4net;
|
||||
|
||||
namespace Results.Entities.helpers
|
||||
{
|
||||
@@ -11,8 +10,6 @@ namespace Results.Entities.helpers
|
||||
/// </summary>
|
||||
public static class DatabaseMigrationHelper
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(DatabaseMigrationHelper));
|
||||
|
||||
public static void EnsureSchema(DBType dbType, string connectionString)
|
||||
{
|
||||
switch (dbType)
|
||||
@@ -32,38 +29,94 @@ namespace Results.Entities.helpers
|
||||
using (var conn = new MySqlConnection(connectionString))
|
||||
{
|
||||
conn.Open();
|
||||
using (var create = conn.CreateCommand())
|
||||
{
|
||||
create.CommandText = "CREATE TABLE IF NOT EXISTS TestRsltCalibFactor (Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, TestRsltId INT NOT NULL DEFAULT 0, WaterMeterPosition INT NOT NULL DEFAULT 0, IsCalibFactorValid INT NOT NULL DEFAULT 0, VolumeStart DOUBLE NOT NULL DEFAULT 0, VolumeEnd DOUBLE NOT NULL DEFAULT 0, CalibFactorIndex INT NOT NULL DEFAULT 0, BaseCalibFactor INT NOT NULL DEFAULT 0, CalculatedCalibFactor INT NOT NULL DEFAULT 0, Stored INT NOT NULL DEFAULT 0, ErrorStr VARCHAR(240) NULL, TimeStart DOUBLE NOT NULL DEFAULT 0, TimeEnd DOUBLE NOT NULL DEFAULT 0, CalibRawStart DOUBLE NOT NULL DEFAULT 0, CalibRawEnd DOUBLE NOT NULL DEFAULT 0, Error DOUBLE NOT NULL DEFAULT 0)";
|
||||
create.ExecuteNonQuery();
|
||||
}
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "TestRsltId", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "WaterMeterPosition", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "IsCalibFactorValid", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "VolumeStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "VolumeEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "CalibFactorIndex", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "BaseCalibFactor", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "CalculatedCalibFactor", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "Stored", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "ErrorStr", "VARCHAR(240) NULL");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "TimeStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "TimeEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "CalibRawStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "CalibRawEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRsltCalibFactor", "Error", "DOUBLE NOT NULL DEFAULT 0");
|
||||
using (var create = conn.CreateCommand())
|
||||
{
|
||||
create.CommandText = "CREATE TABLE IF NOT EXISTS MeterTestCalibFactorRslt (Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MeterTestRslt_id INT NOT NULL DEFAULT 0, CalibFactorIndex INT NOT NULL DEFAULT 0, BaseCalibFactor INT NOT NULL DEFAULT 0, CalculatedCalibFactor INT NOT NULL DEFAULT 0, Stored INT NOT NULL DEFAULT 0, ErrorStr VARCHAR(240) NULL, TimeStart DOUBLE NOT NULL DEFAULT 0, TimeEnd DOUBLE NOT NULL DEFAULT 0, CalibRawStart DOUBLE NOT NULL DEFAULT 0, CalibRawEnd DOUBLE NOT NULL DEFAULT 0, Error DOUBLE NOT NULL DEFAULT 0)";
|
||||
create.ExecuteNonQuery();
|
||||
}
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "MeterTestRslt_id", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "CalibFactorIndex", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "BaseCalibFactor", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "CalculatedCalibFactor", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "Stored", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "ErrorStr", "VARCHAR(240) NULL");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "TimeStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "TimeEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "CalibRawStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "CalibRawEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestCalibFactorRslt", "Error", "DOUBLE NOT NULL DEFAULT 0");
|
||||
|
||||
// Q3Channel migrations from develop/SLM-PT50_genesisDirectDecode_special_2.
|
||||
// Kept as a visible template only; the related Q3 logic is not part of this change.
|
||||
// EnsureColumnMySql(conn, "WaterMeterData", "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", "PulsesPerKilogram", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureMeterTestResultMassColumnsMySql(conn);
|
||||
EnsureColumnMySql(conn, "MeterTestRslt", "FlipMode", "INT NULL");
|
||||
EnsureColumnMySql(conn, "MeterTestRslt", "ExtraDataPath", "VARCHAR(255) NULL");
|
||||
EnsureMeterTestResultExtraColumnsMySql(conn);
|
||||
}
|
||||
}
|
||||
// Genesis Q3 result columns.
|
||||
EnsureColumnMySql(conn, "WaterMeterData", "Q3Channel", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "WaterMeter", "Q3Channel", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "MeterTestRslt", "Q3Channel", "INT NOT NULL DEFAULT 0");
|
||||
|
||||
private static void EnsureMeterTestResultExtraColumnsMySql(MySqlConnection conn)
|
||||
{
|
||||
for (int index = 1; index <= 9; index++)
|
||||
EnsureColumnMySql(conn, "MeterTestRslt", "X" + index, "FLOAT NOT NULL DEFAULT 0");
|
||||
}
|
||||
EnsureColumnMySql(conn, "TestRslt", "ConductMean", "FLOAT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRslt", "ConductStart", "FLOAT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRslt", "ConductEnd", "FLOAT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRslt", "ConductMin", "FLOAT NOT NULL DEFAULT 0");
|
||||
EnsureColumnMySql(conn, "TestRslt", "ConductMax", "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");
|
||||
}
|
||||
EnsureColumnMySql(conn, "MeterTestRslt", "FlipMode", "INT NULL");
|
||||
EnsureColumnMySql(conn, "WaterMeter", "CalibFactorNominal", "DOUBLE NOT NULL DEFAULT 4096");
|
||||
// Nepouzitelne pre teraz - nechavam kod pre buducnost
|
||||
//EnsureColumnMySql(conn, "MeterTestRslt", "CalibFactor", "FLOAT NOT NULL DEFAULT 0");
|
||||
//EnsureColumnTypeMySql(conn, "MeterTestRslt", "CalibFactor", "float", "FLOAT NOT NULL DEFAULT 0");
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureColumnTypeMySql(
|
||||
MySqlConnection conn,
|
||||
string tableName,
|
||||
string columnName,
|
||||
string expectedDataType,
|
||||
string columnDefinition)
|
||||
{
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
SELECT DATA_TYPE
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = @tableName
|
||||
AND COLUMN_NAME = @columnName";
|
||||
|
||||
cmd.Parameters.AddWithValue("@tableName", tableName);
|
||||
cmd.Parameters.AddWithValue("@columnName", columnName);
|
||||
|
||||
string currentDataType = cmd.ExecuteScalar() as string;
|
||||
if (string.Equals(currentDataType, expectedDataType, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
|
||||
using (var alter = conn.CreateCommand())
|
||||
{
|
||||
alter.CommandText = "ALTER TABLE `" + tableName + "` MODIFY COLUMN `" +
|
||||
columnName + "` " + columnDefinition;
|
||||
alter.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureColumnMySql(
|
||||
MySqlConnection conn,
|
||||
@@ -99,9 +152,7 @@ namespace Results.Entities.helpers
|
||||
alter.Transaction = transaction;
|
||||
alter.CommandText = "ALTER TABLE `" + tableName + "` ADD COLUMN `" +
|
||||
columnName + "` " + columnDefinition;
|
||||
alter.ExecuteNonQuery();
|
||||
log.WarnFormat("Results DB migration: added {0}.{1} ({2}).",
|
||||
tableName, columnName, columnDefinition);
|
||||
alter.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,35 +171,53 @@ namespace Results.Entities.helpers
|
||||
using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=" + databaseFile))
|
||||
{
|
||||
conn.Open();
|
||||
using (var create = conn.CreateCommand())
|
||||
{
|
||||
create.CommandText = "CREATE TABLE IF NOT EXISTS TestRsltCalibFactor (Id INTEGER PRIMARY KEY AUTOINCREMENT, TestRsltId INT NOT NULL DEFAULT 0, WaterMeterPosition INT NOT NULL DEFAULT 0, IsCalibFactorValid INT NOT NULL DEFAULT 0, VolumeStart DOUBLE NOT NULL DEFAULT 0, VolumeEnd DOUBLE NOT NULL DEFAULT 0, CalibFactorIndex INT NOT NULL DEFAULT 0, BaseCalibFactor INT NOT NULL DEFAULT 0, CalculatedCalibFactor INT NOT NULL DEFAULT 0, Stored INT NOT NULL DEFAULT 0, ErrorStr VARCHAR(240) NULL, TimeStart DOUBLE NOT NULL DEFAULT 0, TimeEnd DOUBLE NOT NULL DEFAULT 0, CalibRawStart DOUBLE NOT NULL DEFAULT 0, CalibRawEnd DOUBLE NOT NULL DEFAULT 0, Error DOUBLE NOT NULL DEFAULT 0)";
|
||||
create.ExecuteNonQuery();
|
||||
}
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "TestRsltId", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "WaterMeterPosition", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "IsCalibFactorValid", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "VolumeStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "VolumeEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "CalibFactorIndex", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "BaseCalibFactor", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "CalculatedCalibFactor", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "Stored", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "ErrorStr", "VARCHAR(240) NULL");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "TimeStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "TimeEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "CalibRawStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "CalibRawEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "TestRsltCalibFactor", "Error", "DOUBLE NOT NULL DEFAULT 0");
|
||||
using (var create = conn.CreateCommand())
|
||||
{
|
||||
create.CommandText = "CREATE TABLE IF NOT EXISTS MeterTestCalibFactorRslt (Id INTEGER PRIMARY KEY AUTOINCREMENT, MeterTestRslt_id INT NOT NULL DEFAULT 0, CalibFactorIndex INT NOT NULL DEFAULT 0, BaseCalibFactor INT NOT NULL DEFAULT 0, CalculatedCalibFactor INT NOT NULL DEFAULT 0, Stored INT NOT NULL DEFAULT 0, ErrorStr VARCHAR(240) NULL, TimeStart DOUBLE NOT NULL DEFAULT 0, TimeEnd DOUBLE NOT NULL DEFAULT 0, CalibRawStart DOUBLE NOT NULL DEFAULT 0, CalibRawEnd DOUBLE NOT NULL DEFAULT 0, Error DOUBLE NOT NULL DEFAULT 0)";
|
||||
create.ExecuteNonQuery();
|
||||
}
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "MeterTestRslt_id", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "CalibFactorIndex", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "BaseCalibFactor", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "CalculatedCalibFactor", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "Stored", "INT NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "ErrorStr", "VARCHAR(240) NULL");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "TimeStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "TimeEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "CalibRawStart", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "CalibRawEnd", "DOUBLE NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestCalibFactorRslt", "Error", "DOUBLE NOT NULL DEFAULT 0");
|
||||
|
||||
// Q3Channel migrations from develop/SLM-PT50_genesisDirectDecode_special_2.
|
||||
// Kept as a visible template only; the related Q3 logic is not part of this change.
|
||||
// EnsureColumnSQLite(conn, "WaterMeterData", "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", "PulsesPerKilogram", "REAL NOT NULL DEFAULT 0");
|
||||
EnsureMeterTestResultMassColumnsSQLite(conn);
|
||||
EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL");
|
||||
EnsureColumnSQLite(conn, "MeterTestRslt", "ExtraDataPath", "TEXT NULL");
|
||||
EnsureMeterTestResultExtraColumnsSQLite(conn);
|
||||
}
|
||||
}
|
||||
// Genesis Q3 result columns.
|
||||
EnsureColumnSQLite(conn, "WaterMeterData", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "WaterMeter", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
|
||||
EnsureColumnSQLite(conn, "MeterTestRslt", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
|
||||
|
||||
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");
|
||||
}
|
||||
EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL");
|
||||
EnsureColumnSQLite(conn, "WaterMeter", "CalibFactorNominal", "REAL NOT NULL DEFAULT 4096");
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureColumnSQLite(
|
||||
System.Data.SQLite.SQLiteConnection conn,
|
||||
@@ -182,9 +251,7 @@ namespace Results.Entities.helpers
|
||||
{
|
||||
alter.CommandText = "ALTER TABLE " + tableName + " ADD COLUMN " +
|
||||
columnName + " " + columnDefinition;
|
||||
alter.ExecuteNonQuery();
|
||||
log.WarnFormat("Results DB migration: added {0}.{1} ({2}).",
|
||||
tableName, columnName, columnDefinition);
|
||||
alter.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
using System.Collections.Generic;
|
||||
using log4net;
|
||||
using NHibernate;
|
||||
|
||||
namespace Results.Entities.helpers
|
||||
{
|
||||
public static class TestRsltCalibFactorHelper
|
||||
{
|
||||
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(TestRsltCalibFactorHelper));
|
||||
|
||||
public static bool TableExists(ISession session)
|
||||
{
|
||||
try
|
||||
{
|
||||
session.CreateSQLQuery( "SELECT 1 FROM TestRsltCalibFactor LIMIT 1")
|
||||
.UniqueResult();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteByTestRsltIdNoTransaction(
|
||||
ISession session,
|
||||
int testRsltId)
|
||||
{
|
||||
log.DebugFormat("Deleting TestRsltCalibFactor records for TestRsltId: {0}", testRsltId);
|
||||
session.CreateSQLQuery(@" DELETE FROM TestRsltCalibFactor WHERE TestRsltId = :testRsltId")
|
||||
.SetParameter("testRsltId", testRsltId)
|
||||
.ExecuteUpdate();
|
||||
}
|
||||
|
||||
public static IList<TestRsltCalibFactor> GetByTestRsltId(
|
||||
ISession session,
|
||||
int testRsltId)
|
||||
{
|
||||
return session.QueryOver<TestRsltCalibFactor>()
|
||||
.Where(x => x.TestRslt.Id == testRsltId)
|
||||
.OrderBy(x => x.CalibFactorIndex).Asc
|
||||
.List();
|
||||
}
|
||||
|
||||
public static string Truncate(string value, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return string.Empty;
|
||||
|
||||
return value.Length <= maxLength
|
||||
? value
|
||||
: value.Substring(0, maxLength);
|
||||
}
|
||||
|
||||
public static void CreateTableIfNotExists(ISession session)
|
||||
{
|
||||
if (TableExists(session))
|
||||
return;
|
||||
|
||||
string sql;
|
||||
|
||||
if (DB.DbType == Common.DBType.MySql)
|
||||
{
|
||||
sql = @"
|
||||
CREATE TABLE IF NOT EXISTS TestRsltCalibFactor (
|
||||
Id INT NOT NULL AUTO_INCREMENT,
|
||||
TestRsltId INT NOT NULL,
|
||||
CalibFactorIndex INT NOT NULL,
|
||||
BaseCalibFactor INT NOT NULL,
|
||||
CalculatedCalibFactor INT NOT NULL,
|
||||
IsCalibFactorValid BIT NOT NULL,
|
||||
Stored BIT NOT NULL,
|
||||
ErrorStr VARCHAR(240) NULL,
|
||||
TimeStart DOUBLE NOT NULL,
|
||||
TimeEnd DOUBLE NOT NULL,
|
||||
CalibRawStart DOUBLE NOT NULL,
|
||||
CalibRawEnd DOUBLE NOT NULL,
|
||||
Error DOUBLE NOT NULL,
|
||||
VolumeStart DOUBLE NOT NULL,
|
||||
VolumeEnd DOUBLE NOT NULL,
|
||||
PRIMARY KEY (Id),
|
||||
INDEX IX_TestRsltCalibFactor_TestRsltId (TestRsltId),
|
||||
CONSTRAINT FK_TestRsltCalibFactor_TestRslt
|
||||
FOREIGN KEY (TestRsltId) REFERENCES TestRslt(Id)
|
||||
ON DELETE CASCADE
|
||||
);";
|
||||
}
|
||||
else
|
||||
{
|
||||
sql = @"
|
||||
CREATE TABLE IF NOT EXISTS TestRsltCalibFactor (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
TestRsltId INTEGER NOT NULL,
|
||||
CalibFactorIndex INTEGER NOT NULL,
|
||||
BaseCalibFactor INTEGER NOT NULL,
|
||||
CalculatedCalibFactor INTEGER NOT NULL,
|
||||
IsCalibFactorValid INTEGER NOT NULL,
|
||||
Stored INTEGER NOT NULL,
|
||||
ErrorStr VARCHAR(240) NULL,
|
||||
TimeStart DOUBLE NOT NULL,
|
||||
TimeEnd DOUBLE NOT NULL,
|
||||
CalibRawStart DOUBLE NOT NULL,
|
||||
CalibRawEnd DOUBLE NOT NULL,
|
||||
Error DOUBLE NOT NULL,
|
||||
VolumeStart DOUBLE NOT NULL,
|
||||
VolumeEnd DOUBLE NOT NULL,
|
||||
FOREIGN KEY (TestRsltId) REFERENCES TestRslt(Id) ON DELETE CASCADE
|
||||
);";
|
||||
}
|
||||
|
||||
log.InfoFormat("Creating TestRsltCalibFactor table: {0}", sql);
|
||||
session.CreateSQLQuery(sql).ExecuteUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ namespace Results.Forms
|
||||
|
||||
public void Update(Results.Entities.WaterMeter wMtr)
|
||||
{
|
||||
if (wMtr == null || wMtr.Disabled)
|
||||
if (wMtr == null || (wMtr.Disabled && wMtr.Q3Channel == 0))
|
||||
{
|
||||
/// Water meter position is disabled
|
||||
this.disabled = true;
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Results.Forms
|
||||
|
||||
public void Update(Results.Entities.WaterMeter wMtr)
|
||||
{
|
||||
if (wMtr == null || wMtr.Disabled)
|
||||
if (wMtr == null || (wMtr.Disabled && wMtr.Q3Channel == 0))
|
||||
{
|
||||
/// Water meter position is disabled
|
||||
this.disabled = true;
|
||||
@@ -114,8 +114,11 @@ namespace Results.Forms
|
||||
lView.Items.Clear();
|
||||
foreach (var mtr in wMtr.MeterTestRslts)
|
||||
{
|
||||
if (mtr != null && mtr.IsPilotRslt() && mtr.TestDone && mtr.Publish() != Publish.Never
|
||||
&& mtr.Publish() != Publish.Internal)
|
||||
if (mtr != null && mtr.IsPilotRslt() &&
|
||||
(mtr.Q3Channel!=0 || (mtr.TestDone &&
|
||||
mtr.Publish() != Publish.Never &&
|
||||
mtr.Publish() != Publish.Internal)
|
||||
) )
|
||||
{
|
||||
ListViewItem lvi = new ListViewItem(testNames[ix++]);
|
||||
|
||||
|
||||
+385
-321
@@ -1,4 +1,8 @@
|
||||
using System;
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
@@ -11,9 +15,9 @@ namespace Results.Forms
|
||||
public partial class ResultsConfigCtrl : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// ListViewEx columns
|
||||
/// ListViewEx columns.
|
||||
/// </summary>
|
||||
enum Column
|
||||
private enum Column
|
||||
{
|
||||
Item,
|
||||
Caption,
|
||||
@@ -22,83 +26,121 @@ namespace Results.Forms
|
||||
Precision,
|
||||
Width,
|
||||
Alignment,
|
||||
Merge,
|
||||
TestID,
|
||||
Count,
|
||||
Merge,
|
||||
TestID,
|
||||
Count,
|
||||
}
|
||||
|
||||
Control[] editors; /// all editors except of units
|
||||
ComboBox unitsCB; /// units combo box
|
||||
private Control[] editors;
|
||||
private ComboBox unitsCB;
|
||||
private bool unlocked;
|
||||
private string captionColumnText;
|
||||
|
||||
public MetersKind MetersKind;
|
||||
public IList<WMeterRsltItemSpec> SelectedItems;
|
||||
public MetersKind MetersKind;
|
||||
public IList<WMeterRsltItemSpec> SelectedItems;
|
||||
public bool SupressTestIDColumn;
|
||||
public bool Unlocked
|
||||
{
|
||||
set
|
||||
{
|
||||
availableTabControl.Enabled = value;
|
||||
selectedResultsListViewEx.Enabled = value;
|
||||
addButton.Enabled = value;
|
||||
removeButton.Enabled = value;
|
||||
removeAllButton.Enabled = value;
|
||||
upButton.Enabled = value;
|
||||
downButton.Enabled = value;
|
||||
unlocked = value;
|
||||
}
|
||||
get { return unlocked; }
|
||||
}
|
||||
bool unlocked;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional callback used to select a Caption value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When null, Caption keeps the original TextBox editor. When assigned,
|
||||
/// clicking Caption invokes the callback. Returning null means Cancel.
|
||||
/// </remarks>
|
||||
public Func<WMeterRsltItemSpec, string> CaptionPicker
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ResultsConfigCtrl(bool supressTestIDColumn)
|
||||
: this()
|
||||
{
|
||||
this.SupressTestIDColumn = supressTestIDColumn;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the Caption column header text.
|
||||
/// </summary>
|
||||
public string CaptionColumnText
|
||||
{
|
||||
get { return captionColumnText; }
|
||||
set
|
||||
{
|
||||
captionColumnText = value;
|
||||
|
||||
if (selectedResultsListViewEx.Columns.Count > (int)Column.Caption)
|
||||
{
|
||||
selectedResultsListViewEx.Columns[(int)Column.Caption].Text =
|
||||
string.IsNullOrWhiteSpace(value)
|
||||
? Strings.Caption
|
||||
: value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Unlocked
|
||||
{
|
||||
set
|
||||
{
|
||||
availableTabControl.Enabled = value;
|
||||
selectedResultsListViewEx.Enabled = value;
|
||||
addButton.Enabled = value;
|
||||
removeButton.Enabled = value;
|
||||
removeAllButton.Enabled = value;
|
||||
upButton.Enabled = value;
|
||||
downButton.Enabled = value;
|
||||
unlocked = value;
|
||||
}
|
||||
get { return unlocked; }
|
||||
}
|
||||
|
||||
public ResultsConfigCtrl(bool supressTestIDColumn)
|
||||
: this()
|
||||
{
|
||||
SupressTestIDColumn = supressTestIDColumn;
|
||||
}
|
||||
|
||||
public ResultsConfigCtrl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
void Localize()
|
||||
{
|
||||
Text = Strings.Configuration;
|
||||
availableResultsLabel.Text = Strings.Available_results;
|
||||
availableTabControl.TabPages[0].Text = Strings.Quantity;
|
||||
availableTabControl.TabPages[1].Text = Strings.Category;
|
||||
availableTabControl.TabPages[2].Text = "A...Z";
|
||||
|
||||
void Localize()
|
||||
{
|
||||
Text = Strings.Configuration;
|
||||
availableResultsLabel.Text = Strings.Available_results;
|
||||
availableTabControl.TabPages[0].Text = Strings.Quantity;
|
||||
availableTabControl.TabPages[1].Text = Strings.Category;
|
||||
availableTabControl.TabPages[2].Text = "A...Z";
|
||||
selectedResultsLabel.Text = Strings.Selected_results;
|
||||
|
||||
selectedResultsLabel.Text = Strings.Selected_results;
|
||||
|
||||
addButton.Text = Strings.Add;
|
||||
removeButton.Text = Strings.Remove;
|
||||
removeAllButton.Text = Strings.Remove_all;
|
||||
addButton.Text = Strings.Add;
|
||||
removeButton.Text = Strings.Remove;
|
||||
removeAllButton.Text = Strings.Remove_all;
|
||||
upButton.Text = Strings.UpBtnText;
|
||||
downButton.Text = Strings.DownBtnText;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResultsConfigCtrl_Load(object sender, EventArgs e)
|
||||
{
|
||||
Localize();
|
||||
private void ResultsConfigCtrl_Load(object sender, EventArgs e)
|
||||
{
|
||||
Localize();
|
||||
|
||||
/// Add columns to ListViewEx
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Item, Width = 120 });
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Caption });
|
||||
selectedResultsListViewEx.Columns.Add(
|
||||
new ColumnHeader
|
||||
{
|
||||
Text = string.IsNullOrWhiteSpace(captionColumnText)
|
||||
? Strings.Caption
|
||||
: captionColumnText
|
||||
});
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Units });
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Format });
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Precision });
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Width });
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Alignment });
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Merge });
|
||||
if (!SupressTestIDColumn)
|
||||
{
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Test_ID });
|
||||
}
|
||||
|
||||
/// Create controls used by ListViewEx to edit items
|
||||
if (!SupressTestIDColumn)
|
||||
{
|
||||
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Test_ID });
|
||||
}
|
||||
|
||||
unitsCB = new ComboBox();
|
||||
|
||||
var alignmentCB = new ComboBox();
|
||||
@@ -112,17 +154,18 @@ namespace Results.Forms
|
||||
mergeCB.Items.Add(Strings.Yes);
|
||||
|
||||
editors = new Control[]
|
||||
{
|
||||
null,
|
||||
new TextBox(), /// caption
|
||||
unitsCB,
|
||||
new TextBox(), /// format
|
||||
new TextBox(), /// precision
|
||||
new TextBox(), /// width
|
||||
alignmentCB,
|
||||
mergeCB,
|
||||
new TextBox(), /// testID
|
||||
};
|
||||
{
|
||||
null,
|
||||
new TextBox(),
|
||||
unitsCB,
|
||||
new TextBox(),
|
||||
new TextBox(),
|
||||
new TextBox(),
|
||||
alignmentCB,
|
||||
mergeCB,
|
||||
new TextBox(),
|
||||
};
|
||||
|
||||
foreach (var edi in editors)
|
||||
{
|
||||
if (edi != null)
|
||||
@@ -132,31 +175,59 @@ namespace Results.Forms
|
||||
}
|
||||
}
|
||||
|
||||
selectedResultsListViewEx.SubItemClicked += new SubItemEventHandler(selectedResultsListViewEx_SubItemClicked);
|
||||
selectedResultsListViewEx.SubItemEndEditing += new SubItemEndEditingEventHandler(selectedResultsListViewEx_SubItemEndEditing);
|
||||
|
||||
availableByQuantityTreeView.ShowNodeToolTips = true;
|
||||
availableByCategoryTreeView.ShowNodeToolTips = true;
|
||||
availableAlphabeticTreeView.ShowNodeToolTips = true;
|
||||
selectedResultsListViewEx.SubItemClicked +=
|
||||
new SubItemEventHandler(selectedResultsListViewEx_SubItemClicked);
|
||||
|
||||
selectedResultsListViewEx.SubItemEndEditing +=
|
||||
new SubItemEndEditingEventHandler(selectedResultsListViewEx_SubItemEndEditing);
|
||||
|
||||
availableByQuantityTreeView.ShowNodeToolTips = true;
|
||||
availableByCategoryTreeView.ShowNodeToolTips = true;
|
||||
availableAlphabeticTreeView.ShowNodeToolTips = true;
|
||||
|
||||
RedrawAvailable();
|
||||
RedrawSelected();
|
||||
}
|
||||
RedrawSelected();
|
||||
}
|
||||
|
||||
void selectedResultsListViewEx_SubItemClicked(object sender, SubItemEventArgs e)
|
||||
{
|
||||
if (e.SubItem == (int)Column.Units)
|
||||
{
|
||||
Quantity quantity = (e.Item.Tag as WMeterRsltItemSpec).Quantity;
|
||||
unitsCB.Items.Clear();
|
||||
unitsCB.Items.Add(Unit.None.ToDescription()); /// "---"
|
||||
for (Unit u = (Unit)1; u < Unit.Count; u++)
|
||||
{
|
||||
if (Units.IsQuantity(u, quantity)) unitsCB.Items.Add(u.ToDescription());
|
||||
}
|
||||
selectedResultsListViewEx.StartEditing(unitsCB, e.Item, e.SubItem);
|
||||
}
|
||||
else if ((e.SubItem > 0) && (e.SubItem < (int)(SupressTestIDColumn ? Column.TestID : Column.Count)))
|
||||
if (e.SubItem == (int)Column.Caption && CaptionPicker != null)
|
||||
{
|
||||
WMeterRsltItemSpec item = e.Item.Tag as WMeterRsltItemSpec;
|
||||
if (item == null) return;
|
||||
|
||||
string selectedCaption = CaptionPicker(item);
|
||||
|
||||
if (selectedCaption != null)
|
||||
{
|
||||
item.Caption = selectedCaption;
|
||||
e.Item.SubItems[e.SubItem].Text = selectedCaption;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.SubItem == (int)Column.Units)
|
||||
{
|
||||
WMeterRsltItemSpec item = e.Item.Tag as WMeterRsltItemSpec;
|
||||
if (item == null) return;
|
||||
|
||||
Quantity quantity = item.Quantity;
|
||||
unitsCB.Items.Clear();
|
||||
unitsCB.Items.Add(Unit.None.ToDescription());
|
||||
|
||||
for (Unit u = (Unit)1; u < Unit.Count; u++)
|
||||
{
|
||||
if (Units.IsQuantity(u, quantity))
|
||||
{
|
||||
unitsCB.Items.Add(u.ToDescription());
|
||||
}
|
||||
}
|
||||
|
||||
selectedResultsListViewEx.StartEditing(unitsCB, e.Item, e.SubItem);
|
||||
}
|
||||
else if ((e.SubItem > 0) &&
|
||||
(e.SubItem < (int)(SupressTestIDColumn ? Column.TestID : Column.Count)))
|
||||
{
|
||||
selectedResultsListViewEx.StartEditing(editors[e.SubItem], e.Item, e.SubItem);
|
||||
}
|
||||
@@ -169,30 +240,39 @@ namespace Results.Forms
|
||||
|
||||
switch ((Column)e.SubItem)
|
||||
{
|
||||
case Column.Caption: item.Caption = e.DisplayText; return;
|
||||
case Column.Caption:
|
||||
item.Caption = e.DisplayText;
|
||||
return;
|
||||
|
||||
case Column.Units:
|
||||
for (Unit u = 0; u < Unit.Count; u++)
|
||||
{
|
||||
if (u.ToDescription().Equals(unitsCB.Text))
|
||||
if (u.ToDescription().Equals(unitsCB.Text))
|
||||
{
|
||||
item.Units = u;
|
||||
return; /// OK
|
||||
return;
|
||||
}
|
||||
}
|
||||
break; /// Error
|
||||
break;
|
||||
|
||||
case Column.Format: item.Format = e.DisplayText; return;
|
||||
case Column.Precision: item.Precision = e.DisplayText; return;
|
||||
case Column.Width:
|
||||
{
|
||||
int width;
|
||||
if (Int32.TryParse(editors[e.SubItem].Text, out width) && width >= 0)
|
||||
{
|
||||
item.Width = width;
|
||||
return; /// OK
|
||||
}
|
||||
break; /// Error
|
||||
}
|
||||
case Column.Format:
|
||||
item.Format = e.DisplayText;
|
||||
return;
|
||||
|
||||
case Column.Precision:
|
||||
item.Precision = e.DisplayText;
|
||||
return;
|
||||
|
||||
case Column.Width:
|
||||
{
|
||||
int width;
|
||||
if (Int32.TryParse(editors[e.SubItem].Text, out width) && width >= 0)
|
||||
{
|
||||
item.Width = width;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Column.Alignment:
|
||||
for (Alignment a = 0; a < Alignment.Count; a++)
|
||||
@@ -203,7 +283,7 @@ namespace Results.Forms
|
||||
return;
|
||||
}
|
||||
}
|
||||
break; /// Error
|
||||
break;
|
||||
|
||||
case Column.Merge:
|
||||
if (editors[e.SubItem].Text == Strings.Yes)
|
||||
@@ -216,254 +296,242 @@ namespace Results.Forms
|
||||
item.Merge = false;
|
||||
return;
|
||||
}
|
||||
break; /// Error
|
||||
break;
|
||||
|
||||
case Column.TestID: item.TestID = e.DisplayText; return;
|
||||
|
||||
default:
|
||||
return; /// OK
|
||||
case Column.TestID:
|
||||
item.TestID = e.DisplayText;
|
||||
return;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
e.DisplayText = e.Item.SubItems[e.SubItem].Text;
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
void RedrawAvailable()
|
||||
{
|
||||
RedrawByQuantity(availableByQuantityTreeView);
|
||||
RedrawByCategory(availableByCategoryTreeView);
|
||||
RedrawInAlphabeticOrder(availableAlphabeticTreeView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redraw available items (right hand side)
|
||||
/// </summary>
|
||||
void RedrawAvailable()
|
||||
{
|
||||
RedrawByQuantity(availableByQuantityTreeView);
|
||||
RedrawByCategory(availableByCategoryTreeView);
|
||||
RedrawInAlphabeticOrder(availableAlphabeticTreeView);
|
||||
}
|
||||
void RedrawInAlphabeticOrder(TreeView treeView)
|
||||
{
|
||||
treeView.Nodes.Clear();
|
||||
|
||||
IList<WMeterRsltItemSpec> alphabeticList =
|
||||
WMeterRsltItemSpec.AllItems.OrderBy(x => x.Name).ToList();
|
||||
|
||||
void RedrawInAlphabeticOrder(TreeView treeView)
|
||||
{
|
||||
treeView.Nodes.Clear();
|
||||
foreach (var item in alphabeticList)
|
||||
{
|
||||
TreeNode node = new TreeNode(item.Name);
|
||||
node.Tag = item;
|
||||
node.ToolTipText = item.ToolTipText;
|
||||
treeView.Nodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
IList<WMeterRsltItemSpec> alphabeticlList = WMeterRsltItemSpec.AllItems.OrderBy(x => x.Name).ToList();
|
||||
///
|
||||
foreach (var item in alphabeticlList)
|
||||
{
|
||||
TreeNode node = new TreeNode(item.Name);
|
||||
node.Tag = item;
|
||||
node.ToolTipText = item.ToolTipText;
|
||||
treeView.Nodes.Add(node);
|
||||
}
|
||||
}
|
||||
void RedrawByQuantity(TreeView treeView)
|
||||
{
|
||||
treeView.Nodes.Clear();
|
||||
|
||||
IList<Quantity> quantities = new List<Quantity>();
|
||||
for (Quantity q = 0; q < Quantity.Count; q++) quantities.Add(q);
|
||||
|
||||
void RedrawByQuantity(TreeView treeView)
|
||||
{
|
||||
treeView.Nodes.Clear();
|
||||
IList<Quantity> sortedQuantities =
|
||||
quantities.OrderBy(x => x.ToDescription()).ToList();
|
||||
|
||||
IList<Quantity> quantities = new List<Quantity>();
|
||||
for (Quantity q = 0; q < Quantity.Count; q++) quantities.Add(q);
|
||||
foreach (var q in sortedQuantities)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
IList<Quantity> sortedQuantities = quantities.OrderBy(x => x.ToDescription()).ToList();
|
||||
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
||||
{
|
||||
if (ri.Quantity == q) n++;
|
||||
}
|
||||
|
||||
foreach (var q in sortedQuantities)
|
||||
{
|
||||
int n = 0;
|
||||
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
||||
{
|
||||
if (ri.Quantity == q) n++;
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
TreeNode[] array = new TreeNode[n];
|
||||
int i = 0;
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
TreeNode[] array = new TreeNode[n];
|
||||
int i = 0;
|
||||
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
||||
{
|
||||
if (ri.Quantity == q)
|
||||
{
|
||||
TreeNode node = new TreeNode(ri.Name);
|
||||
node.Tag = ri;
|
||||
node.ToolTipText = ri.ToolTipText;
|
||||
array[i++] = node;
|
||||
}
|
||||
}
|
||||
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
||||
{
|
||||
if (ri.Quantity == q)
|
||||
{
|
||||
TreeNode node = new TreeNode(ri.Name);
|
||||
node.Tag = ri;
|
||||
node.ToolTipText = ri.ToolTipText;
|
||||
array[i++] = node;
|
||||
}
|
||||
}
|
||||
|
||||
treeView.Nodes.Add(new TreeNode(q.ToDescription(), array));
|
||||
}
|
||||
}
|
||||
}
|
||||
treeView.Nodes.Add(new TreeNode(q.ToDescription(), array));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RedrawByCategory(TreeView treeView)
|
||||
{
|
||||
treeView.Nodes.Clear();
|
||||
|
||||
void RedrawByCategory(TreeView treeView)
|
||||
{
|
||||
treeView.Nodes.Clear();
|
||||
IList<ItemCategory> categories = new List<ItemCategory>();
|
||||
for (ItemCategory c = 0; c < ItemCategory.Count; c++) categories.Add(c);
|
||||
|
||||
IList<ItemCategory> categories = new List<ItemCategory>();
|
||||
for (ItemCategory c = 0; c < ItemCategory.Count; c++) categories.Add(c);
|
||||
IList<ItemCategory> sortedCategories =
|
||||
categories.OrderBy(x => x.ToDescription()).ToList();
|
||||
|
||||
IList<ItemCategory> sortedCategories = categories.OrderBy(x => x.ToDescription()).ToList();
|
||||
foreach (var c in sortedCategories)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
foreach (var c in sortedCategories)
|
||||
{
|
||||
int n = 0;
|
||||
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
||||
{
|
||||
if (ri.Category == c) n++;
|
||||
}
|
||||
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
||||
{
|
||||
if (ri.Category == c) n++;
|
||||
}
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
TreeNode[] array = new TreeNode[n];
|
||||
int i = 0;
|
||||
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
||||
{
|
||||
if (ri.Category == c)
|
||||
{
|
||||
TreeNode node = new TreeNode(ri.Name);
|
||||
node.Tag = ri;
|
||||
node.ToolTipText = ri.ToolTipText;
|
||||
array[i++] = node;
|
||||
}
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
TreeNode[] array = new TreeNode[n];
|
||||
int i = 0;
|
||||
|
||||
treeView.Nodes.Add(new TreeNode(c.ToDescription(), array));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var ri in WMeterRsltItemSpec.AllItems)
|
||||
{
|
||||
if (ri.Category == c)
|
||||
{
|
||||
TreeNode node = new TreeNode(ri.Name);
|
||||
node.Tag = ri;
|
||||
node.ToolTipText = ri.ToolTipText;
|
||||
array[i++] = node;
|
||||
}
|
||||
}
|
||||
|
||||
treeView.Nodes.Add(new TreeNode(c.ToDescription(), array));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redraw selected items (right hand side)
|
||||
/// </summary>
|
||||
void RedrawSelected()
|
||||
{
|
||||
selectedResultsListViewEx.Items.Clear();
|
||||
void RedrawSelected()
|
||||
{
|
||||
selectedResultsListViewEx.Items.Clear();
|
||||
|
||||
if (SelectedItems == null) return;
|
||||
if (SelectedItems == null) return;
|
||||
|
||||
foreach (var item in SelectedItems)
|
||||
{
|
||||
ListViewItem lvi = new ListViewItem(item.Name); /// Item
|
||||
foreach (var item in SelectedItems)
|
||||
{
|
||||
ListViewItem lvi = new ListViewItem(item.Name);
|
||||
lvi.Tag = item;
|
||||
lvi.SubItems.Add(item.Caption); /// Header
|
||||
lvi.SubItems.Add(item.Units.ToDescription()); /// Units
|
||||
lvi.SubItems.Add(item.Format); /// Format
|
||||
lvi.SubItems.Add(item.Precision); /// Precision
|
||||
lvi.SubItems.Add(item.Width.ToString()); /// Width
|
||||
lvi.SubItems.Add(item.Alignment.ToDescription()); /// Alignment
|
||||
lvi.SubItems.Add(item.Merge ? Strings.Yes : Strings.No); /// Merge
|
||||
if (!SupressTestIDColumn)
|
||||
{
|
||||
lvi.SubItems.Add(item.TestID); /// TestID
|
||||
}
|
||||
lvi.SubItems.Add(item.Caption);
|
||||
lvi.SubItems.Add(item.Units.ToDescription());
|
||||
lvi.SubItems.Add(item.Format);
|
||||
lvi.SubItems.Add(item.Precision);
|
||||
lvi.SubItems.Add(item.Width.ToString());
|
||||
lvi.SubItems.Add(item.Alignment.ToDescription());
|
||||
lvi.SubItems.Add(item.Merge ? Strings.Yes : Strings.No);
|
||||
|
||||
selectedResultsListViewEx.Items.Add(lvi);
|
||||
}
|
||||
}
|
||||
if (!SupressTestIDColumn)
|
||||
{
|
||||
lvi.SubItems.Add(item.TestID);
|
||||
}
|
||||
|
||||
selectedResultsListViewEx.Items.Add(lvi);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSelectedFromView()
|
||||
{
|
||||
}
|
||||
|
||||
void addButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
switch (availableTabControl.SelectedIndex)
|
||||
{
|
||||
case 0:
|
||||
availableByQuantityTreeView_DoubleClick(this, null);
|
||||
break;
|
||||
case 1:
|
||||
availableByCategoryTreeView_DoubleClick(this, null);
|
||||
break;
|
||||
case 2:
|
||||
availableAlphabeticTreeView_DoubleClick(this, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void addButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
switch (availableTabControl.SelectedIndex)
|
||||
{
|
||||
case 0:
|
||||
availableByQuantityTreeView_DoubleClick(this, null);
|
||||
break;
|
||||
case 1:
|
||||
availableByCategoryTreeView_DoubleClick(this, null);
|
||||
break;
|
||||
case 2:
|
||||
availableAlphabeticTreeView_DoubleClick(this, null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void availableByQuantityTreeView_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
if (availableByQuantityTreeView.SelectedNode != null &&
|
||||
availableByQuantityTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
||||
{
|
||||
AddItem(availableByQuantityTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private void availableByQuantityTreeView_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
if (availableByQuantityTreeView.SelectedNode != null &&
|
||||
availableByQuantityTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
||||
{
|
||||
AddItem(availableByQuantityTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
||||
}
|
||||
}
|
||||
private void availableByCategoryTreeView_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
if (availableByCategoryTreeView.SelectedNode != null &&
|
||||
availableByCategoryTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
||||
{
|
||||
AddItem(availableByCategoryTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private void availableByCategoryTreeView_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
if (availableByCategoryTreeView.SelectedNode != null &&
|
||||
availableByCategoryTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
||||
{
|
||||
AddItem(availableByCategoryTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
||||
}
|
||||
}
|
||||
private void availableAlphabeticTreeView_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
if (availableAlphabeticTreeView.SelectedNode != null &&
|
||||
availableAlphabeticTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
||||
{
|
||||
AddItem(availableAlphabeticTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private void availableAlphabeticTreeView_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
if (availableAlphabeticTreeView.SelectedNode != null &&
|
||||
availableAlphabeticTreeView.SelectedNode.Tag is WMeterRsltItemSpec)
|
||||
{
|
||||
AddItem(availableAlphabeticTreeView.SelectedNode.Tag as WMeterRsltItemSpec);
|
||||
}
|
||||
}
|
||||
void AddItem(WMeterRsltItemSpec item)
|
||||
{
|
||||
if (SelectedItems == null)
|
||||
{
|
||||
SelectedItems = new List<WMeterRsltItemSpec>();
|
||||
}
|
||||
|
||||
void AddItem(WMeterRsltItemSpec item)
|
||||
{
|
||||
WMeterRsltItemSpec newItem = item.Clone();
|
||||
newItem.Caption = newItem.Name;
|
||||
SelectedItems.Add(newItem);
|
||||
RedrawSelected();
|
||||
WMeterRsltItemSpec newItem = item.Clone();
|
||||
newItem.Caption = newItem.Name;
|
||||
SelectedItems.Add(newItem);
|
||||
|
||||
/// Select the last item
|
||||
selectedResultsListViewEx.Focus();
|
||||
selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true;
|
||||
selectedResultsListViewEx.Items[SelectedItems.Count - 1].EnsureVisible();
|
||||
}
|
||||
RedrawSelected();
|
||||
|
||||
selectedResultsListViewEx.Focus();
|
||||
selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true;
|
||||
selectedResultsListViewEx.Items[SelectedItems.Count - 1].EnsureVisible();
|
||||
}
|
||||
|
||||
private void selectedResultsListViewEx_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
/// Double click works when just one item is selected
|
||||
private void selectedResultsListViewEx_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
if (selectedResultsListViewEx.SelectedIndices.Count == 1)
|
||||
{
|
||||
SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[0]);
|
||||
RedrawAvailable();
|
||||
RedrawSelected();
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedResultsListViewEx.SelectedIndices.Count == 1)
|
||||
{
|
||||
SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[0]);
|
||||
RedrawAvailable();
|
||||
RedrawSelected();
|
||||
}
|
||||
}
|
||||
void removeButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
for (int i = selectedResultsListViewEx.SelectedIndices.Count - 1; i >= 0; i--)
|
||||
{
|
||||
SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[i]);
|
||||
}
|
||||
|
||||
void removeButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
/// Remove from the list (the last selected item first so that the indexes are not affected)
|
||||
RedrawAvailable();
|
||||
RedrawSelected();
|
||||
}
|
||||
|
||||
for (int i = selectedResultsListViewEx.SelectedIndices.Count - 1; i >= 0; i--)
|
||||
{
|
||||
SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[i]);
|
||||
}
|
||||
RedrawAvailable();
|
||||
RedrawSelected();
|
||||
}
|
||||
|
||||
void removeAllButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
/// Remove all items from 'Selected' list
|
||||
|
||||
SelectedItems.Clear();
|
||||
RedrawAvailable();
|
||||
RedrawSelected();
|
||||
}
|
||||
|
||||
//void okButton_Click(object sender, EventArgs e)
|
||||
//{
|
||||
// DialogResult = DialogResult.OK;
|
||||
// Close();
|
||||
//}
|
||||
void removeAllButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
SelectedItems.Clear();
|
||||
RedrawAvailable();
|
||||
RedrawSelected();
|
||||
}
|
||||
|
||||
private void ResultsConfigCtrl_KeyPress(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
@@ -475,9 +543,9 @@ namespace Results.Forms
|
||||
if (selectedResultsListViewEx.SelectedIndices.Count != 1) return;
|
||||
|
||||
int selIdx = selectedResultsListViewEx.SelectedIndices[0];
|
||||
|
||||
if (selIdx == 0)
|
||||
{
|
||||
/// Cannot move up
|
||||
selectedResultsListViewEx.Focus();
|
||||
selectedResultsListViewEx.Items[0].Selected = true;
|
||||
return;
|
||||
@@ -499,9 +567,9 @@ namespace Results.Forms
|
||||
if (selectedResultsListViewEx.SelectedIndices.Count != 1) return;
|
||||
|
||||
int selIdx = selectedResultsListViewEx.SelectedIndices[0];
|
||||
|
||||
if (selIdx == SelectedItems.Count - 1)
|
||||
{
|
||||
/// Cannot move down
|
||||
selectedResultsListViewEx.Focus();
|
||||
selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true;
|
||||
return;
|
||||
@@ -510,7 +578,7 @@ namespace Results.Forms
|
||||
WMeterRsltItemSpec tmp = SelectedItems[selIdx + 1];
|
||||
SelectedItems[selIdx + 1] = SelectedItems[selIdx];
|
||||
SelectedItems[selIdx] = tmp;
|
||||
|
||||
|
||||
RedrawSelected();
|
||||
|
||||
selectedResultsListViewEx.Focus();
|
||||
@@ -518,16 +586,12 @@ namespace Results.Forms
|
||||
selectedResultsListViewEx.Items[selIdx + 1].EnsureVisible();
|
||||
}
|
||||
|
||||
//private void cancelButton_Click(object sender, EventArgs e)
|
||||
//{
|
||||
// DialogResult = DialogResult.Cancel;
|
||||
// Close();
|
||||
//}
|
||||
|
||||
private void availableByCategoryTreeView_NodeMouseHover2(object sender, TreeNodeMouseHoverEventArgs e)
|
||||
{
|
||||
ToolTip toolTip = new ToolTip();
|
||||
toolTip.SetToolTip(this, e.Node.ToolTipText);
|
||||
}
|
||||
private void availableByCategoryTreeView_NodeMouseHover2(
|
||||
object sender,
|
||||
TreeNodeMouseHoverEventArgs e)
|
||||
{
|
||||
ToolTip toolTip = new ToolTip();
|
||||
toolTip.SetToolTip(this, e.Node.ToolTipText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,6 +362,8 @@ namespace Results
|
||||
Pulses_per_unit, /// 306
|
||||
|
||||
FlipMode, /// 307 iPerl mode used for this meter test result
|
||||
CalibFactorPercentage, /// 308 iPerl calibration factor represented as a percentage
|
||||
CalibFactorCorrectionPercentage,/// 309 iPerl calibration correction represented as a percentage
|
||||
Count,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using FluentNHibernate.Mapping;
|
||||
using Results.Entities;
|
||||
|
||||
namespace Results.Mappings
|
||||
{
|
||||
public class MeterTestCalibFactorRsltMap : ClassMap<MeterTestCalibFactorRslt>
|
||||
{
|
||||
public MeterTestCalibFactorRsltMap()
|
||||
{
|
||||
Id(x => x.Id);
|
||||
|
||||
References(x => x.MeterTestRslt).Column("MeterTestRslt_id")
|
||||
.Not.Nullable()
|
||||
.Cascade.None();
|
||||
|
||||
Map(x => x.CalibFactorIndex).Not.Nullable();
|
||||
|
||||
Map(x => x.BaseCalibFactor).Not.Nullable();
|
||||
Map(x => x.CalculatedCalibFactor).Not.Nullable();
|
||||
|
||||
Map(x => x.Stored).Not.Nullable();
|
||||
|
||||
Map(x => x.ErrorStr)
|
||||
.Length(240)
|
||||
.Nullable();
|
||||
|
||||
Map(x => x.TimeStart).Not.Nullable();
|
||||
Map(x => x.TimeEnd).Not.Nullable();
|
||||
|
||||
Map(x => x.CalibRawStart).Not.Nullable();
|
||||
Map(x => x.CalibRawEnd).Not.Nullable();
|
||||
|
||||
Map(x => x.Error).Not.Nullable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ namespace Results.Mappings
|
||||
#endif
|
||||
References(x => x.WaterMeter);
|
||||
References(x => x.TestRslt);
|
||||
Map(x => x.Q3Channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using FluentNHibernate.Mapping;
|
||||
using Results.Entities;
|
||||
|
||||
namespace Results.Mappings
|
||||
{
|
||||
class TestRsltCalibFactorMap : ClassMap<TestRsltCalibFactor>
|
||||
{
|
||||
public TestRsltCalibFactorMap()
|
||||
{
|
||||
Id(x => x.Id);
|
||||
|
||||
References(x => x.TestRslt)
|
||||
.Column("TestRsltId")
|
||||
.Not.Nullable();
|
||||
|
||||
Map(x => x.WaterMeterPosition).Not.Nullable();
|
||||
Map(x => x.CalibFactorIndex).Not.Nullable();
|
||||
Map(x => x.IsCalibFactorValid).Not.Nullable();
|
||||
|
||||
Map(x => x.BaseCalibFactor).Not.Nullable();
|
||||
Map(x => x.CalculatedCalibFactor).Not.Nullable();
|
||||
|
||||
Map(x => x.Stored).Not.Nullable();
|
||||
|
||||
Map(x => x.ErrorStr)
|
||||
.Length(240)
|
||||
.Nullable();
|
||||
|
||||
Map(x => x.TimeStart).Not.Nullable();
|
||||
Map(x => x.TimeEnd).Not.Nullable();
|
||||
|
||||
Map(x => x.CalibRawStart).Not.Nullable();
|
||||
Map(x => x.CalibRawEnd).Not.Nullable();
|
||||
|
||||
Map(x => x.Error).Not.Nullable();
|
||||
|
||||
Map(x => x.VolumeStart).Not.Nullable();
|
||||
Map(x => x.VolumeEnd).Not.Nullable();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,8 @@ namespace Results.Mappings
|
||||
#if ORACLE_DB
|
||||
Map(x => x.WMTypeId);
|
||||
#endif
|
||||
}
|
||||
Map(x => x.Q3Channel);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Results.Mappings
|
||||
#if IPERL
|
||||
Map(x => x.OrigCalibFactor);
|
||||
Map(x => x.CalibFactor);
|
||||
Map(x => x.CalibFactorNominal);
|
||||
Map(x => x.OrigCalibFactorLNA);
|
||||
Map(x => x.CalibFactorLNA);
|
||||
Map(x => x.Q2ErrWOCorrection);
|
||||
@@ -65,6 +66,7 @@ namespace Results.Mappings
|
||||
Map(x => x.Pruefindex);
|
||||
Map(x => x.HydrPruefung);
|
||||
#endif
|
||||
Map(x => x.Q3Channel);// Genesis meter identification
|
||||
References(x => x.WaterMeterData);
|
||||
References(x => x.Batch);
|
||||
HasMany(x => x.MeterTestRslts)
|
||||
|
||||
+21
-3
@@ -13,6 +13,8 @@
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -53,12 +55,13 @@
|
||||
<Reference Include="NHibernate">
|
||||
<HintPath>..\packages\NHibernate.4.0.4.4000\lib\net40\NHibernate.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SQLite, Version=2.0.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Data.SQLite.2.0.3\lib\net471\System.Data.SQLite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.SQLite, Version=1.0.119.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\lib\net46\System.Data.SQLite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Windows.Forms.DataVisualization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
@@ -255,6 +258,7 @@
|
||||
<EmbeddedResource Include="Resources\Strings.ru.resx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="Resources\Headpic.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -270,4 +274,18 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<Import Project="..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets" Condition="Exists('..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\build\net46\Stub.System.Data.SQLite.Core.NetFramework.targets'))" />
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<Compile Include="Entities\MeterTestCalibFactorRslt.cs" />
|
||||
<Compile Include="Entities\TestRsltCalibFactor.cs" />
|
||||
<Compile Include="Entities\helpers\TestRsltCalibFactorHelper.cs" />
|
||||
<Compile Include="Mappings\MeterTestCalibFactorRsltMap.cs" />
|
||||
<Compile Include="Mappings\TestRsltCalibFactorMap.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -457,6 +457,8 @@ namespace Results
|
||||
#if IPERL
|
||||
AllItems.Add(new WMeterRsltItemSpec(ItemID.OrigCalibFactor, "iPerl OrigCalibFactor", Quantity.Number, ItemCategory.MeterResult, (w,t,u,f,p) => FormatDbl(u, f, p, "V3", w.OrigCalibFactor)));
|
||||
AllItems.Add(new WMeterRsltItemSpec(ItemID.CalibFactor, "iPerl CalibFactor", Quantity.Number, ItemCategory.MeterResult, (w,t,u,f,p) => FormatDbl(u, f, p, "V3", w.CalibFactor)));
|
||||
AllItems.Add(new WMeterRsltItemSpec(ItemID.CalibFactorPercentage, "iPerl CalibFactor (%)", Quantity.Number, ItemCategory.MeterResult, (w,t,u,f,p) => FormatDbl(u, f, p, "F4", w.CalibFactorPercentage)));
|
||||
AllItems.Add(new WMeterRsltItemSpec(ItemID.CalibFactorCorrectionPercentage, "iPerl Calibration correction (%)", Quantity.Number, ItemCategory.MeterResult, (w,t,u,f,p) => FormatDbl(u, f, p, "F4", w.CalibFactorCorrectionPercentage)));
|
||||
AllItems.Add(new WMeterRsltItemSpec(ItemID.OrigCalibFactorLNA,"iPerl OrigCalibFactorLNA",Quantity.Number, ItemCategory.MeterResult, (w,t,u,f,p) => FormatDbl(u, f, p, "V3", w.OrigCalibFactorLNA)));
|
||||
AllItems.Add(new WMeterRsltItemSpec(ItemID.CalibFactorLNA, "iPerl CalibFactorLNA", Quantity.Number, ItemCategory.MeterResult, (w,t,u,f,p) => FormatDbl(u, f, p, "V3", w.CalibFactorLNA)));
|
||||
AllItems.Add(new WMeterRsltItemSpec(ItemID.Q2ErrWOCorrection, "iPerl Q2ErrWOCorrection", Quantity.Error, ItemCategory.MeterResult, (w,t,u,f,p) => FormatDbl(u, f, p, "V3", w.Q2ErrWOCorrection)));
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="log4net" version="2.0.15" targetFramework="net472" />
|
||||
<package id="Stub.System.Data.SQLite.Core.NetFramework" version="1.0.119.0" targetFramework="net472" />
|
||||
<package id="System.Data.SQLite" version="2.0.4" targetFramework="net472" />
|
||||
<package id="System.Data.SQLite.Core" version="1.0.119.0" targetFramework="net472" />
|
||||
</packages>
|
||||
@@ -227,12 +227,6 @@ namespace TBF
|
||||
///
|
||||
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
|
||||
public string[] LastSNTexts;
|
||||
[XmlIgnore]
|
||||
|
||||
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion("3.9.3143.0")]
|
||||
[assembly: AssemblyFileVersion("3.9.3143.0")]
|
||||
[assembly: AssemblyVersion("3.9.3145.101")]
|
||||
[assembly: AssemblyFileVersion("3.9.3145.101")]
|
||||
|
||||
@@ -3,14 +3,3 @@
|
||||
| Version | Source of change | Target Environment | Title | Description |
|
||||
|------------|----------------------|--------------------------------------------------------------|-----------------------------------------------------|------------------------------------|
|
||||
| 3.9.2149.0 | Michal databse error | HeatMeters, Heat meter sensors, Procedure Dilog, Tab Process | Excanged columns value 'Sensor' and 'Heat meter sensor' | Fix in code ProcedureDlg, row 1851 |
|
||||
| 3.9.2149.1 | Main TBF | General release | Version iteration | Assembly and file version increment. |
|
||||
| 3.9.2149.2 | Main TBF | Poseidon register reader | Poseidon pulse and timing handling | Added reference-pulse reading in `Run()` and improved Poseidon timing/task tracking. |
|
||||
| 3.9.2149.4 | Main TBF | Mass collection / Poseidon start-stop | Mass collection update | Refactored standing-start mass collection, extended Poseidon start/end data-entry configuration and improved dialog/task handling. |
|
||||
| 3.9.2200.1 | Main TBF | Test infrastructure | TBF test assembly access | Added `InternalsVisibleTo` support for `TBFTests`. |
|
||||
| 3.9.2201.1 | Main TBF | Poseidon CLI | Poseidon CLI configuration | Added macro descriptions, refined serial-port/CLI argument configuration and extended CLI test coverage. |
|
||||
| 3.9.2202.1 | Main TBF | Poseidon CLI | CLI release iteration | Assembly and file version increment for the Poseidon CLI workstream. |
|
||||
| 3.9.2203.1 | Main TBF | Poseidon CLI / smart-meter sequence | CLI test and configuration update | Updated CLI executable test setup, serial-port default responses and smart-meter component-name handling. |
|
||||
| 3.9.2204.1 | Morrisville | Morrisville / Poseidon CLI | Morrisville CLI diagnostics | Restored lost 2204 versioning, added detailed CLI command/response logging and used a fixed CLI directory for deployment. |
|
||||
| 3.9.2205.1 | Morrisville | Morrisville / Poseidon CLI / Results DB | Poseidon read diagnostics and results DB migration | Improved Poseidon CLI execution and diagnostics: fixed CLI working directory, exit code/stdout/stderr capture, JSON/NFC/reading validation and culture-independent decimal parsing. Added reader-cycle and fake-CLI coverage. Results DB now creates missing compatibility columns (`FlipMode`, `ExtraDataPath`, `X1`-`X9`) automatically for MySQL and SQLite. |
|
||||
| 3.9.2206.0 | Morrisville / Main TBF | Poseidon CLI / Results DB | Poseidon start/end rearm and simulation isolation | Re-armed a completed START reader once for END, preventing reused START values or skipped END CLI calls. Added CLI response, dialog prefill and confirmed-value diagnostics. Simulation always runs `C:\TBF\Cli\cmdSleepTest.exe` instead of the configured physical Hat CLI. Added customer-response, decimal separator, non-zero, dialog-transfer, reader-cycle and simulation-selection regression tests. Added compatibility migration for `PulsesPerKilogram`, `MassMeter`, `MassRef`, `ErrorMass`, `PassedMass` and `QuantityUnits`. |
|
||||
| 3.9.3143.0 | Ally port | Ally / Poseidon CLI / Results DB | Morrisville Poseidon fixes transferred | Ported the applicable Morrisville Poseidon read, simulation, logging, regression-test and results-schema migration fixes to the Ally source branch while retaining its compatible legacy reader configuration and non-blocking UI flow. |
|
||||
|
||||
@@ -1770,4 +1770,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Načítání</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Simulace</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>Nenačteno</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Výchozí kalibrační faktor:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Nominální surový kalibrační faktor odpovídající 100 %. Tato hodnota se používá pro výpočet položky „iPerl CalibFactor (%)“ v konfiguraci výsledků.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2232,4 +2232,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Laden</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Simuliert</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>Nicht geladen</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Standard-Kalibrierfaktor:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Roher Kalibrierfaktor, der 100 % entspricht. Dieser Wert wird zur Berechnung von „iPerl CalibFactor (%)“ in der Ergebnis-Konfiguration verwendet.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -141,4 +141,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Cargando</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Simulado</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>No cargado</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Factor de calibración predeterminado:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Factor de calibración bruto que representa el 100 %. Este valor se utiliza para calcular «iPerl CalibFactor (%)» en la configuración de resultados.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2031,4 +2031,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Chargement</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Simulé</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>Non chargé</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Facteur d’étalonnage par défaut :</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Facteur d’étalonnage brut représentant 100 %. Cette valeur est utilisée pour calculer « iPerl CalibFactor (%) » dans la configuration des résultats.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1803,4 +1803,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Caricamento</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Simulato</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>Non caricato</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Fattore di calibrazione predefinito:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Fattore di calibrazione grezzo corrispondente al 100 %. Questo valore viene usato per calcolare «iPerl CalibFactor (%)» nella configurazione dei risultati.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1707,4 +1707,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Ładowanie</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Symulacja</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>Nie wczytano</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Domyślny współczynnik kalibracji:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Surowy współczynnik kalibracji odpowiadający 100 %. Ta wartość służy do obliczania „iPerl CalibFactor (%)” w konfiguracji wyników.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2503,4 +2503,10 @@
|
||||
<data name="Not_loaded" xml:space="preserve">
|
||||
<value>Not loaded</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Default calibration factor:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Raw calibration factor representing 100 %. This value is used to calculate 'iPerl CalibFactor (%)' in the results configuration.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -855,4 +855,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Se încarcă</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Simulat</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>Neîncărcat</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Factor de calibrare implicit:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Factorul brut de calibrare care reprezintă 100 %. Această valoare este utilizată pentru calculul „iPerl CalibFactor (%)” în configurarea rezultatelor.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1620,4 +1620,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Загрузка</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Симуляция</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>Не загружено</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Номинальный коэффициент калибровки:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Необработанный коэффициент калибровки, соответствующий 100 %. Это значение используется для вычисления «iPerl CalibFactor (%)» в конфигурации результатов.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -268,4 +268,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>Načítavanie</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>Simulácia</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>Nenačítané</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>Predvolený kalibračný faktor:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>Nominálny surový kalibračný faktor zodpovedajúci 100 %. Táto hodnota sa používa na výpočet položky „iPerl CalibFactor (%)“ v konfigurácii výsledkov.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1209,4 +1209,10 @@
|
||||
<data name="Loading" xml:space="preserve"><value>正在加载</value></data>
|
||||
<data name="Simulated" xml:space="preserve"><value>模拟</value></data>
|
||||
<data name="Not_loaded" xml:space="preserve"><value>未加载</value></data>
|
||||
<data name="IperlCalibFactorNominal" xml:space="preserve">
|
||||
<value>默认校准系数:</value>
|
||||
</data>
|
||||
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
|
||||
<value>表示 100% 的原始校准系数。该值用于在结果配置中计算“iPerl CalibFactor (%)”。</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -41,8 +41,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
|
||||
|
||||
System.Windows.Forms.Form modelessDlg;
|
||||
bool modelessDialogOpening;
|
||||
bool modelessDialogWaitLogged;
|
||||
public bool Completed { get { return (modelessDlg is IHasCompleted) ? (modelessDlg as IHasCompleted).Completed : true; } }
|
||||
|
||||
double refVolume;
|
||||
@@ -72,15 +70,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
}
|
||||
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() { }
|
||||
|
||||
@@ -137,8 +126,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
//entryFormCfg.Direction = Direction.S640;
|
||||
//currentOp = CurrentOp.EnterTestStartStates;
|
||||
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;
|
||||
foreach (IRegReader reader in regReaders)
|
||||
{
|
||||
@@ -180,105 +167,35 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
this.refVolume = refVolume;
|
||||
this.errLimLo = errLimLo;
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (ShowForm == 0)
|
||||
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,
|
||||
ProcessData.SelectedProcedure.OrderInfo != null ? ProcessData.SelectedProcedure.OrderInfo.POName : string.Empty);
|
||||
(myRef.modelessDlg as CycleBeginningForm)?.AutoClickOkAfterDelay();
|
||||
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)
|
||||
{
|
||||
if (ShowForm == 0)
|
||||
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);
|
||||
modelessDlg.Show();
|
||||
log.DebugFormat("Poseidon data-entry: TestStartEndForm START visible={0}, handleCreated={1}",
|
||||
modelessDlg.Visible, modelessDlg.IsHandleCreated);
|
||||
}
|
||||
///
|
||||
void OpenTestEndStatesDlg(EntryForm myRef)
|
||||
{
|
||||
if (ShowForm == 0)
|
||||
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);
|
||||
modelessDlg.Show();
|
||||
log.DebugFormat("Poseidon data-entry: TestStartEndForm END visible={0}, handleCreated={1}",
|
||||
modelessDlg.Visible, modelessDlg.IsHandleCreated);
|
||||
}
|
||||
|
||||
/// <summary>Start this operation</summary>
|
||||
@@ -287,12 +204,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
readDataOp = ReadDataOp.None;
|
||||
readAndSetDataToMeters = false;
|
||||
filedDataToMeters = false;
|
||||
poseidonPhaseRunner = null;
|
||||
poseidonPhaseReaders = null;
|
||||
sendStartPhaseInitialized = false;
|
||||
sendStartPhaseIterations = 0;
|
||||
modelessDialogOpening = false;
|
||||
modelessDialogWaitLogged = false;
|
||||
|
||||
if (ShowForm == 0)
|
||||
return ;
|
||||
@@ -304,7 +215,7 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
//ReadAndSetDataToMeters();
|
||||
if (ProcessData.SelectedProcedure.OrderInfo == null)
|
||||
{
|
||||
BeginOpenDialog("CycleBeginningForm", OpenBeginningDlg);
|
||||
Program.MainWnd.Invoke(new EntryFormDlgt(OpenBeginningDlg), this);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -314,10 +225,10 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
}
|
||||
break;
|
||||
case CurrentOp.ReadDatastream_StartStates:
|
||||
BeginOpenDialog("TestStartEndForm.START", OpenTestStartStatesDlg);
|
||||
Program.MainWnd.Invoke(new EntryFormDlgt(OpenTestStartStatesDlg), this);
|
||||
break;
|
||||
case CurrentOp.ReadDatastream_EndStates:
|
||||
BeginOpenDialog("TestStartEndForm.END", OpenTestEndStatesDlg);
|
||||
Program.MainWnd.Invoke(new EntryFormDlgt(OpenTestEndStatesDlg), this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -329,17 +240,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
/// <returns>Event.ResultsPrinted</returns>
|
||||
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
|
||||
readAndSetDataToMeters = ReadAndSetDataToMeters();
|
||||
|
||||
@@ -368,16 +268,10 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
{
|
||||
dlg.WMStartState[item] = poseidonReader.BeginWMState;
|
||||
dlg.WMStartStateStr[item] = poseidonReader.BeginWMState.ToString();
|
||||
log.InfoFormat("Poseidon dialog prefill: phase=Start, WM{0}, reader={1}, value={2}",
|
||||
item + 1, poseidonReader.Name, dlg.WMStartState[item]);
|
||||
}
|
||||
|
||||
if (currentOp == CurrentOp.ReadDatastream_EndStates)
|
||||
{
|
||||
dlg.WMEndState[item] = poseidonReader.EndWMState;
|
||||
log.InfoFormat("Poseidon dialog prefill: phase=End, WM{0}, reader={1}, value={2}",
|
||||
item + 1, poseidonReader.Name, dlg.WMEndState[item]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,44 +326,38 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (modelessDlg is TestStartEndForm)
|
||||
else if (modelessDlg is TestStartEndForm && currentOp == CurrentOp.ReadDatastream_StartStates)
|
||||
{
|
||||
StoreAcceptedDialogValues((TestStartEndForm)modelessDlg);
|
||||
}
|
||||
/// Fixed start test - start
|
||||
TestStartEndForm dlg = (modelessDlg as TestStartEndForm);
|
||||
if (dlg != null)
|
||||
{
|
||||
for (int i = 0; i < dlg.WaterMetersCount; i++)
|
||||
{
|
||||
wmStartState[i] = dlg.WMStartState[i];
|
||||
wmStartStateStr[i] = dlg.WMStartStateStr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (modelessDlg is TestStartEndForm && currentOp == CurrentOp.ReadDatastream_EndStates)
|
||||
{
|
||||
/// Fixed start test - end
|
||||
TestStartEndForm dlg = (modelessDlg as TestStartEndForm);
|
||||
if (dlg != null)
|
||||
{
|
||||
for (int i = 0; i < dlg.WaterMetersCount; i++)
|
||||
{
|
||||
wmEndState[i] = dlg.WMEndState[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
resultSaved = true;
|
||||
modelessDlg = null;
|
||||
modelessDialogOpening = false;
|
||||
modelessDlg = null;
|
||||
}
|
||||
|
||||
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>
|
||||
public void Stop()
|
||||
{
|
||||
@@ -481,7 +369,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
currentOp = CurrentOp.None;
|
||||
readAndSetDataToMeters = false;
|
||||
filedDataToMeters = false;
|
||||
sendStartPhaseInitialized = false;
|
||||
}
|
||||
|
||||
public int ShowForm
|
||||
@@ -502,108 +389,88 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
bool bOperationSuccess = false;
|
||||
if (currentOp == CurrentOp.SendStartDataStream)
|
||||
{
|
||||
if (!sendStartPhaseInitialized)
|
||||
bool finishedReading = regReaders == null; // we can work only with register readers
|
||||
while (!finishedReading) //TODO BUMI lock - fuck ?
|
||||
{
|
||||
sendStartPhaseInitialized = true;
|
||||
sendStartPhaseIterations = 0;
|
||||
poseidonPhaseStartedAt = DateTime.UtcNow;
|
||||
poseidonNextProgressLogAt = poseidonPhaseStartedAt.AddSeconds(5);
|
||||
log.InfoFormat("Poseidon send-start phase started; readers=[{0}]", DescribeReaders(regReaders));
|
||||
if (regReaders != null)
|
||||
bool bAllReadersFinished = true;
|
||||
foreach (var iRegReader in regReaders )
|
||||
{
|
||||
foreach (IRegReader regReader in regReaders)
|
||||
if (iRegReader is PoseidonReader)
|
||||
{
|
||||
PoseidonReader poseidonReader = regReader as PoseidonReader;
|
||||
if (poseidonReader == null) continue;
|
||||
PoseidonReader poseidonReader = (iRegReader as PoseidonReader);
|
||||
poseidonReader.SetCliLogging(CliLogging);
|
||||
poseidonReader.SetCurrentOp(PoseidonReader.CurrentPoseidonOp.SendStartDataStream);
|
||||
log.DebugFormat("Poseidon send-start: armed reader={0}, COM={1}, state={2}",
|
||||
poseidonReader.Name, poseidonReader.ComPortNr, poseidonReader.CurrentOp);
|
||||
|
||||
if (poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.SendStartDataStream_Done
|
||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.SendStartDataStream_Runing)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendStartPhaseIterations++;
|
||||
bool allReadersFinished = true;
|
||||
if (regReaders != null)
|
||||
{
|
||||
foreach (IRegReader regReader in regReaders)
|
||||
/// Wait for all readers to finish
|
||||
if (bAllReadersFinished)
|
||||
{
|
||||
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;
|
||||
finishedReading = true;
|
||||
readDataOp = ReadDataOp.Done;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Threading.Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
bOperationSuccess = true;
|
||||
}
|
||||
else if ((currentOp == CurrentOp.ReadDatastream_StartStates)
|
||||
|| (currentOp == CurrentOp.ReadDatastream_EndStates))
|
||||
{
|
||||
if (poseidonPhaseRunner == null)
|
||||
bool finishedReading = regReaders == null; // we can work only with register readers
|
||||
while (!finishedReading) //TODO BUMI lock - fuck ?
|
||||
{
|
||||
poseidonPhaseReaders = new List<IPoseidonReadOperation>();
|
||||
if (regReaders != null)
|
||||
bool bAllReadersFinished = true;
|
||||
foreach (var iRegReader in regReaders )
|
||||
{
|
||||
foreach (IRegReader regReader in regReaders)
|
||||
if (iRegReader is PoseidonReader)
|
||||
{
|
||||
PoseidonReader poseidonReader = regReader as PoseidonReader;
|
||||
if (poseidonReader == null) continue;
|
||||
PoseidonReader poseidonReader = (iRegReader as PoseidonReader);
|
||||
if(poseidonReader == null)
|
||||
continue;
|
||||
poseidonReader.SetCliLogging(CliLogging);
|
||||
poseidonPhaseReaders.Add(new PoseidonReaderOperation(poseidonReader));
|
||||
if (!(poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.ReadDatastream_Done
|
||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.ReadDatastream_Running))
|
||||
{
|
||||
poseidonReader.SetCurrentOp((currentOp == CurrentOp.ReadDatastream_StartStates)?
|
||||
PoseidonReader.CurrentPoseidonOp.ReadDataStream_Start :
|
||||
PoseidonReader.CurrentPoseidonOp.ReadDataStream_End);
|
||||
}
|
||||
/// Send start data stream
|
||||
poseidonReader.Run();
|
||||
if (!(poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Done
|
||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error))
|
||||
{
|
||||
bAllReadersFinished = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
poseidonPhaseRunner = new PoseidonReadPhaseRunner(
|
||||
currentOp == CurrentOp.ReadDatastream_StartStates);
|
||||
poseidonPhaseStartedAt = DateTime.UtcNow;
|
||||
poseidonNextProgressLogAt = poseidonPhaseStartedAt.AddSeconds(5);
|
||||
log.InfoFormat("Poseidon phase started: phase={0}, readers={1}, detail=[{2}]",
|
||||
currentOp, poseidonPhaseReaders.Count, DescribeReaders(regReaders));
|
||||
if (bAllReadersFinished)
|
||||
{
|
||||
finishedReading = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Threading.Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
bOperationSuccess = true;
|
||||
}
|
||||
|
||||
return bOperationSuccess;
|
||||
|
||||
@@ -147,8 +147,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
|
||||
private void SetUiBusy(bool busy, long deltaTime = -1)
|
||||
{
|
||||
log.DebugFormat("Poseidon UI: SetUiBusy busy={0}, deltaTime={1}, enabledTextBoxes={2}",
|
||||
busy, deltaTime, enabledTextBoxes.Count);
|
||||
// show wait cursor for form and children
|
||||
this.UseWaitCursor = busy;
|
||||
|
||||
@@ -298,8 +296,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
BeginInvoke(new Action(() => UpdateValues(stratValue, enableEdit)));
|
||||
return;
|
||||
}
|
||||
log.DebugFormat("Poseidon UI: UpdateValues on UI thread startValue={0}, enableEdit={1}, deltaTime={2}",
|
||||
stratValue, enableEdit, deltaTime);
|
||||
|
||||
for (int i = 0; i < TextBoxesCount; i++)
|
||||
{
|
||||
@@ -324,7 +320,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
if (enableEdit)
|
||||
{
|
||||
SetUiBusy(false, deltaTime);
|
||||
log.Debug("Poseidon UI: values applied and dialog released from Loading...");
|
||||
}
|
||||
// if you also need to enable/disable editing, do it here,
|
||||
// it's now safely on the UI thread.
|
||||
@@ -353,8 +348,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
if (endTextBoxes[i].Visible && endTextBoxes[i].Enabled)
|
||||
{
|
||||
WMEndState[i] = Utils.ParseUDouble(endTextBoxes[i].Text);
|
||||
log.InfoFormat("Poseidon dialog OK: phase=End, WM{0}, enteredText='{1}', parsedValue={2}",
|
||||
i + 1, endTextBoxes[i].Text, WMEndState[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,8 +359,6 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
{
|
||||
WMStartStateStr[i] = startTextBoxes[i].Text;
|
||||
WMStartState[i] = Utils.ParseUDouble(startTextBoxes[i].Text);
|
||||
log.InfoFormat("Poseidon dialog OK: phase=Start, WM{0}, enteredText='{1}', parsedValue={2}",
|
||||
i + 1, startTextBoxes[i].Text, WMStartState[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ using System.Linq;
|
||||
using log4net;
|
||||
using Results.Entities;
|
||||
using TBF.Rig.GenericDevices;
|
||||
using TBF.Rig.Sequences;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||
|
||||
namespace TBF.Rig.DataEntry.Uni
|
||||
{
|
||||
@@ -25,22 +23,21 @@ namespace TBF.Rig.DataEntry.Uni
|
||||
? new IRegReader[0]
|
||||
: readers.ToArray();
|
||||
long enabledHeads = Program.LocalSettings.OptoHeadsEnabled;
|
||||
SmartReaderSelection.EnsureConfiguredReaders(Program.LocalSettings, ProcessData.SmartHeadsUni);
|
||||
|
||||
IRegReader[] selected = allSlots
|
||||
.Where(reader => IsSelectedForDataEntry(reader, waterMeters, Program.LocalSettings, enabledHeads))
|
||||
.Where(reader => IsSelectedForDataEntry(reader, waterMeters, enabledHeads))
|
||||
.ToArray();
|
||||
|
||||
string selectedPositions = string.Join(",", selected.Select(reader => reader.Position));
|
||||
string skippedPositions = string.Join(",", allSlots
|
||||
.Where(reader => reader != null &&
|
||||
!IsSelectedForDataEntry(reader, waterMeters, Program.LocalSettings, enabledHeads))
|
||||
!IsSelectedForDataEntry(reader, waterMeters, enabledHeads))
|
||||
.Select(reader => reader.Position));
|
||||
|
||||
log.DebugFormat(
|
||||
"DATA_ENTRY_READER_FILTER Operation={0}, LegacyOptoHeadsEnabled=0x{1:X12}, " +
|
||||
"DATA_ENTRY_READER_FILTER Operation={0}, OptoHeadsEnabled=0x{1:X12}, " +
|
||||
"Slots={2}, Readers={3}, Selected={4}, SelectedPositions=[{5}], " +
|
||||
"SkippedPositions=[{6}], SelectionRule=SmartReaderFamilySelectionOrPrecheckedWaterMeter",
|
||||
"SkippedPositions=[{6}], SelectionRule=SmartMeterMaskOrPrecheckedWaterMeter",
|
||||
operation,
|
||||
enabledHeads,
|
||||
allSlots.Length,
|
||||
@@ -59,8 +56,8 @@ namespace TBF.Rig.DataEntry.Uni
|
||||
reader.GetType().Name,
|
||||
reader.Position,
|
||||
reader.DebugLevel,
|
||||
IsSelectedForDataEntry(reader, waterMeters, Program.LocalSettings, enabledHeads),
|
||||
GetSelectionDetails(reader, waterMeters, Program.LocalSettings, enabledHeads));
|
||||
IsSelectedForDataEntry(reader, waterMeters, enabledHeads),
|
||||
GetSelectionDetails(reader, waterMeters, enabledHeads));
|
||||
}
|
||||
|
||||
return selected;
|
||||
@@ -70,15 +67,6 @@ namespace TBF.Rig.DataEntry.Uni
|
||||
IRegReader reader,
|
||||
IList<WaterMeter> waterMeters,
|
||||
long enabledHeads)
|
||||
{
|
||||
return IsSelectedForDataEntry(reader, waterMeters, null, enabledHeads);
|
||||
}
|
||||
|
||||
internal static bool IsSelectedForDataEntry(
|
||||
IRegReader reader,
|
||||
IList<WaterMeter> waterMeters,
|
||||
LocalSettings settings,
|
||||
long enabledHeads)
|
||||
{
|
||||
if (reader == null)
|
||||
return false;
|
||||
@@ -99,9 +87,6 @@ namespace TBF.Rig.DataEntry.Uni
|
||||
|
||||
if (reader is ISmartMeterReader)
|
||||
{
|
||||
if (settings != null && settings.SmartReaderSelections != null)
|
||||
return SmartReaderSelection.IsEnabled(settings, reader);
|
||||
|
||||
int position0 = reader.Position - 1;
|
||||
return position0 >= 0 && position0 < 63 &&
|
||||
(enabledHeads & (1L << position0)) != 0;
|
||||
@@ -130,7 +115,6 @@ namespace TBF.Rig.DataEntry.Uni
|
||||
private static string GetSelectionDetails(
|
||||
IRegReader reader,
|
||||
IList<WaterMeter> waterMeters,
|
||||
LocalSettings settings,
|
||||
long enabledHeads)
|
||||
{
|
||||
#if IPERL
|
||||
@@ -152,15 +136,6 @@ namespace TBF.Rig.DataEntry.Uni
|
||||
#endif
|
||||
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;
|
||||
bool maskBit = position0 >= 0 && position0 < 63 &&
|
||||
(enabledHeads & (1L << position0)) != 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,19 +9,27 @@ using TBF.Rig.Generic;
|
||||
|
||||
namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
{
|
||||
public class ResultsWriterCfg : ComponentCfgBase, IComponentCfg
|
||||
/// <summary>
|
||||
/// Configuration of the ResultsWriter component.
|
||||
/// </summary>
|
||||
public class ResultsWriterCfg :
|
||||
ComponentCfgBase,
|
||||
IComponentCfg
|
||||
{
|
||||
public static XmlSerializer Serializer =
|
||||
XmlSerializer.FromTypes(new[] { typeof(ResultsWriterCfg) })[0];
|
||||
XmlSerializer.FromTypes(
|
||||
new[] { typeof(ResultsWriterCfg) })[0];
|
||||
|
||||
public override XmlSerializer GetSerializer()
|
||||
{
|
||||
return Serializer;
|
||||
}
|
||||
|
||||
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities)
|
||||
public IComponentCfgCtrl GetControl(
|
||||
IList<Config.Entities.Component> cmpntEntities)
|
||||
{
|
||||
return new ResultsWriterCfgCtrl(cmpntEntities);
|
||||
return new ResultsWriterCfgCtrl(
|
||||
cmpntEntities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -34,25 +42,34 @@ namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
/// </summary>
|
||||
public string StorageName;
|
||||
|
||||
/// Runtime model used ResultsConfigCtrl
|
||||
/// <summary>
|
||||
/// Runtime result model used by ResultsConfigCtrl.
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
public List<WMeterRsltItemSpec> SelectedItems;
|
||||
|
||||
/// <summary>
|
||||
/// Serialized representation of selected result items.
|
||||
/// </summary>
|
||||
public string[] Items;
|
||||
|
||||
/// Serializable model
|
||||
/// <summary>
|
||||
/// Serializable model retained for compatibility.
|
||||
/// </summary>
|
||||
public List<ResultsWriterItemCfg> SelectedItemsCfg;
|
||||
|
||||
ResultsWriterCfg()
|
||||
{
|
||||
ParentName = string.Empty; // here should be UniDataStorageWriter component name
|
||||
ParentName = string.Empty;
|
||||
SelectedItems = new List<WMeterRsltItemSpec>();
|
||||
SelectedItemsCfg = new List<ResultsWriterItemCfg>();
|
||||
Enabled = true;
|
||||
StorageName = "Results";
|
||||
}
|
||||
|
||||
public ResultsWriterCfg(string name, IComponentFactory factory)
|
||||
public ResultsWriterCfg(
|
||||
string name,
|
||||
IComponentFactory factory)
|
||||
: this()
|
||||
{
|
||||
Name = name;
|
||||
@@ -67,22 +84,35 @@ namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
ParentName,
|
||||
Enabled,
|
||||
StorageName,
|
||||
SelectedItems != null ? SelectedItems.Count : 0);
|
||||
SelectedItems != null
|
||||
? SelectedItems.Count
|
||||
: 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies runtime result items into the serialized model.
|
||||
/// </summary>
|
||||
public void UpdateSerializableModel()
|
||||
{
|
||||
Items = WMeterRsltItemSpec.ToStrArray(SelectedItems);
|
||||
Items =
|
||||
WMeterRsltItemSpec.ToStrArray(
|
||||
SelectedItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreates runtime result items after deserialization.
|
||||
/// </summary>
|
||||
public void UpdateRuntimeModel()
|
||||
{
|
||||
SelectedItems = new List<WMeterRsltItemSpec>();
|
||||
SelectedItems =
|
||||
new List<WMeterRsltItemSpec>();
|
||||
|
||||
if (Items == null)
|
||||
return;
|
||||
|
||||
SelectedItems.AddRange(WMeterRsltItemSpec.FromStrArray(Items));
|
||||
SelectedItems.AddRange(
|
||||
WMeterRsltItemSpec.FromStrArray(
|
||||
Items));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,37 +8,53 @@ using System.Windows.Forms;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using TBF.Rig.Generic;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters;
|
||||
|
||||
namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
{
|
||||
public partial class ResultsWriterCfgCtrl : Configs.ConfigCtrlUtils, IComponentCfgCtrl
|
||||
public partial class ResultsWriterCfgCtrl :
|
||||
Configs.ConfigCtrlUtils,
|
||||
IComponentCfgCtrl
|
||||
{
|
||||
public bool ShowMore { get { return false; } }
|
||||
public bool ShowMore
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
ResultsWriterCfg config;
|
||||
IList<Component> cmpntEntities;
|
||||
|
||||
bool resultsConfigChanged;
|
||||
private ResultsWriterCfg config;
|
||||
private IList<Component> cmpntEntities;
|
||||
private bool resultsConfigChanged;
|
||||
|
||||
public IComponentCfg Config
|
||||
{
|
||||
get { return config as IComponentCfg; }
|
||||
set
|
||||
{
|
||||
config = value as ResultsWriterCfg;
|
||||
config =
|
||||
value as ResultsWriterCfg;
|
||||
|
||||
Redraw();
|
||||
}
|
||||
}
|
||||
|
||||
public ResultsWriterCfgCtrl(IList<Component> cmpntEntities)
|
||||
public ResultsWriterCfgCtrl(
|
||||
IList<Component> cmpntEntities)
|
||||
{
|
||||
InitializeComponent();
|
||||
this.cmpntEntities = cmpntEntities;
|
||||
|
||||
this.cmpntEntities =
|
||||
cmpntEntities;
|
||||
}
|
||||
|
||||
private void ResultsWriterCfgCtrl_Load(object sender, EventArgs e)
|
||||
private void ResultsWriterCfgCtrl_Load(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
if (config == null) return;
|
||||
if (config == null)
|
||||
return;
|
||||
|
||||
Redraw();
|
||||
}
|
||||
|
||||
@@ -46,42 +62,59 @@ namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
{
|
||||
}
|
||||
|
||||
void Redraw()
|
||||
private void Redraw()
|
||||
{
|
||||
if (config == null) return;
|
||||
if (config == null)
|
||||
return;
|
||||
|
||||
config.UpdateRuntimeModel();
|
||||
|
||||
classNameLabel.Text = config.Factory.ClassName;
|
||||
nameTextBox.Text = config.Name;
|
||||
enabledCheckBox.Checked = config.Enabled;
|
||||
storageNameTextBox.Text = config.StorageName;
|
||||
classNameLabel.Text =
|
||||
config.Factory.ClassName;
|
||||
|
||||
nameTextBox.Text =
|
||||
config.Name;
|
||||
|
||||
enabledCheckBox.Checked =
|
||||
config.Enabled;
|
||||
|
||||
storageNameTextBox.Text =
|
||||
config.StorageName;
|
||||
|
||||
parentComboBox.Items.Clear();
|
||||
parentComboBox.Items.Add(string.Empty);
|
||||
parentComboBox.Items.Add(
|
||||
string.Empty);
|
||||
|
||||
if (cmpntEntities != null)
|
||||
{
|
||||
foreach (Component cmpnt in cmpntEntities)
|
||||
foreach (Component cmpnt
|
||||
in cmpntEntities)
|
||||
{
|
||||
if (cmpnt == null) continue;
|
||||
if (cmpnt == null)
|
||||
continue;
|
||||
|
||||
// for now, a simple filter by name/classname
|
||||
if (cmpnt.ClassName != null &&
|
||||
cmpnt.ClassName.IndexOf("UniDataStorageWriter") >= 0)
|
||||
cmpnt.ClassName.IndexOf(
|
||||
"UniDataStorageWriter") >= 0)
|
||||
{
|
||||
parentComboBox.Items.Add(cmpnt.Name);
|
||||
parentComboBox.Items.Add(
|
||||
cmpnt.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parentComboBox.Text = config.ParentName;
|
||||
parentComboBox.Text =
|
||||
config.ParentName;
|
||||
|
||||
selectedItemsLabel.Text = string.Format(
|
||||
"{0} selected item(s)",
|
||||
config.SelectedItems != null ? config.SelectedItems.Count : 0);
|
||||
selectedItemsLabel.Text =
|
||||
string.Format(
|
||||
"{0} selected item(s)",
|
||||
config.SelectedItems != null
|
||||
? config.SelectedItems.Count
|
||||
: 0);
|
||||
|
||||
resultsConfigChanged = false;
|
||||
resultsConfigChanged =
|
||||
false;
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
@@ -94,23 +127,35 @@ namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
previewRequestButton.Enabled = true;
|
||||
}
|
||||
|
||||
public CfgUpdateFlags VerifyCfg(ref string message)
|
||||
public CfgUpdateFlags VerifyCfg(
|
||||
ref string message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(nameTextBox.Text))
|
||||
if (string.IsNullOrEmpty(
|
||||
nameTextBox.Text))
|
||||
{
|
||||
message = "Component name is empty.";
|
||||
message =
|
||||
"Component name is empty.";
|
||||
|
||||
return CfgUpdateFlags.Error;
|
||||
}
|
||||
|
||||
if (enabledCheckBox.Checked && string.IsNullOrEmpty(parentComboBox.Text))
|
||||
if (enabledCheckBox.Checked &&
|
||||
string.IsNullOrEmpty(
|
||||
parentComboBox.Text))
|
||||
{
|
||||
message = "Parent UniDataStorageWriter is not selected.";
|
||||
message =
|
||||
"Parent UniDataStorageWriter is not selected.";
|
||||
|
||||
return CfgUpdateFlags.Error;
|
||||
}
|
||||
|
||||
if (enabledCheckBox.Checked && string.IsNullOrEmpty(storageNameTextBox.Text))
|
||||
if (enabledCheckBox.Checked &&
|
||||
string.IsNullOrEmpty(
|
||||
storageNameTextBox.Text))
|
||||
{
|
||||
message = "Storage name is empty.";
|
||||
message =
|
||||
"Storage name is empty.";
|
||||
|
||||
return CfgUpdateFlags.Error;
|
||||
}
|
||||
|
||||
@@ -119,89 +164,140 @@ namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
|
||||
public CfgUpdateFlags UpdateCfg()
|
||||
{
|
||||
CfgUpdateFlags flags = CfgUpdateFlags.None;
|
||||
CfgUpdateFlags flags =
|
||||
CfgUpdateFlags.None;
|
||||
|
||||
if (config == null) return CfgUpdateFlags.Error;
|
||||
if (config == null)
|
||||
return CfgUpdateFlags.Error;
|
||||
|
||||
if (config.Name != nameTextBox.Text)
|
||||
if (config.Name !=
|
||||
nameTextBox.Text)
|
||||
{
|
||||
config.Name = nameTextBox.Text;
|
||||
flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd;
|
||||
config.Name =
|
||||
nameTextBox.Text;
|
||||
|
||||
flags |=
|
||||
CfgUpdateFlags.AnyChange |
|
||||
CfgUpdateFlags.RestartRqrd;
|
||||
}
|
||||
|
||||
if (config.ParentName != parentComboBox.Text)
|
||||
if (config.ParentName !=
|
||||
parentComboBox.Text)
|
||||
{
|
||||
config.ParentName = parentComboBox.Text;
|
||||
flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd;
|
||||
config.ParentName =
|
||||
parentComboBox.Text;
|
||||
|
||||
flags |=
|
||||
CfgUpdateFlags.AnyChange |
|
||||
CfgUpdateFlags.RestartRqrd;
|
||||
}
|
||||
|
||||
flags |= UpdateDifferent(
|
||||
ref config.Enabled,
|
||||
enabledCheckBox.Checked,
|
||||
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
||||
flags |=
|
||||
UpdateDifferent(
|
||||
ref config.Enabled,
|
||||
enabledCheckBox.Checked,
|
||||
CfgUpdateFlags.AnyChange |
|
||||
CfgUpdateFlags.RestartRqrd);
|
||||
|
||||
flags |= UpdateDifferent(
|
||||
ref config.StorageName,
|
||||
storageNameTextBox.Text,
|
||||
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
||||
flags |=
|
||||
UpdateDifferent(
|
||||
ref config.StorageName,
|
||||
storageNameTextBox.Text,
|
||||
CfgUpdateFlags.AnyChange |
|
||||
CfgUpdateFlags.RestartRqrd);
|
||||
|
||||
if (resultsConfigChanged)
|
||||
{
|
||||
flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd;
|
||||
resultsConfigChanged = false;
|
||||
config.UpdateSerializableModel();
|
||||
|
||||
flags |=
|
||||
CfgUpdateFlags.AnyChange |
|
||||
CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
resultsConfigChanged =
|
||||
false;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
private void configureResultsButton_Click(object sender, EventArgs e)
|
||||
private void configureResultsButton_Click(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
if (config == null) return;
|
||||
if (config == null)
|
||||
return;
|
||||
|
||||
using (ResultsWriterResultsDlg dlg = new ResultsWriterResultsDlg())
|
||||
string payloadTemplatePath =
|
||||
ResolvePayloadTemplatePath();
|
||||
|
||||
using (ResultsWriterResultsDlg dlg =
|
||||
new ResultsWriterResultsDlg())
|
||||
{
|
||||
dlg.SelectedItems = config.SelectedItems;
|
||||
dlg.SelectedItems =
|
||||
CloneSelectedItems(
|
||||
config.SelectedItems);
|
||||
|
||||
if (dlg.ShowDialog(this) == DialogResult.OK)
|
||||
dlg.PayloadTemplatePath =
|
||||
payloadTemplatePath;
|
||||
|
||||
if (dlg.ShowDialog(this) ==
|
||||
DialogResult.OK)
|
||||
{
|
||||
config.SelectedItems = new List<Results.WMeterRsltItemSpec>(dlg.SelectedItems);
|
||||
config.UpdateSerializableModel();
|
||||
resultsConfigChanged = true;
|
||||
config.SelectedItems =
|
||||
new List<Results.WMeterRsltItemSpec>(
|
||||
dlg.SelectedItems);
|
||||
|
||||
selectedItemsLabel.Text = string.Format(
|
||||
"{0} selected item(s)",
|
||||
config.SelectedItems != null ? config.SelectedItems.Count : 0);
|
||||
config.UpdateSerializableModel();
|
||||
|
||||
resultsConfigChanged =
|
||||
true;
|
||||
|
||||
selectedItemsLabel.Text =
|
||||
string.Format(
|
||||
"{0} selected item(s)",
|
||||
config.SelectedItems != null
|
||||
? config.SelectedItems.Count
|
||||
: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void previewRequestButton_Click(object sender, EventArgs e)
|
||||
/// <summary>
|
||||
/// Generates a five-test XML dry-run and never calls the database.
|
||||
/// </summary>
|
||||
private void previewRequestButton_Click(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
if (config == null) return;
|
||||
|
||||
Results.Entities.Batch batch = CreateSimulationBatch();
|
||||
|
||||
if (batch == null || batch.WaterMeters == null || batch.WaterMeters.Count == 0)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"No current batch results are available.",
|
||||
"ResultsWriter",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
if (config == null)
|
||||
return;
|
||||
}
|
||||
|
||||
Results.Entities.Batch batch =
|
||||
CreateSimulationBatch();
|
||||
|
||||
try
|
||||
{
|
||||
config.UpdateSerializableModel();
|
||||
config.UpdateRuntimeModel();
|
||||
|
||||
ResultsWriter writer = new ResultsWriter(config);
|
||||
ResultsWriter writer =
|
||||
new ResultsWriter(
|
||||
config);
|
||||
|
||||
writer.InitializeParent();
|
||||
writer.WriteBatchResults(batch);
|
||||
|
||||
XmlPayloadBuildResult preview =
|
||||
writer.GeneratePreviewPayload(
|
||||
batch,
|
||||
5);
|
||||
|
||||
MessageBox.Show(
|
||||
"Current batch was written by ResultsWriter.",
|
||||
"ResultsWriter",
|
||||
string.Format(
|
||||
"XML preview generated successfully.{0}{0}File:{0}{1}{0}{0}No production output operation was executed.",
|
||||
Environment.NewLine,
|
||||
preview.ArchiveFilePath),
|
||||
"ResultsWriter XML preview",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
}
|
||||
@@ -209,35 +305,95 @@ namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
{
|
||||
MessageBox.Show(
|
||||
ex.Message,
|
||||
"ResultsWriter write failed",
|
||||
"ResultsWriter preview failed",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolvePayloadTemplatePath()
|
||||
{
|
||||
WriterCfg writerCfg =
|
||||
ResolveParentWriterCfg();
|
||||
|
||||
if (writerCfg == null ||
|
||||
!writerCfg.UsesXmlPayload())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return writerCfg.PayloadTemplatePath;
|
||||
}
|
||||
|
||||
private WriterCfg ResolveParentWriterCfg()
|
||||
{
|
||||
string parentName =
|
||||
parentComboBox.Text;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
parentName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IComponent parent =
|
||||
TbfComponents.FindComponent(
|
||||
parentName);
|
||||
|
||||
TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer writer =
|
||||
parent as
|
||||
TBF.Rig.Output.DataStorage
|
||||
.UniDataStorageWriter.Writer;
|
||||
|
||||
if (writer == null)
|
||||
return null;
|
||||
|
||||
return writer.Cfg
|
||||
as WriterCfg;
|
||||
}
|
||||
|
||||
private IList<Results.WMeterRsltItemSpec> CloneSelectedItems(
|
||||
IList<Results.WMeterRsltItemSpec> source)
|
||||
{
|
||||
List<Results.WMeterRsltItemSpec> result =
|
||||
new List<Results.WMeterRsltItemSpec>();
|
||||
|
||||
if (source == null)
|
||||
return result;
|
||||
|
||||
foreach (Results.WMeterRsltItemSpec item
|
||||
in source)
|
||||
{
|
||||
if (item != null)
|
||||
result.Add(
|
||||
item.Clone());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Results.Entities.Batch CreateSimulationBatch()
|
||||
{
|
||||
Results.Entities.Batch batch = new Results.Entities.Batch();
|
||||
Results.Entities.Batch batch =
|
||||
new Results.Entities.Batch();
|
||||
|
||||
batch.BatchNr = 999999;
|
||||
batch.ProcedureName = "ResultsWriter simulation";
|
||||
batch.ProcedureName = "ResultsWriter XML preview";
|
||||
batch.StartTime = DateTime.Now;
|
||||
batch.EndTime = DateTime.Now;
|
||||
batch.TestBenchName = "Mexico";
|
||||
batch.TestBenchName = "SIMULATION-BENCH";
|
||||
|
||||
Results.Entities.WaterMeter wm1 = new Results.Entities.WaterMeter();
|
||||
wm1.Batch = batch;
|
||||
wm1.WMPosition = 1;
|
||||
wm1.SerialNr = "SN000001";
|
||||
batch.WaterMeters.Add(wm1);
|
||||
Results.Entities.WaterMeter wm =
|
||||
new Results.Entities.WaterMeter();
|
||||
|
||||
Results.Entities.WaterMeter wm2 = new Results.Entities.WaterMeter();
|
||||
wm2.Batch = batch;
|
||||
wm2.WMPosition = 2;
|
||||
wm2.SerialNr = "SN000002";
|
||||
batch.WaterMeters.Add(wm2);
|
||||
wm.Batch = batch;
|
||||
wm.WMPosition = 1;
|
||||
wm.SerialNr = "SIM000001";
|
||||
|
||||
batch.WaterMeters.Add(
|
||||
wm);
|
||||
|
||||
return batch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
|
||||
namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
{
|
||||
partial class ResultsWriterResultsDlg
|
||||
{
|
||||
@@ -6,7 +7,9 @@
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null)) components.Dispose();
|
||||
if (disposing && (components != null))
|
||||
components.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
@@ -34,10 +37,11 @@
|
||||
this.okButton.Location = new System.Drawing.Point(714, 512);
|
||||
this.okButton.Name = "okButton";
|
||||
this.okButton.Size = new System.Drawing.Size(104, 30);
|
||||
this.okButton.TabIndex = 1;
|
||||
this.okButton.TabIndex = 2;
|
||||
this.okButton.Text = "OK";
|
||||
this.okButton.UseVisualStyleBackColor = true;
|
||||
this.okButton.Click += new System.EventHandler(this.okButton_Click);
|
||||
this.okButton.Click +=
|
||||
new System.EventHandler(this.okButton_Click);
|
||||
|
||||
this.cancelButton.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
@@ -46,7 +50,7 @@
|
||||
this.cancelButton.Location = new System.Drawing.Point(824, 512);
|
||||
this.cancelButton.Name = "cancelButton";
|
||||
this.cancelButton.Size = new System.Drawing.Size(104, 30);
|
||||
this.cancelButton.TabIndex = 2;
|
||||
this.cancelButton.TabIndex = 3;
|
||||
this.cancelButton.Text = "Cancel";
|
||||
this.cancelButton.UseVisualStyleBackColor = true;
|
||||
|
||||
@@ -61,7 +65,8 @@
|
||||
this.Name = "ResultsWriterResultsDlg";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "ResultsWriter configuration";
|
||||
this.Load += new System.EventHandler(this.ResultsWriterResultsDlg_Load);
|
||||
this.Load +=
|
||||
new System.EventHandler(this.ResultsWriterResultsDlg_Load);
|
||||
this.ResumeLayout(false);
|
||||
}
|
||||
|
||||
@@ -69,4 +74,4 @@
|
||||
private System.Windows.Forms.Button okButton;
|
||||
private System.Windows.Forms.Button cancelButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,35 +11,181 @@ using TBF.Resources;
|
||||
|
||||
namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures ResultsWriter result items and optional XML destinations.
|
||||
/// </summary>
|
||||
public partial class ResultsWriterResultsDlg : Form
|
||||
{
|
||||
private string payloadTemplatePath;
|
||||
|
||||
public IList<WMeterRsltItemSpec> SelectedItems
|
||||
{
|
||||
set { resultsConfigCtrl.SelectedItems = value; }
|
||||
get { return resultsConfigCtrl.SelectedItems; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the customer XML reference path.
|
||||
/// </summary>
|
||||
public string PayloadTemplatePath
|
||||
{
|
||||
get { return payloadTemplatePath; }
|
||||
set { payloadTemplatePath = value; }
|
||||
}
|
||||
|
||||
public ResultsWriterResultsDlg()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.Icon = Properties.Resources.TBF_icon;
|
||||
resultsConfigCtrl.SupressTestIDColumn = true;
|
||||
Icon =
|
||||
Properties.Resources.TBF_icon;
|
||||
|
||||
resultsConfigCtrl.SupressTestIDColumn =
|
||||
true;
|
||||
|
||||
}
|
||||
|
||||
private void ResultsWriterResultsDlg_Load(object sender, EventArgs e)
|
||||
private void ResultsWriterResultsDlg_Load(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
Text = "ResultsWriter configuration";
|
||||
okButton.Text = Strings.OkBtnText;
|
||||
cancelButton.Text = Strings.CancelBtnText;
|
||||
Text =
|
||||
"ResultsWriter configuration";
|
||||
|
||||
resultsConfigCtrl.Unlocked = true;
|
||||
okButton.Text =
|
||||
Strings.OkBtnText;
|
||||
|
||||
cancelButton.Text =
|
||||
Strings.CancelBtnText;
|
||||
|
||||
resultsConfigCtrl.Unlocked =
|
||||
true;
|
||||
|
||||
ConfigureCaptionEditor();
|
||||
}
|
||||
|
||||
private void okButton_Click(object sender, EventArgs e)
|
||||
private void ConfigureCaptionEditor()
|
||||
{
|
||||
DialogResult = DialogResult.OK;
|
||||
bool xmlMode =
|
||||
!string.IsNullOrWhiteSpace(
|
||||
PayloadTemplatePath);
|
||||
|
||||
if (!xmlMode)
|
||||
{
|
||||
resultsConfigCtrl.CaptionPicker =
|
||||
null;
|
||||
|
||||
resultsConfigCtrl.CaptionColumnText =
|
||||
"Caption";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
resultsConfigCtrl.CaptionPicker =
|
||||
PickXmlDestination;
|
||||
|
||||
resultsConfigCtrl.CaptionColumnText =
|
||||
"Destination [...]";
|
||||
}
|
||||
|
||||
private string PickXmlDestination(
|
||||
WMeterRsltItemSpec editedItem)
|
||||
{
|
||||
if (editedItem == null)
|
||||
return null;
|
||||
|
||||
using (XmlDestinationPickerDlg dlg =
|
||||
new XmlDestinationPickerDlg())
|
||||
{
|
||||
dlg.TemplatePath =
|
||||
PayloadTemplatePath;
|
||||
|
||||
dlg.CurrentSourceName =
|
||||
editedItem.Name;
|
||||
|
||||
dlg.CurrentDestinationPath =
|
||||
editedItem.Caption;
|
||||
|
||||
dlg.ConfiguredMappings =
|
||||
BuildConfiguredMappings(
|
||||
editedItem);
|
||||
|
||||
if (dlg.ShowDialog(this) ==
|
||||
DialogResult.OK)
|
||||
{
|
||||
return dlg.SelectedDestinationPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IDictionary<string, IList<string>> BuildConfiguredMappings(
|
||||
WMeterRsltItemSpec editedItem)
|
||||
{
|
||||
Dictionary<string, IList<string>> mappings =
|
||||
new Dictionary<string, IList<string>>(
|
||||
StringComparer.Ordinal);
|
||||
|
||||
if (resultsConfigCtrl.SelectedItems != null)
|
||||
{
|
||||
foreach (WMeterRsltItemSpec item
|
||||
in resultsConfigCtrl.SelectedItems)
|
||||
{
|
||||
if (item == null ||
|
||||
object.ReferenceEquals(
|
||||
item,
|
||||
editedItem) ||
|
||||
string.IsNullOrWhiteSpace(
|
||||
item.Caption))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddMapping(
|
||||
mappings,
|
||||
item.Caption,
|
||||
item.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private void AddMapping(
|
||||
IDictionary<string, IList<string>> mappings,
|
||||
string path,
|
||||
string sourceName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return;
|
||||
|
||||
IList<string> names;
|
||||
|
||||
if (!mappings.TryGetValue(
|
||||
path,
|
||||
out names))
|
||||
{
|
||||
names =
|
||||
new List<string>();
|
||||
|
||||
mappings.Add(
|
||||
path,
|
||||
names);
|
||||
}
|
||||
|
||||
if (!names.Contains(sourceName))
|
||||
names.Add(sourceName);
|
||||
}
|
||||
|
||||
private void okButton_Click(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
DialogResult =
|
||||
DialogResult.OK;
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
{
|
||||
partial class XmlDestinationPickerDlg
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected override void Dispose(
|
||||
bool disposing)
|
||||
{
|
||||
if (disposing &&
|
||||
components != null)
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.templateLabel =
|
||||
new System.Windows.Forms.Label();
|
||||
|
||||
this.templatePathTextBox =
|
||||
new System.Windows.Forms.TextBox();
|
||||
|
||||
this.configuringResultLabel =
|
||||
new System.Windows.Forms.Label();
|
||||
|
||||
this.configuringResultTextBox =
|
||||
new System.Windows.Forms.TextBox();
|
||||
|
||||
this.xmlTreeView =
|
||||
new System.Windows.Forms.TreeView();
|
||||
|
||||
this.selectedDestinationLabel =
|
||||
new System.Windows.Forms.Label();
|
||||
|
||||
this.selectedPathTextBox =
|
||||
new System.Windows.Forms.TextBox();
|
||||
|
||||
this.mappedResultsLabel =
|
||||
new System.Windows.Forms.Label();
|
||||
|
||||
this.mappedResultsTextBox =
|
||||
new System.Windows.Forms.TextBox();
|
||||
|
||||
this.mappingStatusLabel =
|
||||
new System.Windows.Forms.Label();
|
||||
|
||||
this.legendCurrentLabel =
|
||||
new System.Windows.Forms.Label();
|
||||
|
||||
this.legendMappedLabel =
|
||||
new System.Windows.Forms.Label();
|
||||
|
||||
this.legendUsedLabel =
|
||||
new System.Windows.Forms.Label();
|
||||
|
||||
this.chooseButton =
|
||||
new System.Windows.Forms.Button();
|
||||
|
||||
this.cancelButton =
|
||||
new System.Windows.Forms.Button();
|
||||
|
||||
this.SuspendLayout();
|
||||
|
||||
//
|
||||
// templateLabel
|
||||
//
|
||||
this.templateLabel.AutoSize = true;
|
||||
this.templateLabel.Location = new System.Drawing.Point(12, 15);
|
||||
this.templateLabel.Name = "templateLabel";
|
||||
this.templateLabel.Size = new System.Drawing.Size(90, 13);
|
||||
this.templateLabel.TabIndex = 0;
|
||||
this.templateLabel.Text = "XML reference:";
|
||||
|
||||
//
|
||||
// templatePathTextBox
|
||||
//
|
||||
this.templatePathTextBox.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
(((System.Windows.Forms.AnchorStyles.Top |
|
||||
System.Windows.Forms.AnchorStyles.Left) |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
|
||||
this.templatePathTextBox.Location =
|
||||
new System.Drawing.Point(108, 12);
|
||||
this.templatePathTextBox.Name = "templatePathTextBox";
|
||||
this.templatePathTextBox.ReadOnly = true;
|
||||
this.templatePathTextBox.Size = new System.Drawing.Size(860, 20);
|
||||
this.templatePathTextBox.TabIndex = 1;
|
||||
|
||||
//
|
||||
// configuringResultLabel
|
||||
//
|
||||
this.configuringResultLabel.AutoSize = true;
|
||||
this.configuringResultLabel.Location = new System.Drawing.Point(12, 43);
|
||||
this.configuringResultLabel.Name = "configuringResultLabel";
|
||||
this.configuringResultLabel.Size = new System.Drawing.Size(118, 13);
|
||||
this.configuringResultLabel.TabIndex = 2;
|
||||
this.configuringResultLabel.Text = "Configuring TBF result:";
|
||||
|
||||
//
|
||||
// configuringResultTextBox
|
||||
//
|
||||
this.configuringResultTextBox.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
(((System.Windows.Forms.AnchorStyles.Top |
|
||||
System.Windows.Forms.AnchorStyles.Left) |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
|
||||
this.configuringResultTextBox.Location =
|
||||
new System.Drawing.Point(136, 40);
|
||||
this.configuringResultTextBox.Name = "configuringResultTextBox";
|
||||
this.configuringResultTextBox.ReadOnly = true;
|
||||
this.configuringResultTextBox.Size = new System.Drawing.Size(832, 20);
|
||||
this.configuringResultTextBox.TabIndex = 3;
|
||||
|
||||
//
|
||||
// xmlTreeView
|
||||
//
|
||||
this.xmlTreeView.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((((System.Windows.Forms.AnchorStyles.Top |
|
||||
System.Windows.Forms.AnchorStyles.Bottom) |
|
||||
System.Windows.Forms.AnchorStyles.Left) |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
|
||||
this.xmlTreeView.FullRowSelect = true;
|
||||
this.xmlTreeView.HideSelection = false;
|
||||
this.xmlTreeView.Location = new System.Drawing.Point(12, 72);
|
||||
this.xmlTreeView.Name = "xmlTreeView";
|
||||
this.xmlTreeView.ShowNodeToolTips = true;
|
||||
this.xmlTreeView.Size = new System.Drawing.Size(956, 392);
|
||||
this.xmlTreeView.TabIndex = 4;
|
||||
|
||||
this.xmlTreeView.AfterSelect +=
|
||||
new System.Windows.Forms.TreeViewEventHandler(
|
||||
this.xmlTreeView_AfterSelect);
|
||||
|
||||
this.xmlTreeView.NodeMouseDoubleClick +=
|
||||
new System.Windows.Forms.TreeNodeMouseClickEventHandler(
|
||||
this.xmlTreeView_NodeMouseDoubleClick);
|
||||
|
||||
//
|
||||
// selectedDestinationLabel
|
||||
//
|
||||
this.selectedDestinationLabel.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
|
||||
this.selectedDestinationLabel.AutoSize = true;
|
||||
this.selectedDestinationLabel.Location = new System.Drawing.Point(12, 477);
|
||||
this.selectedDestinationLabel.Name = "selectedDestinationLabel";
|
||||
this.selectedDestinationLabel.Size = new System.Drawing.Size(109, 13);
|
||||
this.selectedDestinationLabel.TabIndex = 5;
|
||||
this.selectedDestinationLabel.Text = "Selected destination:";
|
||||
|
||||
//
|
||||
// selectedPathTextBox
|
||||
//
|
||||
this.selectedPathTextBox.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
(((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left) |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
|
||||
this.selectedPathTextBox.Location = new System.Drawing.Point(15, 493);
|
||||
this.selectedPathTextBox.Name = "selectedPathTextBox";
|
||||
this.selectedPathTextBox.ReadOnly = true;
|
||||
this.selectedPathTextBox.Size = new System.Drawing.Size(953, 20);
|
||||
this.selectedPathTextBox.TabIndex = 6;
|
||||
|
||||
//
|
||||
// mappedResultsLabel
|
||||
//
|
||||
this.mappedResultsLabel.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
|
||||
this.mappedResultsLabel.AutoSize = true;
|
||||
this.mappedResultsLabel.Location = new System.Drawing.Point(12, 526);
|
||||
this.mappedResultsLabel.Name = "mappedResultsLabel";
|
||||
this.mappedResultsLabel.Size = new System.Drawing.Size(119, 13);
|
||||
this.mappedResultsLabel.TabIndex = 7;
|
||||
this.mappedResultsLabel.Text = "Mapped TBF result(s):";
|
||||
|
||||
//
|
||||
// mappedResultsTextBox
|
||||
//
|
||||
this.mappedResultsTextBox.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
(((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left) |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
|
||||
this.mappedResultsTextBox.Location = new System.Drawing.Point(137, 523);
|
||||
this.mappedResultsTextBox.Name = "mappedResultsTextBox";
|
||||
this.mappedResultsTextBox.ReadOnly = true;
|
||||
this.mappedResultsTextBox.Size = new System.Drawing.Size(831, 20);
|
||||
this.mappedResultsTextBox.TabIndex = 8;
|
||||
|
||||
//
|
||||
// mappingStatusLabel
|
||||
//
|
||||
this.mappingStatusLabel.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
|
||||
this.mappingStatusLabel.AutoSize = true;
|
||||
this.mappingStatusLabel.Location = new System.Drawing.Point(12, 556);
|
||||
this.mappingStatusLabel.Name = "mappingStatusLabel";
|
||||
this.mappingStatusLabel.Size = new System.Drawing.Size(0, 13);
|
||||
this.mappingStatusLabel.TabIndex = 9;
|
||||
|
||||
//
|
||||
// legendCurrentLabel
|
||||
//
|
||||
this.legendCurrentLabel.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
|
||||
this.legendCurrentLabel.AutoSize = true;
|
||||
this.legendCurrentLabel.BackColor = System.Drawing.Color.LightBlue;
|
||||
this.legendCurrentLabel.Location = new System.Drawing.Point(12, 582);
|
||||
this.legendCurrentLabel.Name = "legendCurrentLabel";
|
||||
this.legendCurrentLabel.Padding = new System.Windows.Forms.Padding(4, 2, 4, 2);
|
||||
this.legendCurrentLabel.Size = new System.Drawing.Size(100, 17);
|
||||
this.legendCurrentLabel.TabIndex = 10;
|
||||
this.legendCurrentLabel.Text = "Current mapping";
|
||||
|
||||
//
|
||||
// legendMappedLabel
|
||||
//
|
||||
this.legendMappedLabel.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
|
||||
this.legendMappedLabel.AutoSize = true;
|
||||
this.legendMappedLabel.BackColor = System.Drawing.Color.PaleGreen;
|
||||
this.legendMappedLabel.Location = new System.Drawing.Point(122, 582);
|
||||
this.legendMappedLabel.Name = "legendMappedLabel";
|
||||
this.legendMappedLabel.Padding = new System.Windows.Forms.Padding(4, 2, 4, 2);
|
||||
this.legendMappedLabel.Size = new System.Drawing.Size(165, 17);
|
||||
this.legendMappedLabel.TabIndex = 11;
|
||||
this.legendMappedLabel.Text = "Mapped repeating destination";
|
||||
|
||||
//
|
||||
// legendUsedLabel
|
||||
//
|
||||
this.legendUsedLabel.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
|
||||
this.legendUsedLabel.AutoSize = true;
|
||||
this.legendUsedLabel.BackColor = System.Drawing.Color.LightGoldenrodYellow;
|
||||
this.legendUsedLabel.Location = new System.Drawing.Point(297, 582);
|
||||
this.legendUsedLabel.Name = "legendUsedLabel";
|
||||
this.legendUsedLabel.Padding = new System.Windows.Forms.Padding(4, 2, 4, 2);
|
||||
this.legendUsedLabel.Size = new System.Drawing.Size(151, 17);
|
||||
this.legendUsedLabel.TabIndex = 12;
|
||||
this.legendUsedLabel.Text = "Used one-time destination";
|
||||
|
||||
//
|
||||
// chooseButton
|
||||
//
|
||||
this.chooseButton.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
|
||||
this.chooseButton.Location = new System.Drawing.Point(754, 610);
|
||||
this.chooseButton.Name = "chooseButton";
|
||||
this.chooseButton.Size = new System.Drawing.Size(104, 30);
|
||||
this.chooseButton.TabIndex = 13;
|
||||
this.chooseButton.Text = "Choose";
|
||||
this.chooseButton.UseVisualStyleBackColor = true;
|
||||
|
||||
this.chooseButton.Click +=
|
||||
new System.EventHandler(
|
||||
this.chooseButton_Click);
|
||||
|
||||
//
|
||||
// cancelButton
|
||||
//
|
||||
this.cancelButton.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
|
||||
this.cancelButton.DialogResult =
|
||||
System.Windows.Forms.DialogResult.Cancel;
|
||||
|
||||
this.cancelButton.Location = new System.Drawing.Point(864, 610);
|
||||
this.cancelButton.Name = "cancelButton";
|
||||
this.cancelButton.Size = new System.Drawing.Size(104, 30);
|
||||
this.cancelButton.TabIndex = 14;
|
||||
this.cancelButton.Text = "Cancel";
|
||||
this.cancelButton.UseVisualStyleBackColor = true;
|
||||
|
||||
//
|
||||
// XmlDestinationPickerDlg
|
||||
//
|
||||
this.AcceptButton = this.chooseButton;
|
||||
this.CancelButton = this.cancelButton;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(980, 652);
|
||||
|
||||
this.Controls.Add(this.cancelButton);
|
||||
this.Controls.Add(this.chooseButton);
|
||||
this.Controls.Add(this.legendUsedLabel);
|
||||
this.Controls.Add(this.legendMappedLabel);
|
||||
this.Controls.Add(this.legendCurrentLabel);
|
||||
this.Controls.Add(this.mappingStatusLabel);
|
||||
this.Controls.Add(this.mappedResultsTextBox);
|
||||
this.Controls.Add(this.mappedResultsLabel);
|
||||
this.Controls.Add(this.selectedPathTextBox);
|
||||
this.Controls.Add(this.selectedDestinationLabel);
|
||||
this.Controls.Add(this.xmlTreeView);
|
||||
this.Controls.Add(this.configuringResultTextBox);
|
||||
this.Controls.Add(this.configuringResultLabel);
|
||||
this.Controls.Add(this.templatePathTextBox);
|
||||
this.Controls.Add(this.templateLabel);
|
||||
|
||||
this.MinimumSize = new System.Drawing.Size(760, 560);
|
||||
this.Name = "XmlDestinationPickerDlg";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Select XML destination";
|
||||
|
||||
this.Load +=
|
||||
new System.EventHandler(
|
||||
this.XmlDestinationPickerDlg_Load);
|
||||
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
}
|
||||
|
||||
private System.Windows.Forms.Label templateLabel;
|
||||
private System.Windows.Forms.TextBox templatePathTextBox;
|
||||
private System.Windows.Forms.Label configuringResultLabel;
|
||||
private System.Windows.Forms.TextBox configuringResultTextBox;
|
||||
private System.Windows.Forms.TreeView xmlTreeView;
|
||||
private System.Windows.Forms.Label selectedDestinationLabel;
|
||||
private System.Windows.Forms.TextBox selectedPathTextBox;
|
||||
private System.Windows.Forms.Label mappedResultsLabel;
|
||||
private System.Windows.Forms.TextBox mappedResultsTextBox;
|
||||
private System.Windows.Forms.Label mappingStatusLabel;
|
||||
private System.Windows.Forms.Label legendCurrentLabel;
|
||||
private System.Windows.Forms.Label legendMappedLabel;
|
||||
private System.Windows.Forms.Label legendUsedLabel;
|
||||
private System.Windows.Forms.Button chooseButton;
|
||||
private System.Windows.Forms.Button cancelButton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
|
||||
|
||||
namespace TBF.Rig.Output.DB.ResultsWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays the structural model of an XML payload example and allows
|
||||
/// selection of one logical XML destination.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Sample XML values are intentionally not displayed. The dialog is a
|
||||
/// structure viewer and mapping configurator, not an XML value editor.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Existing TBF mappings are shown directly beside their XML destinations.
|
||||
/// Repeating prototype destinations can be used by multiple TBF results.
|
||||
/// One-time destinations can be assigned only once.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public partial class XmlDestinationPickerDlg : Form
|
||||
{
|
||||
private readonly PayloadTemplateInspector inspector;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new picker dialog.
|
||||
/// </summary>
|
||||
public XmlDestinationPickerDlg()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
inspector =
|
||||
new PayloadTemplateInspector();
|
||||
|
||||
try
|
||||
{
|
||||
Icon =
|
||||
Properties.Resources.TBF_icon;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Optional icon only.
|
||||
}
|
||||
|
||||
ConfiguredMappings =
|
||||
new Dictionary<string, IList<string>>(
|
||||
StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the customer XML example path.
|
||||
/// </summary>
|
||||
public string TemplatePath
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TBF result currently being configured.
|
||||
/// </summary>
|
||||
public string CurrentSourceName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the destination currently assigned to the edited item.
|
||||
/// </summary>
|
||||
public string CurrentDestinationPath
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets mappings already configured by other TBF result items.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Dictionary key is the logical XML destination. The list contains
|
||||
/// TBF result names currently mapped to the destination.
|
||||
/// </remarks>
|
||||
public IDictionary<string, IList<string>> ConfiguredMappings
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected logical destination.
|
||||
/// </summary>
|
||||
public string SelectedDestinationPath
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private void XmlDestinationPickerDlg_Load(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
Text =
|
||||
"Select XML destination";
|
||||
|
||||
templatePathTextBox.Text =
|
||||
TemplatePath ?? string.Empty;
|
||||
|
||||
configuringResultTextBox.Text =
|
||||
CurrentSourceName ?? string.Empty;
|
||||
|
||||
selectedPathTextBox.Text =
|
||||
string.Empty;
|
||||
|
||||
mappedResultsTextBox.Text =
|
||||
string.Empty;
|
||||
|
||||
mappingStatusLabel.Text =
|
||||
"Select an XML attribute or value.";
|
||||
|
||||
chooseButton.Enabled =
|
||||
false;
|
||||
|
||||
LoadStructure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the customer XML example and builds the structure tree.
|
||||
/// </summary>
|
||||
private void LoadStructure()
|
||||
{
|
||||
xmlTreeView.BeginUpdate();
|
||||
|
||||
try
|
||||
{
|
||||
xmlTreeView.Nodes.Clear();
|
||||
|
||||
PayloadTemplateInspectionResult result =
|
||||
inspector.Inspect(
|
||||
TemplatePath);
|
||||
|
||||
if (result.Root == null)
|
||||
return;
|
||||
|
||||
TreeNode rootNode =
|
||||
CreateTreeNode(
|
||||
result.Root);
|
||||
|
||||
xmlTreeView.Nodes.Add(
|
||||
rootNode);
|
||||
|
||||
rootNode.Expand();
|
||||
|
||||
TreeNode currentNode =
|
||||
FindTreeNode(
|
||||
xmlTreeView.Nodes,
|
||||
CurrentDestinationPath);
|
||||
|
||||
if (currentNode != null)
|
||||
{
|
||||
ExpandParents(
|
||||
currentNode);
|
||||
|
||||
xmlTreeView.SelectedNode =
|
||||
currentNode;
|
||||
|
||||
currentNode.EnsureVisible();
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
MessageBox.Show(
|
||||
this,
|
||||
"Failed to analyze XML payload example." +
|
||||
Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
exc.Message,
|
||||
"XML payload structure",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
xmlTreeView.EndUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates one visual tree node from the structural payload model.
|
||||
/// </summary>
|
||||
private TreeNode CreateTreeNode(
|
||||
PayloadTemplateNode payloadNode)
|
||||
{
|
||||
string displayText =
|
||||
payloadNode.DisplayText ??
|
||||
payloadNode.Name ??
|
||||
string.Empty;
|
||||
|
||||
IList<string> mappedBy =
|
||||
GetConfiguredMappings(
|
||||
payloadNode.DestinationPath);
|
||||
|
||||
bool isCurrentMapping =
|
||||
payloadNode.IsSelectable &&
|
||||
string.Equals(
|
||||
payloadNode.DestinationPath,
|
||||
CurrentDestinationPath,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
if (payloadNode.IsSelectable)
|
||||
{
|
||||
List<string> visibleMappings =
|
||||
new List<string>();
|
||||
|
||||
if (mappedBy != null)
|
||||
{
|
||||
visibleMappings.AddRange(
|
||||
mappedBy.Where(
|
||||
value =>
|
||||
!string.IsNullOrWhiteSpace(
|
||||
value)));
|
||||
}
|
||||
|
||||
if (isCurrentMapping &&
|
||||
!string.IsNullOrWhiteSpace(
|
||||
CurrentSourceName) &&
|
||||
!visibleMappings.Contains(
|
||||
CurrentSourceName))
|
||||
{
|
||||
visibleMappings.Insert(
|
||||
0,
|
||||
CurrentSourceName);
|
||||
}
|
||||
|
||||
if (visibleMappings.Count > 0)
|
||||
{
|
||||
displayText +=
|
||||
" <- " +
|
||||
string.Join(
|
||||
", ",
|
||||
visibleMappings);
|
||||
}
|
||||
}
|
||||
|
||||
TreeNode treeNode =
|
||||
new TreeNode(
|
||||
displayText);
|
||||
|
||||
treeNode.Tag =
|
||||
payloadNode;
|
||||
|
||||
ApplyMappingAppearance(
|
||||
treeNode,
|
||||
payloadNode,
|
||||
mappedBy,
|
||||
isCurrentMapping);
|
||||
|
||||
foreach (PayloadTemplateNode child
|
||||
in payloadNode.Children)
|
||||
{
|
||||
treeNode.Nodes.Add(
|
||||
CreateTreeNode(
|
||||
child));
|
||||
}
|
||||
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies mapping state colors and tooltips.
|
||||
/// </summary>
|
||||
private void ApplyMappingAppearance(
|
||||
TreeNode treeNode,
|
||||
PayloadTemplateNode payloadNode,
|
||||
IList<string> mappedBy,
|
||||
bool isCurrentMapping)
|
||||
{
|
||||
if (!payloadNode.IsSelectable)
|
||||
{
|
||||
if (payloadNode.IsRepeatingPrototypeRoot)
|
||||
{
|
||||
treeNode.NodeFont =
|
||||
new Font(
|
||||
xmlTreeView.Font,
|
||||
FontStyle.Bold);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCurrentMapping)
|
||||
{
|
||||
treeNode.BackColor =
|
||||
Color.LightBlue;
|
||||
|
||||
treeNode.ToolTipText =
|
||||
"Current mapping for: " +
|
||||
(CurrentSourceName ?? string.Empty);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedBy != null &&
|
||||
mappedBy.Count > 0)
|
||||
{
|
||||
if (payloadNode.DestinationKind ==
|
||||
PayloadDestinationKind.RepeatingPrototype)
|
||||
{
|
||||
treeNode.BackColor =
|
||||
Color.PaleGreen;
|
||||
|
||||
treeNode.ToolTipText =
|
||||
"Repeating prototype destination. Mapped by: " +
|
||||
string.Join(
|
||||
", ",
|
||||
mappedBy);
|
||||
}
|
||||
else
|
||||
{
|
||||
treeNode.BackColor =
|
||||
Color.LightGoldenrodYellow;
|
||||
|
||||
treeNode.ToolTipText =
|
||||
"One-time destination already mapped by: " +
|
||||
string.Join(
|
||||
", ",
|
||||
mappedBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void xmlTreeView_AfterSelect(
|
||||
object sender,
|
||||
TreeViewEventArgs e)
|
||||
{
|
||||
PayloadTemplateNode payloadNode =
|
||||
e.Node != null
|
||||
? e.Node.Tag as PayloadTemplateNode
|
||||
: null;
|
||||
|
||||
UpdateSelectedNodeInformation(
|
||||
payloadNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the detail section for the selected structural destination.
|
||||
/// </summary>
|
||||
private void UpdateSelectedNodeInformation(
|
||||
PayloadTemplateNode payloadNode)
|
||||
{
|
||||
chooseButton.Enabled =
|
||||
false;
|
||||
|
||||
selectedPathTextBox.Text =
|
||||
string.Empty;
|
||||
|
||||
mappedResultsTextBox.Text =
|
||||
string.Empty;
|
||||
|
||||
mappingStatusLabel.Text =
|
||||
"Select an XML attribute or value.";
|
||||
|
||||
if (payloadNode == null ||
|
||||
!payloadNode.IsSelectable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
selectedPathTextBox.Text =
|
||||
payloadNode.DestinationPath ??
|
||||
string.Empty;
|
||||
|
||||
IList<string> mappedBy =
|
||||
GetConfiguredMappings(
|
||||
payloadNode.DestinationPath);
|
||||
|
||||
mappedResultsTextBox.Text =
|
||||
mappedBy != null &&
|
||||
mappedBy.Count > 0
|
||||
? string.Join(
|
||||
", ",
|
||||
mappedBy)
|
||||
: "Not mapped";
|
||||
|
||||
bool isCurrentMapping =
|
||||
string.Equals(
|
||||
payloadNode.DestinationPath,
|
||||
CurrentDestinationPath,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
if (isCurrentMapping)
|
||||
{
|
||||
mappingStatusLabel.Text =
|
||||
"Current mapping.";
|
||||
|
||||
chooseButton.Enabled =
|
||||
true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (payloadNode.DestinationKind ==
|
||||
PayloadDestinationKind.RepeatingPrototype)
|
||||
{
|
||||
if (mappedBy != null &&
|
||||
mappedBy.Count > 0)
|
||||
{
|
||||
mappingStatusLabel.Text =
|
||||
"Repeating prototype destination. Multiple TBF results can use this mapping.";
|
||||
}
|
||||
else
|
||||
{
|
||||
mappingStatusLabel.Text =
|
||||
"Repeating prototype destination.";
|
||||
}
|
||||
|
||||
chooseButton.Enabled =
|
||||
true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedBy != null &&
|
||||
mappedBy.Count > 0)
|
||||
{
|
||||
mappingStatusLabel.Text =
|
||||
"This one-time destination is already mapped.";
|
||||
|
||||
chooseButton.Enabled =
|
||||
false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mappingStatusLabel.Text =
|
||||
"Destination is available.";
|
||||
|
||||
chooseButton.Enabled =
|
||||
true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns configured source names for a destination.
|
||||
/// </summary>
|
||||
private IList<string> GetConfiguredMappings(
|
||||
string destinationPath)
|
||||
{
|
||||
if (ConfiguredMappings == null ||
|
||||
string.IsNullOrWhiteSpace(
|
||||
destinationPath))
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, IList<string>> pair
|
||||
in ConfiguredMappings)
|
||||
{
|
||||
if (string.Equals(
|
||||
pair.Key,
|
||||
destinationPath,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
return pair.Value ??
|
||||
new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
private void chooseButton_Click(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
ChooseSelectedDestination();
|
||||
}
|
||||
|
||||
private void xmlTreeView_NodeMouseDoubleClick(
|
||||
object sender,
|
||||
TreeNodeMouseClickEventArgs e)
|
||||
{
|
||||
if (e.Node == null)
|
||||
return;
|
||||
|
||||
xmlTreeView.SelectedNode =
|
||||
e.Node;
|
||||
|
||||
if (chooseButton.Enabled)
|
||||
{
|
||||
ChooseSelectedDestination();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the selected destination and closes the dialog.
|
||||
/// </summary>
|
||||
private void ChooseSelectedDestination()
|
||||
{
|
||||
TreeNode selectedTreeNode =
|
||||
xmlTreeView.SelectedNode;
|
||||
|
||||
if (selectedTreeNode == null)
|
||||
return;
|
||||
|
||||
PayloadTemplateNode payloadNode =
|
||||
selectedTreeNode.Tag
|
||||
as PayloadTemplateNode;
|
||||
|
||||
if (payloadNode == null ||
|
||||
!payloadNode.IsSelectable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedDestinationPath =
|
||||
payloadNode.DestinationPath;
|
||||
|
||||
DialogResult =
|
||||
DialogResult.OK;
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
private TreeNode FindTreeNode(
|
||||
TreeNodeCollection nodes,
|
||||
string destinationPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
destinationPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (TreeNode node
|
||||
in nodes)
|
||||
{
|
||||
PayloadTemplateNode payloadNode =
|
||||
node.Tag
|
||||
as PayloadTemplateNode;
|
||||
|
||||
if (payloadNode != null &&
|
||||
string.Equals(
|
||||
payloadNode.DestinationPath,
|
||||
destinationPath,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
TreeNode childResult =
|
||||
FindTreeNode(
|
||||
node.Nodes,
|
||||
destinationPath);
|
||||
|
||||
if (childResult != null)
|
||||
return childResult;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ExpandParents(
|
||||
TreeNode node)
|
||||
{
|
||||
TreeNode parent =
|
||||
node.Parent;
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
parent.Expand();
|
||||
|
||||
parent =
|
||||
parent.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,30 +9,140 @@ using TBF.Rig.Generic;
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory component 'UniDataStorageWriter' implements more storing modules
|
||||
/// Modules:
|
||||
/// Provides factory services for the
|
||||
/// <see cref="UniDataStorageWriter"/> component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The factory is responsible for creating runtime
|
||||
/// <see cref="Writer"/> instances and their corresponding
|
||||
/// <see cref="WriterCfg"/> configuration objects.
|
||||
///
|
||||
/// The concrete storage technology is not selected by the factory.
|
||||
/// Technology-specific writer selection is performed later by
|
||||
/// <see cref="Writer"/> according to the active configuration.
|
||||
///
|
||||
/// Consequently, database write modes such as INSERT, UPDATE and
|
||||
/// stored procedure execution do not require separate component
|
||||
/// factories.
|
||||
/// </remarks>
|
||||
public class Factory : IComponentFactory
|
||||
{
|
||||
public string ClassName { get { return GetType().Namespace.Substring(8); } } /// For backward compatibility
|
||||
public override string ToString() { return ClassName; }
|
||||
|
||||
public IComponent DummyComponent() { return new Writer(new WriterCfg("UniDataStorageWriter", this)); }
|
||||
|
||||
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the component class name used by the TBF component framework.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The namespace prefix is removed for backward compatibility with
|
||||
/// existing component configurations.
|
||||
/// </remarks>
|
||||
public string ClassName
|
||||
{
|
||||
WriterCfg WriterCfg = cfg as WriterCfg;
|
||||
if (WriterCfg == null)
|
||||
throw new ArgumentException("Invalid config for UniDataStorageWriter");
|
||||
|
||||
return new Writer(WriterCfg);
|
||||
get { return GetType().Namespace.Substring(8); }
|
||||
}
|
||||
|
||||
public IComponentCfg DefaultConfig() { return new WriterCfg("UniDataStorageWriter", this); }
|
||||
|
||||
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
|
||||
/// <summary>
|
||||
/// Returns the component class name.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The value of <see cref="ClassName"/>.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return ComponentCfgBase.CreateFromDbEntity(WriterCfg.Serializer, component, this);
|
||||
return ClassName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a default runtime instance of the
|
||||
/// <see cref="Writer"/> component.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A new <see cref="Writer"/> configured with a default
|
||||
/// <see cref="WriterCfg"/> instance.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The dummy component is primarily used by the component framework
|
||||
/// for discovery and configuration purposes.
|
||||
/// </remarks>
|
||||
public IComponent DummyComponent()
|
||||
{
|
||||
return new Writer(
|
||||
new WriterCfg("UniDataStorageWriter", this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a runtime <see cref="Writer"/> from an existing
|
||||
/// component configuration.
|
||||
/// </summary>
|
||||
/// <param name="cfg">
|
||||
/// Component configuration expected to be a
|
||||
/// <see cref="WriterCfg"/> instance.
|
||||
/// </param>
|
||||
/// <param name="components">
|
||||
/// Collection of already created components supplied by the
|
||||
/// component framework.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new <see cref="Writer"/> instance using the supplied configuration.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="cfg"/> is not a valid
|
||||
/// <see cref="WriterCfg"/> instance.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// The <paramref name="components"/> collection is currently not
|
||||
/// required by <see cref="Writer"/>, but remains part of the factory
|
||||
/// contract defined by <see cref="IComponentFactory"/>.
|
||||
/// </remarks>
|
||||
public IComponent GetComponent(
|
||||
IComponentCfg cfg,
|
||||
IList<IComponent> components)
|
||||
{
|
||||
WriterCfg writerCfg = cfg as WriterCfg;
|
||||
|
||||
if (writerCfg == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Invalid config for UniDataStorageWriter");
|
||||
}
|
||||
|
||||
return new Writer(writerCfg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the default configuration for the
|
||||
/// <see cref="UniDataStorageWriter"/> component.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A new default <see cref="WriterCfg"/> instance.
|
||||
/// </returns>
|
||||
public IComponentCfg DefaultConfig()
|
||||
{
|
||||
return new WriterCfg(
|
||||
"UniDataStorageWriter",
|
||||
this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="WriterCfg"/> instance from a persisted
|
||||
/// component database entity.
|
||||
/// </summary>
|
||||
/// <param name="component">
|
||||
/// Persisted component entity containing serialized configuration data.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Deserialized component configuration compatible with
|
||||
/// <see cref="Writer"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Configuration deserialization is delegated to
|
||||
/// <see cref="ComponentCfgBase.CreateFromDbEntity"/>.
|
||||
/// </remarks>
|
||||
public IComponentCfg CmpntCfgFromCmpntEntity(
|
||||
Config.Entities.Component component)
|
||||
{
|
||||
return ComponentCfgBase.CreateFromDbEntity(
|
||||
WriterCfg.Serializer,
|
||||
component,
|
||||
this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps one named runtime source value to one XML destination.
|
||||
/// </summary>
|
||||
public class PayloadMapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the logical source key.
|
||||
/// </summary>
|
||||
public string SourceKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the XML destination path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Repeating destinations use the <c>repeat:</c> prefix.
|
||||
/// </remarks>
|
||||
public string DestinationPath { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains named runtime values used during XML generation.
|
||||
/// </summary>
|
||||
public class PayloadValueSet
|
||||
{
|
||||
private readonly Dictionary<string, string> values;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an empty value set.
|
||||
/// </summary>
|
||||
public PayloadValueSet()
|
||||
{
|
||||
values = new Dictionary<string, string>(
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a runtime value.
|
||||
/// </summary>
|
||||
public void SetValue(string key, object value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
throw new ArgumentException(
|
||||
"Payload source key must not be empty.",
|
||||
nameof(key));
|
||||
|
||||
values[key] = value != null
|
||||
? Convert.ToString(value)
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a runtime value.
|
||||
/// </summary>
|
||||
public bool TryGetValue(string key, out string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return values.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes one generated instance of a repeating XML prototype.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each record owns its own mappings. Therefore multiple TBF result items
|
||||
/// may use the same prototype destination while still creating separate
|
||||
/// GROUP/TEST instances.
|
||||
/// </remarks>
|
||||
public class PayloadRepeatRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes an empty repeat record.
|
||||
/// </summary>
|
||||
public PayloadRepeatRecord()
|
||||
{
|
||||
Mappings = new List<PayloadMapping>();
|
||||
Values = new PayloadValueSet();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets mappings applied to this prototype instance.
|
||||
/// </summary>
|
||||
public IList<PayloadMapping> Mappings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets runtime values used by this prototype instance.
|
||||
/// </summary>
|
||||
public PayloadValueSet Values { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes one XML payload generation operation.
|
||||
/// </summary>
|
||||
public class PayloadGenerationRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes an empty generation request.
|
||||
/// </summary>
|
||||
public PayloadGenerationRequest()
|
||||
{
|
||||
SingleMappings = new List<PayloadMapping>();
|
||||
SingleValues = new PayloadValueSet();
|
||||
RepeatRecords = new List<PayloadRepeatRecord>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets mappings applied once to the clean base document.
|
||||
/// </summary>
|
||||
public IList<PayloadMapping> SingleMappings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets values used by one-time mappings.
|
||||
/// </summary>
|
||||
public PayloadValueSet SingleValues { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets generated repeating records.
|
||||
/// </summary>
|
||||
public IList<PayloadRepeatRecord> RepeatRecords { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logical repeat prototype path.
|
||||
/// </summary>
|
||||
public string RepeatPrototypePath { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains a generated XML payload.
|
||||
/// </summary>
|
||||
public class PayloadGenerationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the generated XML document.
|
||||
/// </summary>
|
||||
public System.Xml.Linq.XDocument Document { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the generated XML text.
|
||||
/// </summary>
|
||||
public string Payload { get; internal set; }
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a customer result XML example into a clean base structure and repeat prototypes.
|
||||
/// </summary>
|
||||
public class PayloadReferenceAnalyzer
|
||||
{
|
||||
private const string RepeatPrefix = "repeat:";
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes a customer XML reference/example file.
|
||||
/// </summary>
|
||||
public PayloadTemplateDefinition Analyze(string referencePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(referencePath))
|
||||
throw new ArgumentException("XML reference path must not be empty.", nameof(referencePath));
|
||||
|
||||
if (!File.Exists(referencePath))
|
||||
throw new FileNotFoundException("XML reference file was not found.", referencePath);
|
||||
|
||||
XDocument source = XDocument.Load(referencePath, LoadOptions.PreserveWhitespace);
|
||||
|
||||
if (source.Root == null)
|
||||
throw new InvalidOperationException("XML reference does not contain a root element.");
|
||||
|
||||
List<PayloadRepeatPrototypeDefinition> prototypes =
|
||||
new List<PayloadRepeatPrototypeDefinition>();
|
||||
|
||||
string rootPath = "/" + source.Root.Name.LocalName;
|
||||
|
||||
XElement cleanRoot = CreateCleanBaseElement(
|
||||
source.Root,
|
||||
rootPath,
|
||||
prototypes);
|
||||
|
||||
XDocument cleanDocument = new XDocument(
|
||||
source.Declaration != null ? new XDeclaration(source.Declaration) : null,
|
||||
cleanRoot);
|
||||
|
||||
return new PayloadTemplateDefinition(
|
||||
referencePath,
|
||||
cleanDocument,
|
||||
prototypes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates one clean base element and extracts repeated child sets as prototypes.
|
||||
/// </summary>
|
||||
private XElement CreateCleanBaseElement(
|
||||
XElement source,
|
||||
string currentPath,
|
||||
IList<PayloadRepeatPrototypeDefinition> prototypes)
|
||||
{
|
||||
XElement clean = CreateElementShell(source);
|
||||
|
||||
List<XElement> children = source.Elements().ToList();
|
||||
HashSet<XName> processedNames = new HashSet<XName>();
|
||||
|
||||
foreach (XElement child in children)
|
||||
{
|
||||
if (processedNames.Contains(child.Name))
|
||||
continue;
|
||||
|
||||
processedNames.Add(child.Name);
|
||||
|
||||
List<XElement> sameNameChildren = children
|
||||
.Where(candidate => candidate.Name == child.Name)
|
||||
.ToList();
|
||||
|
||||
string childPath = currentPath + "/" + child.Name.LocalName;
|
||||
|
||||
if (sameNameChildren.Count > 1)
|
||||
{
|
||||
XElement representative = SelectRepresentative(sameNameChildren);
|
||||
XElement cleanPrototype = CreateCleanPrototypeElement(representative);
|
||||
|
||||
prototypes.Add(
|
||||
new PayloadRepeatPrototypeDefinition(
|
||||
RepeatPrefix + childPath,
|
||||
currentPath,
|
||||
cleanPrototype,
|
||||
sameNameChildren.Count));
|
||||
|
||||
// Example instances are deliberately not copied to the base.
|
||||
continue;
|
||||
}
|
||||
|
||||
clean.Add(
|
||||
CreateCleanBaseElement(
|
||||
child,
|
||||
childPath,
|
||||
prototypes));
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a clean prototype element. Nested repeated children are represented once.
|
||||
/// </summary>
|
||||
private XElement CreateCleanPrototypeElement(XElement source)
|
||||
{
|
||||
XElement clean = CreateElementShell(source);
|
||||
|
||||
List<XElement> children = source.Elements().ToList();
|
||||
HashSet<XName> processedNames = new HashSet<XName>();
|
||||
|
||||
foreach (XElement child in children)
|
||||
{
|
||||
if (processedNames.Contains(child.Name))
|
||||
continue;
|
||||
|
||||
processedNames.Add(child.Name);
|
||||
|
||||
List<XElement> sameNameChildren = children
|
||||
.Where(candidate => candidate.Name == child.Name)
|
||||
.ToList();
|
||||
|
||||
XElement representative = sameNameChildren.Count > 1
|
||||
? SelectRepresentative(sameNameChildren)
|
||||
: child;
|
||||
|
||||
clean.Add(CreateCleanPrototypeElement(representative));
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an XML element with original names but without example runtime values.
|
||||
/// </summary>
|
||||
private XElement CreateElementShell(XElement source)
|
||||
{
|
||||
XElement clean = new XElement(source.Name);
|
||||
|
||||
foreach (XAttribute attribute in source.Attributes())
|
||||
{
|
||||
clean.Add(
|
||||
attribute.IsNamespaceDeclaration
|
||||
? new XAttribute(attribute.Name, attribute.Value)
|
||||
: new XAttribute(attribute.Name, string.Empty));
|
||||
}
|
||||
|
||||
return clean;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the structurally richest example from a repeated sibling set.
|
||||
/// </summary>
|
||||
private XElement SelectRepresentative(IList<XElement> examples)
|
||||
{
|
||||
if (examples == null || examples.Count == 0)
|
||||
throw new ArgumentException("Repeated XML example set is empty.", nameof(examples));
|
||||
|
||||
XElement best = examples[0];
|
||||
int bestScore = GetStructuralRichnessScore(best);
|
||||
|
||||
for (int i = 1; i < examples.Count; i++)
|
||||
{
|
||||
int score = GetStructuralRichnessScore(examples[i]);
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
best = examples[i];
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a structure-only richness score.
|
||||
/// </summary>
|
||||
private int GetStructuralRichnessScore(XElement element)
|
||||
{
|
||||
int ownAttributes = element.Attributes()
|
||||
.Count(attribute => !attribute.IsNamespaceDeclaration);
|
||||
|
||||
int descendants = element.Descendants().Count();
|
||||
|
||||
int descendantAttributes = element.Descendants()
|
||||
.SelectMany(descendant => descendant.Attributes())
|
||||
.Count(attribute => !attribute.IsNamespaceDeclaration);
|
||||
|
||||
return ownAttributes + descendants + descendantAttributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes one repeating XML element extracted from a customer reference XML file.
|
||||
/// </summary>
|
||||
public class PayloadRepeatPrototypeDefinition
|
||||
{
|
||||
private readonly XElement prototypeElement;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new repeat prototype definition.
|
||||
/// </summary>
|
||||
public PayloadRepeatPrototypeDefinition(
|
||||
string prototypePath,
|
||||
string parentPath,
|
||||
XElement prototypeElement,
|
||||
int exampleInstanceCount)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prototypePath))
|
||||
throw new ArgumentException("Prototype path must not be empty.", nameof(prototypePath));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(parentPath))
|
||||
throw new ArgumentException("Prototype parent path must not be empty.", nameof(parentPath));
|
||||
|
||||
if (prototypeElement == null)
|
||||
throw new ArgumentNullException(nameof(prototypeElement));
|
||||
|
||||
PrototypePath = prototypePath;
|
||||
ParentPath = parentPath;
|
||||
this.prototypeElement = new XElement(prototypeElement);
|
||||
ExampleInstanceCount = exampleInstanceCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logical repeat path, for example
|
||||
/// <c>repeat:/BATCH/PANEL/DUT/GROUP</c>.
|
||||
/// </summary>
|
||||
public string PrototypePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XPath of the element into which generated prototype instances are inserted.
|
||||
/// </summary>
|
||||
public string ParentPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of repeated instances found in the customer example.
|
||||
/// </summary>
|
||||
public int ExampleInstanceCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an independent clean clone of the repeat prototype.
|
||||
/// </summary>
|
||||
public XElement CreatePrototypeElement()
|
||||
{
|
||||
return new XElement(prototypeElement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when a logical repeating destination belongs to this prototype.
|
||||
/// </summary>
|
||||
public bool ContainsDestination(string destinationPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(destinationPath))
|
||||
return false;
|
||||
|
||||
return string.Equals(destinationPath, PrototypePath, StringComparison.Ordinal) ||
|
||||
destinationPath.StartsWith(PrototypePath + "/", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a clean XML payload structure derived from a customer reference/example XML file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The base document contains only structure. Example runtime values are removed.
|
||||
/// Repeated sibling elements are removed from the base document and represented by
|
||||
/// <see cref="RepeatPrototypes"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class PayloadTemplateDefinition
|
||||
{
|
||||
private readonly XDocument baseDocument;
|
||||
private readonly List<PayloadRepeatPrototypeDefinition> repeatPrototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new payload template definition.
|
||||
/// </summary>
|
||||
public PayloadTemplateDefinition(
|
||||
string referencePath,
|
||||
XDocument baseDocument,
|
||||
IEnumerable<PayloadRepeatPrototypeDefinition> repeatPrototypes)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(referencePath))
|
||||
throw new ArgumentException("Reference path must not be empty.", nameof(referencePath));
|
||||
|
||||
if (baseDocument == null)
|
||||
throw new ArgumentNullException(nameof(baseDocument));
|
||||
|
||||
ReferencePath = referencePath;
|
||||
this.baseDocument = new XDocument(baseDocument);
|
||||
this.repeatPrototypes = repeatPrototypes != null
|
||||
? new List<PayloadRepeatPrototypeDefinition>(repeatPrototypes)
|
||||
: new List<PayloadRepeatPrototypeDefinition>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the customer reference/example XML path.
|
||||
/// </summary>
|
||||
public string ReferencePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the detected repeating XML prototypes.
|
||||
/// </summary>
|
||||
public IList<PayloadRepeatPrototypeDefinition> RepeatPrototypes
|
||||
{
|
||||
get { return repeatPrototypes.AsReadOnly(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an independent clone of the clean base XML document.
|
||||
/// </summary>
|
||||
public XDocument CreateBaseDocument()
|
||||
{
|
||||
return new XDocument(baseDocument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the repeating prototype that owns a logical repeating destination.
|
||||
/// </summary>
|
||||
public PayloadRepeatPrototypeDefinition FindRepeatPrototype(string destinationPath)
|
||||
{
|
||||
return repeatPrototypes.FirstOrDefault(
|
||||
prototype => prototype.ContainsDestination(destinationPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes a customer XML example and creates a structural payload model.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The input XML is treated as a reference example, not as a clean template.
|
||||
/// Runtime values contained in the example are deliberately ignored by the
|
||||
/// viewer model.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Repeated sibling elements are collapsed into one structural prototype.
|
||||
/// For example, multiple GROUP elements below one DUT are displayed as a
|
||||
/// single GROUP repeating prototype. The prototype keeps the XML structure
|
||||
/// and available attributes, while example values and repeated instances
|
||||
/// are hidden.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// This behavior allows configuration to describe the base structure once
|
||||
/// even when the result XML contains many tests with the same structure.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class PayloadTemplateInspector
|
||||
{
|
||||
private const string RepeatPrefix =
|
||||
"repeat:";
|
||||
|
||||
/// <summary>
|
||||
/// Loads and inspects an XML payload example.
|
||||
/// </summary>
|
||||
public PayloadTemplateInspectionResult Inspect(
|
||||
string templatePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
templatePath))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"XML example path must not be empty.",
|
||||
nameof(templatePath));
|
||||
}
|
||||
|
||||
if (!File.Exists(
|
||||
templatePath))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
"XML payload example was not found.",
|
||||
templatePath);
|
||||
}
|
||||
|
||||
XDocument document =
|
||||
XDocument.Load(
|
||||
templatePath,
|
||||
LoadOptions.PreserveWhitespace);
|
||||
|
||||
if (document.Root == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"XML payload example does not contain a root element.");
|
||||
}
|
||||
|
||||
string rootPath =
|
||||
"/" +
|
||||
document.Root.Name.LocalName;
|
||||
|
||||
PayloadTemplateNode rootNode =
|
||||
BuildElementNode(
|
||||
document.Root,
|
||||
rootPath,
|
||||
false,
|
||||
false,
|
||||
1);
|
||||
|
||||
return new PayloadTemplateInspectionResult(
|
||||
templatePath,
|
||||
rootNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds one element node and its structural descendants.
|
||||
/// </summary>
|
||||
private PayloadTemplateNode BuildElementNode(
|
||||
XElement element,
|
||||
string structuralPath,
|
||||
bool insideRepeatingPrototype,
|
||||
bool isRepeatingPrototypeRoot,
|
||||
int representedInstanceCount)
|
||||
{
|
||||
PayloadTemplateNode node =
|
||||
new PayloadTemplateNode
|
||||
{
|
||||
NodeType =
|
||||
PayloadTemplateNodeType.Element,
|
||||
|
||||
Name =
|
||||
element.Name.LocalName,
|
||||
|
||||
DisplayText =
|
||||
CreateElementDisplayText(
|
||||
element.Name.LocalName,
|
||||
isRepeatingPrototypeRoot,
|
||||
representedInstanceCount),
|
||||
|
||||
DestinationPath =
|
||||
CreateDestinationPath(
|
||||
structuralPath,
|
||||
insideRepeatingPrototype ||
|
||||
isRepeatingPrototypeRoot),
|
||||
|
||||
IsSelectable =
|
||||
false,
|
||||
|
||||
DestinationKind =
|
||||
insideRepeatingPrototype ||
|
||||
isRepeatingPrototypeRoot
|
||||
? PayloadDestinationKind.RepeatingPrototype
|
||||
: PayloadDestinationKind.SingleValue,
|
||||
|
||||
IsRepeatingPrototypeRoot =
|
||||
isRepeatingPrototypeRoot,
|
||||
|
||||
ExampleInstanceCount =
|
||||
representedInstanceCount
|
||||
};
|
||||
|
||||
bool effectiveRepeatingState =
|
||||
insideRepeatingPrototype ||
|
||||
isRepeatingPrototypeRoot;
|
||||
|
||||
//
|
||||
// Attributes are structural destinations. Their sample values are
|
||||
// intentionally not copied to the presentation model.
|
||||
//
|
||||
foreach (XAttribute attribute
|
||||
in element.Attributes()
|
||||
.Where(
|
||||
attribute =>
|
||||
!attribute.IsNamespaceDeclaration))
|
||||
{
|
||||
string attributeStructuralPath =
|
||||
structuralPath +
|
||||
"/@" +
|
||||
attribute.Name.LocalName;
|
||||
|
||||
node.Children.Add(
|
||||
new PayloadTemplateNode
|
||||
{
|
||||
NodeType =
|
||||
PayloadTemplateNodeType.Attribute,
|
||||
|
||||
Name =
|
||||
attribute.Name.LocalName,
|
||||
|
||||
DisplayText =
|
||||
"@" +
|
||||
attribute.Name.LocalName,
|
||||
|
||||
DestinationPath =
|
||||
CreateDestinationPath(
|
||||
attributeStructuralPath,
|
||||
effectiveRepeatingState),
|
||||
|
||||
IsSelectable =
|
||||
true,
|
||||
|
||||
DestinationKind =
|
||||
effectiveRepeatingState
|
||||
? PayloadDestinationKind.RepeatingPrototype
|
||||
: PayloadDestinationKind.SingleValue,
|
||||
|
||||
IsRepeatingPrototypeRoot =
|
||||
false,
|
||||
|
||||
ExampleInstanceCount =
|
||||
representedInstanceCount
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Leaf element text can also be a writable destination. The sample
|
||||
// text itself is not exposed.
|
||||
//
|
||||
if (!element.Elements().Any() &&
|
||||
!string.IsNullOrWhiteSpace(
|
||||
element.Value))
|
||||
{
|
||||
node.Children.Add(
|
||||
new PayloadTemplateNode
|
||||
{
|
||||
NodeType =
|
||||
PayloadTemplateNodeType.Text,
|
||||
|
||||
Name =
|
||||
"#text",
|
||||
|
||||
DisplayText =
|
||||
"#text",
|
||||
|
||||
DestinationPath =
|
||||
CreateDestinationPath(
|
||||
structuralPath +
|
||||
"/text()",
|
||||
effectiveRepeatingState),
|
||||
|
||||
IsSelectable =
|
||||
true,
|
||||
|
||||
DestinationKind =
|
||||
effectiveRepeatingState
|
||||
? PayloadDestinationKind.RepeatingPrototype
|
||||
: PayloadDestinationKind.SingleValue,
|
||||
|
||||
IsRepeatingPrototypeRoot =
|
||||
false,
|
||||
|
||||
ExampleInstanceCount =
|
||||
representedInstanceCount
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Group direct children by element name. When one name occurs more
|
||||
// than once, all examples are represented by one prototype.
|
||||
//
|
||||
IEnumerable<IGrouping<XName, XElement>> childGroups =
|
||||
element
|
||||
.Elements()
|
||||
.GroupBy(
|
||||
child =>
|
||||
child.Name);
|
||||
|
||||
foreach (IGrouping<XName, XElement> childGroup
|
||||
in childGroups)
|
||||
{
|
||||
List<XElement> examples =
|
||||
childGroup
|
||||
.ToList();
|
||||
|
||||
bool isRepeated =
|
||||
examples.Count > 1;
|
||||
|
||||
XElement representative =
|
||||
SelectRepresentative(
|
||||
examples);
|
||||
|
||||
string childStructuralPath =
|
||||
structuralPath +
|
||||
"/" +
|
||||
representative.Name.LocalName;
|
||||
|
||||
node.Children.Add(
|
||||
BuildElementNode(
|
||||
representative,
|
||||
childStructuralPath,
|
||||
effectiveRepeatingState,
|
||||
isRepeated,
|
||||
examples.Count));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the richest example from a repeated sibling set.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The representative is chosen by structural richness rather than by
|
||||
/// runtime values. This gives the prototype the best chance of exposing
|
||||
/// all attributes and child elements present in the example set.
|
||||
/// </remarks>
|
||||
private XElement SelectRepresentative(
|
||||
IList<XElement> examples)
|
||||
{
|
||||
if (examples == null ||
|
||||
examples.Count == 0)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Repeated XML example set is empty.",
|
||||
nameof(examples));
|
||||
}
|
||||
|
||||
XElement representative =
|
||||
examples[0];
|
||||
|
||||
int bestScore =
|
||||
GetStructuralRichnessScore(
|
||||
representative);
|
||||
|
||||
for (int i = 1;
|
||||
i < examples.Count;
|
||||
i++)
|
||||
{
|
||||
int score =
|
||||
GetStructuralRichnessScore(
|
||||
examples[i]);
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
representative =
|
||||
examples[i];
|
||||
|
||||
bestScore =
|
||||
score;
|
||||
}
|
||||
}
|
||||
|
||||
return representative;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a simple structure-only richness score.
|
||||
/// </summary>
|
||||
private int GetStructuralRichnessScore(
|
||||
XElement element)
|
||||
{
|
||||
int attributeCount =
|
||||
element
|
||||
.Attributes()
|
||||
.Count(
|
||||
attribute =>
|
||||
!attribute.IsNamespaceDeclaration);
|
||||
|
||||
int childCount =
|
||||
element
|
||||
.Descendants()
|
||||
.Count();
|
||||
|
||||
int descendantAttributeCount =
|
||||
element
|
||||
.Descendants()
|
||||
.SelectMany(
|
||||
descendant =>
|
||||
descendant.Attributes())
|
||||
.Count(
|
||||
attribute =>
|
||||
!attribute.IsNamespaceDeclaration);
|
||||
|
||||
return attributeCount +
|
||||
childCount +
|
||||
descendantAttributeCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the element text shown in the structure viewer.
|
||||
/// </summary>
|
||||
private string CreateElementDisplayText(
|
||||
string elementName,
|
||||
bool isRepeatingPrototypeRoot,
|
||||
int representedInstanceCount)
|
||||
{
|
||||
if (!isRepeatingPrototypeRoot)
|
||||
return elementName;
|
||||
|
||||
return string.Format(
|
||||
"{0} [repeating prototype x{1}]",
|
||||
elementName,
|
||||
representedInstanceCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a logical destination identifier.
|
||||
/// </summary>
|
||||
private string CreateDestinationPath(
|
||||
string structuralPath,
|
||||
bool isRepeating)
|
||||
{
|
||||
if (!isRepeating)
|
||||
return structuralPath;
|
||||
|
||||
return RepeatPrefix +
|
||||
structuralPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the type of a node displayed by an XML payload structure viewer.
|
||||
/// </summary>
|
||||
public enum PayloadTemplateNodeType
|
||||
{
|
||||
/// <summary>
|
||||
/// XML element used primarily for structural navigation.
|
||||
/// </summary>
|
||||
Element,
|
||||
|
||||
/// <summary>
|
||||
/// XML attribute that can be used as a runtime destination.
|
||||
/// </summary>
|
||||
Attribute,
|
||||
|
||||
/// <summary>
|
||||
/// Text content of an XML element that can be used as a runtime destination.
|
||||
/// </summary>
|
||||
Text
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines how a destination behaves in the payload structure.
|
||||
/// </summary>
|
||||
public enum PayloadDestinationKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Destination exists only once in the payload.
|
||||
/// </summary>
|
||||
SingleValue,
|
||||
|
||||
/// <summary>
|
||||
/// Destination belongs to a repeating XML prototype.
|
||||
/// Multiple TBF results may therefore use the same prototype destination.
|
||||
/// </summary>
|
||||
RepeatingPrototype
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents one structural node of an inspected XML payload example.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The model intentionally does not expose sample runtime values from the
|
||||
/// customer example XML. The example file is treated as a structural
|
||||
/// reference from which writable destinations and repeating prototypes are
|
||||
/// derived.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Element nodes are navigation nodes. Attribute and text nodes are
|
||||
/// selectable runtime destinations.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class PayloadTemplateNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the
|
||||
/// <see cref="PayloadTemplateNode"/> class.
|
||||
/// </summary>
|
||||
public PayloadTemplateNode()
|
||||
{
|
||||
Children =
|
||||
new List<PayloadTemplateNode>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the node type.
|
||||
/// </summary>
|
||||
public PayloadTemplateNodeType NodeType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original XML element or attribute name.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text shown by a structure viewer.
|
||||
/// </summary>
|
||||
public string DisplayText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logical destination path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Normal one-time destinations use XPath-like paths, for example:
|
||||
/// </para>
|
||||
///
|
||||
/// <code>
|
||||
/// /BATCH/FACTORY/@TESTER
|
||||
/// </code>
|
||||
///
|
||||
/// <para>
|
||||
/// Destinations belonging to a detected repeating prototype use the
|
||||
/// <c>repeat:</c> prefix, for example:
|
||||
/// </para>
|
||||
///
|
||||
/// <code>
|
||||
/// repeat:/BATCH/PANEL/DUT/GROUP/TEST/@VALUE
|
||||
/// </code>
|
||||
///
|
||||
/// <para>
|
||||
/// The repeat prefix is a logical mapping identifier. It is not intended
|
||||
/// to be evaluated directly as XPath.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public string DestinationPath
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this node can be selected
|
||||
/// as a runtime mapping destination.
|
||||
/// </summary>
|
||||
public bool IsSelectable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the destination behavior.
|
||||
/// </summary>
|
||||
public PayloadDestinationKind DestinationKind
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this element is the root of
|
||||
/// a collapsed repeating prototype.
|
||||
/// </summary>
|
||||
public bool IsRepeatingPrototypeRoot
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of example instances represented by a
|
||||
/// collapsed repeating prototype.
|
||||
/// </summary>
|
||||
public int ExampleInstanceCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets child structural nodes.
|
||||
/// </summary>
|
||||
public List<PayloadTemplateNode> Children
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates this node and all descendant nodes.
|
||||
/// </summary>
|
||||
public IEnumerable<PayloadTemplateNode> Traverse()
|
||||
{
|
||||
yield return this;
|
||||
|
||||
foreach (PayloadTemplateNode child
|
||||
in Children)
|
||||
{
|
||||
foreach (PayloadTemplateNode descendant
|
||||
in child.Traverse())
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the result of inspecting an XML payload example.
|
||||
/// </summary>
|
||||
public class PayloadTemplateInspectionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new inspection result.
|
||||
/// </summary>
|
||||
public PayloadTemplateInspectionResult(
|
||||
string templatePath,
|
||||
PayloadTemplateNode root)
|
||||
{
|
||||
TemplatePath =
|
||||
templatePath;
|
||||
|
||||
Root =
|
||||
root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the inspected XML example path.
|
||||
/// </summary>
|
||||
public string TemplatePath
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root structural node.
|
||||
/// </summary>
|
||||
public PayloadTemplateNode Root
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all selectable destinations.
|
||||
/// </summary>
|
||||
public IEnumerable<PayloadTemplateNode> Destinations
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Root == null)
|
||||
{
|
||||
return Enumerable.Empty<
|
||||
PayloadTemplateNode>();
|
||||
}
|
||||
|
||||
return Root
|
||||
.Traverse()
|
||||
.Where(
|
||||
node =>
|
||||
node.IsSelectable);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a destination by its logical destination path.
|
||||
/// </summary>
|
||||
public PayloadTemplateNode FindDestination(
|
||||
string destinationPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
destinationPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Destinations.FirstOrDefault(
|
||||
node =>
|
||||
string.Equals(
|
||||
node.DestinationPath,
|
||||
destinationPath,
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates XML from a clean payload base, mappings and runtime values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Customer example runtime values are never copied to the output.
|
||||
/// Only explicitly mapped runtime values are written.
|
||||
/// </remarks>
|
||||
public class XmlPayloadGenerator
|
||||
{
|
||||
private const string RepeatPrefix = "repeat:";
|
||||
|
||||
/// <summary>
|
||||
/// Generates one XML payload.
|
||||
/// </summary>
|
||||
public PayloadGenerationResult Generate(
|
||||
PayloadTemplateDefinition definition,
|
||||
PayloadGenerationRequest request)
|
||||
{
|
||||
if (definition == null)
|
||||
throw new ArgumentNullException(nameof(definition));
|
||||
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
|
||||
XDocument document = definition.CreateBaseDocument();
|
||||
|
||||
ApplySingleMappings(
|
||||
document,
|
||||
request.SingleMappings,
|
||||
request.SingleValues);
|
||||
|
||||
if (request.RepeatRecords != null &&
|
||||
request.RepeatRecords.Count > 0)
|
||||
{
|
||||
GenerateRepeatRecords(
|
||||
definition,
|
||||
document,
|
||||
request);
|
||||
}
|
||||
|
||||
return new PayloadGenerationResult
|
||||
{
|
||||
Document = document,
|
||||
Payload = document.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private void ApplySingleMappings(
|
||||
XDocument document,
|
||||
IEnumerable<PayloadMapping> mappings,
|
||||
PayloadValueSet values)
|
||||
{
|
||||
HashSet<string> usedDestinations =
|
||||
new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
foreach (PayloadMapping mapping
|
||||
in mappings ?? Enumerable.Empty<PayloadMapping>())
|
||||
{
|
||||
ValidateMapping(mapping);
|
||||
|
||||
if (mapping.DestinationPath.StartsWith(
|
||||
RepeatPrefix,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Repeating destination was supplied as a single mapping: " +
|
||||
mapping.DestinationPath);
|
||||
}
|
||||
|
||||
if (!usedDestinations.Add(mapping.DestinationPath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"One-time XML destination is mapped more than once: " +
|
||||
mapping.DestinationPath);
|
||||
}
|
||||
|
||||
string value;
|
||||
|
||||
if (!values.TryGetValue(mapping.SourceKey, out value))
|
||||
continue;
|
||||
|
||||
ApplyValue(
|
||||
ResolveSingleDestination(
|
||||
document,
|
||||
mapping.DestinationPath),
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateRepeatRecords(
|
||||
PayloadTemplateDefinition definition,
|
||||
XDocument document,
|
||||
PayloadGenerationRequest request)
|
||||
{
|
||||
PayloadRepeatPrototypeDefinition prototype =
|
||||
ResolvePrototype(
|
||||
definition,
|
||||
request);
|
||||
|
||||
XElement parent =
|
||||
ResolveElement(
|
||||
document,
|
||||
prototype.ParentPath);
|
||||
|
||||
foreach (PayloadRepeatRecord record
|
||||
in request.RepeatRecords)
|
||||
{
|
||||
XElement instance =
|
||||
prototype.CreatePrototypeElement();
|
||||
|
||||
HashSet<string> usedDestinations =
|
||||
new HashSet<string>(
|
||||
StringComparer.Ordinal);
|
||||
|
||||
foreach (PayloadMapping mapping
|
||||
in record.Mappings ??
|
||||
Enumerable.Empty<PayloadMapping>())
|
||||
{
|
||||
ValidateMapping(mapping);
|
||||
|
||||
if (!mapping.DestinationPath.StartsWith(
|
||||
RepeatPrefix,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Non-repeating destination was supplied inside a repeat record: " +
|
||||
mapping.DestinationPath);
|
||||
}
|
||||
|
||||
if (!prototype.ContainsDestination(
|
||||
mapping.DestinationPath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Repeating destination does not belong to prototype '" +
|
||||
prototype.PrototypePath +
|
||||
"': " +
|
||||
mapping.DestinationPath);
|
||||
}
|
||||
|
||||
if (!usedDestinations.Add(
|
||||
mapping.DestinationPath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"One repeat record maps the same XML destination more than once: " +
|
||||
mapping.DestinationPath);
|
||||
}
|
||||
|
||||
string value;
|
||||
|
||||
if (!record.Values.TryGetValue(
|
||||
mapping.SourceKey,
|
||||
out value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyValue(
|
||||
ResolvePrototypeDestination(
|
||||
instance,
|
||||
prototype,
|
||||
mapping.DestinationPath),
|
||||
value);
|
||||
}
|
||||
|
||||
parent.Add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private PayloadRepeatPrototypeDefinition ResolvePrototype(
|
||||
PayloadTemplateDefinition definition,
|
||||
PayloadGenerationRequest request)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(
|
||||
request.RepeatPrototypePath))
|
||||
{
|
||||
PayloadRepeatPrototypeDefinition selected =
|
||||
definition.RepeatPrototypes
|
||||
.FirstOrDefault(
|
||||
prototype =>
|
||||
string.Equals(
|
||||
prototype.PrototypePath,
|
||||
request.RepeatPrototypePath,
|
||||
StringComparison.Ordinal));
|
||||
|
||||
if (selected == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Repeat prototype was not found: " +
|
||||
request.RepeatPrototypePath);
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
foreach (PayloadRepeatRecord record
|
||||
in request.RepeatRecords)
|
||||
{
|
||||
PayloadMapping firstMapping =
|
||||
record.Mappings.FirstOrDefault();
|
||||
|
||||
if (firstMapping == null)
|
||||
continue;
|
||||
|
||||
PayloadRepeatPrototypeDefinition inferred =
|
||||
definition.FindRepeatPrototype(
|
||||
firstMapping.DestinationPath);
|
||||
|
||||
if (inferred != null)
|
||||
return inferred;
|
||||
}
|
||||
|
||||
if (definition.RepeatPrototypes.Count == 1)
|
||||
return definition.RepeatPrototypes[0];
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"Repeat prototype cannot be inferred.");
|
||||
}
|
||||
|
||||
private XObject ResolveSingleDestination(
|
||||
XDocument document,
|
||||
string destinationPath)
|
||||
{
|
||||
List<XObject> matches =
|
||||
EvaluateNodes(
|
||||
document,
|
||||
destinationPath);
|
||||
|
||||
if (matches.Count == 0)
|
||||
throw new InvalidOperationException(
|
||||
"XML destination was not found in the clean base: " +
|
||||
destinationPath);
|
||||
|
||||
if (matches.Count > 1)
|
||||
throw new InvalidOperationException(
|
||||
"XML destination is ambiguous in the clean base: " +
|
||||
destinationPath);
|
||||
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
private XObject ResolvePrototypeDestination(
|
||||
XElement instance,
|
||||
PayloadRepeatPrototypeDefinition prototype,
|
||||
string destinationPath)
|
||||
{
|
||||
string suffix =
|
||||
destinationPath.Substring(
|
||||
prototype.PrototypePath.Length);
|
||||
|
||||
string relativePath =
|
||||
string.IsNullOrEmpty(suffix)
|
||||
? "."
|
||||
: "." + suffix;
|
||||
|
||||
List<XObject> matches =
|
||||
EvaluateNodes(
|
||||
instance,
|
||||
relativePath);
|
||||
|
||||
if (matches.Count == 0)
|
||||
throw new InvalidOperationException(
|
||||
"Repeat prototype destination was not found: " +
|
||||
destinationPath);
|
||||
|
||||
if (matches.Count > 1)
|
||||
throw new InvalidOperationException(
|
||||
"Repeat prototype destination is ambiguous: " +
|
||||
destinationPath);
|
||||
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
private XElement ResolveElement(
|
||||
XDocument document,
|
||||
string path)
|
||||
{
|
||||
List<XObject> matches =
|
||||
EvaluateNodes(
|
||||
document,
|
||||
path);
|
||||
|
||||
if (matches.Count != 1 ||
|
||||
!(matches[0] is XElement))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Prototype parent element cannot be uniquely resolved: " +
|
||||
path);
|
||||
}
|
||||
|
||||
return (XElement)matches[0];
|
||||
}
|
||||
|
||||
private List<XObject> EvaluateNodes(
|
||||
XNode context,
|
||||
string xpath)
|
||||
{
|
||||
object evaluationResult;
|
||||
|
||||
try
|
||||
{
|
||||
evaluationResult =
|
||||
context.XPathEvaluate(xpath);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Invalid XML destination path: " +
|
||||
xpath,
|
||||
exc);
|
||||
}
|
||||
|
||||
IEnumerable enumerable =
|
||||
evaluationResult as IEnumerable;
|
||||
|
||||
if (enumerable == null)
|
||||
return new List<XObject>();
|
||||
|
||||
return enumerable
|
||||
.Cast<object>()
|
||||
.OfType<XObject>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void ApplyValue(
|
||||
XObject destination,
|
||||
string value)
|
||||
{
|
||||
string safeValue =
|
||||
value ?? string.Empty;
|
||||
|
||||
XAttribute attribute =
|
||||
destination as XAttribute;
|
||||
|
||||
if (attribute != null)
|
||||
{
|
||||
attribute.Value = safeValue;
|
||||
return;
|
||||
}
|
||||
|
||||
XElement element =
|
||||
destination as XElement;
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
element.Value = safeValue;
|
||||
return;
|
||||
}
|
||||
|
||||
XText text =
|
||||
destination as XText;
|
||||
|
||||
if (text != null)
|
||||
{
|
||||
text.Value = safeValue;
|
||||
return;
|
||||
}
|
||||
|
||||
XCData cdata =
|
||||
destination as XCData;
|
||||
|
||||
if (cdata != null)
|
||||
{
|
||||
cdata.Value = safeValue;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"Unsupported XML destination node type: " +
|
||||
destination.GetType().FullName);
|
||||
}
|
||||
|
||||
private void ValidateMapping(
|
||||
PayloadMapping mapping)
|
||||
{
|
||||
if (mapping == null)
|
||||
throw new InvalidOperationException(
|
||||
"Payload mapping is null.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
mapping.SourceKey))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Payload mapping source key is empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
mapping.DestinationPath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Payload mapping destination path is empty.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+609
@@ -0,0 +1,609 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates XML from the configured customer reference file and prepares
|
||||
/// either a stored-procedure request or an XML-file request.
|
||||
/// </summary>
|
||||
public class XmlPayloadRequestBuilder
|
||||
{
|
||||
private readonly WriterCfg cfg;
|
||||
private readonly PayloadReferenceAnalyzer analyzer;
|
||||
private readonly XmlPayloadGenerator generator;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new XML payload request builder.
|
||||
/// </summary>
|
||||
public XmlPayloadRequestBuilder(
|
||||
WriterCfg cfg)
|
||||
{
|
||||
this.cfg =
|
||||
cfg ??
|
||||
throw new ArgumentNullException(
|
||||
nameof(cfg));
|
||||
|
||||
analyzer =
|
||||
new PayloadReferenceAnalyzer();
|
||||
|
||||
generator =
|
||||
new XmlPayloadGenerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first detected repeat prototype path.
|
||||
/// </summary>
|
||||
public string GetDefaultRepeatPrototypePath()
|
||||
{
|
||||
ValidateReference();
|
||||
|
||||
PayloadTemplateDefinition definition =
|
||||
analyzer.Analyze(
|
||||
cfg.PayloadTemplatePath);
|
||||
|
||||
if (definition.RepeatPrototypes.Count == 0)
|
||||
return null;
|
||||
|
||||
return definition
|
||||
.RepeatPrototypes[0]
|
||||
.PrototypePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a production payload and creates a stored-procedure request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method preserves the original stored-procedure behavior.
|
||||
/// </remarks>
|
||||
public XmlPayloadBuildResult Build(
|
||||
PayloadGenerationRequest generationRequest,
|
||||
string archiveFilePrefix = null)
|
||||
{
|
||||
if (generationRequest == null)
|
||||
throw new ArgumentNullException(
|
||||
nameof(generationRequest));
|
||||
|
||||
ValidateStoredProcedureConfiguration();
|
||||
|
||||
string payload =
|
||||
GeneratePayload(
|
||||
generationRequest);
|
||||
|
||||
string archiveFilePath =
|
||||
null;
|
||||
|
||||
string outputFileName =
|
||||
CreatePayloadFileName(
|
||||
archiveFilePrefix,
|
||||
false);
|
||||
|
||||
if (cfg.ArchivePayload)
|
||||
{
|
||||
archiveFilePath =
|
||||
ArchivePayload(
|
||||
payload,
|
||||
outputFileName,
|
||||
cfg.PayloadArchivePath);
|
||||
}
|
||||
|
||||
DataWriteRequest request =
|
||||
new DataWriteRequest
|
||||
{
|
||||
Mode =
|
||||
WriteMode.StoredProcedure
|
||||
};
|
||||
|
||||
request.StoredProcedureParameters.Add(
|
||||
new StoredProcedureWriteParameter
|
||||
{
|
||||
ParameterName =
|
||||
cfg.PayloadParameterName,
|
||||
|
||||
ParameterType =
|
||||
StoredProcedureParameterType.Xml,
|
||||
|
||||
Value =
|
||||
payload
|
||||
});
|
||||
|
||||
return new XmlPayloadBuildResult
|
||||
{
|
||||
Payload =
|
||||
payload,
|
||||
|
||||
Request =
|
||||
request,
|
||||
|
||||
ArchiveFilePath =
|
||||
archiveFilePath,
|
||||
|
||||
OutputFileName =
|
||||
outputFileName
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a production payload and creates an XML-file request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The physical file is not written here. The returned request is sent
|
||||
/// through UniDataStorageWriter and is handled by
|
||||
/// <see cref="TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers.XmlFileWriter"/>.
|
||||
/// </remarks>
|
||||
public XmlPayloadBuildResult BuildFile(
|
||||
PayloadGenerationRequest generationRequest,
|
||||
string filePrefix = null)
|
||||
{
|
||||
if (generationRequest == null)
|
||||
throw new ArgumentNullException(
|
||||
nameof(generationRequest));
|
||||
|
||||
ValidateXmlFileConfiguration();
|
||||
|
||||
string payload =
|
||||
GeneratePayload(
|
||||
generationRequest);
|
||||
|
||||
string outputFileName =
|
||||
CreatePayloadFileName(
|
||||
filePrefix,
|
||||
false);
|
||||
|
||||
DataWriteRequest request =
|
||||
new DataWriteRequest
|
||||
{
|
||||
Mode =
|
||||
WriteMode.Insert,
|
||||
|
||||
Payload =
|
||||
payload,
|
||||
|
||||
OutputFileName =
|
||||
outputFileName
|
||||
};
|
||||
|
||||
return new XmlPayloadBuildResult
|
||||
{
|
||||
Payload =
|
||||
payload,
|
||||
|
||||
Request =
|
||||
request,
|
||||
|
||||
ArchiveFilePath =
|
||||
null,
|
||||
|
||||
OutputFileName =
|
||||
outputFileName
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates and saves a preview payload without executing a database
|
||||
/// operation or a production XML-file write request.
|
||||
/// </summary>
|
||||
public XmlPayloadBuildResult BuildPreview(
|
||||
PayloadGenerationRequest generationRequest,
|
||||
string archiveFilePrefix = null)
|
||||
{
|
||||
if (generationRequest == null)
|
||||
throw new ArgumentNullException(
|
||||
nameof(generationRequest));
|
||||
|
||||
ValidateReference();
|
||||
|
||||
string payload =
|
||||
GeneratePayload(
|
||||
generationRequest);
|
||||
|
||||
string previewDirectory =
|
||||
ResolvePreviewDirectory();
|
||||
|
||||
string outputFileName =
|
||||
CreatePayloadFileName(
|
||||
archiveFilePrefix,
|
||||
true);
|
||||
|
||||
string archiveFilePath =
|
||||
ArchivePayload(
|
||||
payload,
|
||||
outputFileName,
|
||||
previewDirectory);
|
||||
|
||||
return new XmlPayloadBuildResult
|
||||
{
|
||||
Payload =
|
||||
payload,
|
||||
|
||||
Request =
|
||||
null,
|
||||
|
||||
ArchiveFilePath =
|
||||
archiveFilePath,
|
||||
|
||||
OutputFileName =
|
||||
outputFileName
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the complete XML payload.
|
||||
/// </summary>
|
||||
private string GeneratePayload(
|
||||
PayloadGenerationRequest generationRequest)
|
||||
{
|
||||
PayloadTemplateDefinition definition =
|
||||
analyzer.Analyze(
|
||||
cfg.PayloadTemplatePath);
|
||||
|
||||
return generator.Generate(
|
||||
definition,
|
||||
generationRequest)
|
||||
.Payload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the common XML reference configuration.
|
||||
/// </summary>
|
||||
private void ValidateReference()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
cfg.PayloadTemplatePath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Payload reference XML path is not configured.");
|
||||
}
|
||||
|
||||
if (!File.Exists(
|
||||
cfg.PayloadTemplatePath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Payload reference XML does not exist: " +
|
||||
cfg.PayloadTemplatePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates Microsoft SQL stored-procedure payload configuration.
|
||||
/// </summary>
|
||||
private void ValidateStoredProcedureConfiguration()
|
||||
{
|
||||
ValidateReference();
|
||||
|
||||
if (!cfg.IsStoredProcedurePayloadTarget())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Writer configuration is not an XML stored-procedure target.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
cfg.PayloadParameterName))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Payload parameter name is not configured.");
|
||||
}
|
||||
|
||||
string storedProcedureName =
|
||||
cfg.GetTemplate(
|
||||
WriteMode.StoredProcedure);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
storedProcedureName))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Stored procedure name is not configured.");
|
||||
}
|
||||
|
||||
if (cfg.ArchivePayload &&
|
||||
string.IsNullOrWhiteSpace(
|
||||
cfg.PayloadArchivePath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Payload archive path is not configured.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates XML-file payload configuration.
|
||||
/// </summary>
|
||||
private void ValidateXmlFileConfiguration()
|
||||
{
|
||||
ValidateReference();
|
||||
|
||||
if (!cfg.IsXmlFileTarget())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Writer configuration is not an XML file target.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
cfg.DataSource))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"XML output directory is not configured.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the directory used by Preview request.
|
||||
/// </summary>
|
||||
private string ResolvePreviewDirectory()
|
||||
{
|
||||
if (cfg.IsXmlFileTarget() &&
|
||||
!string.IsNullOrWhiteSpace(
|
||||
cfg.DataSource))
|
||||
{
|
||||
return cfg.DataSource;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(
|
||||
cfg.PayloadArchivePath))
|
||||
{
|
||||
return cfg.PayloadArchivePath;
|
||||
}
|
||||
|
||||
return Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"TBF",
|
||||
"ResultsWriterPreview");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Archives one generated XML payload.
|
||||
/// </summary>
|
||||
private string ArchivePayload(
|
||||
string payload,
|
||||
string fileName,
|
||||
string archiveDirectory)
|
||||
{
|
||||
Directory.CreateDirectory(
|
||||
archiveDirectory);
|
||||
|
||||
string fullPath =
|
||||
Path.Combine(
|
||||
archiveDirectory,
|
||||
fileName);
|
||||
|
||||
fullPath =
|
||||
CreateUniqueFilePath(
|
||||
fullPath);
|
||||
|
||||
File.WriteAllText(
|
||||
fullPath,
|
||||
payload,
|
||||
new UTF8Encoding(false));
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the standard generated payload file name.
|
||||
/// </summary>
|
||||
private string CreatePayloadFileName(
|
||||
string payloadIdentifier,
|
||||
bool preview)
|
||||
{
|
||||
string payloadName =
|
||||
GetPayloadBaseName();
|
||||
|
||||
string safeIdentifier =
|
||||
CreateSafeFileNamePart(
|
||||
payloadIdentifier);
|
||||
|
||||
string timestamp =
|
||||
DateTime.Now.ToString(
|
||||
"yyyyMMdd_HHmmss_fff");
|
||||
|
||||
string fileName =
|
||||
payloadName;
|
||||
|
||||
if (preview)
|
||||
{
|
||||
fileName +=
|
||||
"_PREVIEW";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(
|
||||
safeIdentifier))
|
||||
{
|
||||
fileName +=
|
||||
"_" +
|
||||
safeIdentifier;
|
||||
}
|
||||
|
||||
fileName +=
|
||||
"_" +
|
||||
timestamp +
|
||||
".xml";
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derives a generic payload name from the customer reference file.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <c>sp_InsertDashboardResults_FF_Example.xml</c> becomes
|
||||
/// <c>DashboardResults_FF</c>.
|
||||
/// </example>
|
||||
private string GetPayloadBaseName()
|
||||
{
|
||||
string name =
|
||||
Path.GetFileNameWithoutExtension(
|
||||
cfg.PayloadTemplatePath) ??
|
||||
string.Empty;
|
||||
|
||||
string[] suffixes =
|
||||
{
|
||||
"_ReferenceExample",
|
||||
"_Reference",
|
||||
"_Example"
|
||||
};
|
||||
|
||||
foreach (string suffix
|
||||
in suffixes)
|
||||
{
|
||||
if (name.EndsWith(
|
||||
suffix,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
name =
|
||||
name.Substring(
|
||||
0,
|
||||
name.Length -
|
||||
suffix.Length);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const string storedProcedurePrefix =
|
||||
"sp_Insert";
|
||||
|
||||
if (name.StartsWith(
|
||||
storedProcedurePrefix,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
name =
|
||||
name.Substring(
|
||||
storedProcedurePrefix.Length);
|
||||
}
|
||||
|
||||
name =
|
||||
CreateSafeFileNamePart(
|
||||
name);
|
||||
|
||||
return string.IsNullOrWhiteSpace(
|
||||
name)
|
||||
? "Payload"
|
||||
: name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents accidental overwrite of archived preview files.
|
||||
/// </summary>
|
||||
private string CreateUniqueFilePath(
|
||||
string fullPath)
|
||||
{
|
||||
if (!File.Exists(
|
||||
fullPath))
|
||||
{
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
string directory =
|
||||
Path.GetDirectoryName(
|
||||
fullPath);
|
||||
|
||||
string name =
|
||||
Path.GetFileNameWithoutExtension(
|
||||
fullPath);
|
||||
|
||||
string extension =
|
||||
Path.GetExtension(
|
||||
fullPath);
|
||||
|
||||
int index =
|
||||
1;
|
||||
|
||||
string candidate;
|
||||
|
||||
do
|
||||
{
|
||||
candidate =
|
||||
Path.Combine(
|
||||
directory,
|
||||
string.Format(
|
||||
"{0}_{1}{2}",
|
||||
name,
|
||||
index,
|
||||
extension));
|
||||
|
||||
index++;
|
||||
}
|
||||
while (File.Exists(
|
||||
candidate));
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts arbitrary text to a safe file-name component.
|
||||
/// </summary>
|
||||
private string CreateSafeFileNamePart(
|
||||
string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string result =
|
||||
value.Trim();
|
||||
|
||||
foreach (char invalidCharacter
|
||||
in Path.GetInvalidFileNameChars())
|
||||
{
|
||||
result =
|
||||
result.Replace(
|
||||
invalidCharacter,
|
||||
'_');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains generated XML and the optional physical write request.
|
||||
/// </summary>
|
||||
public class XmlPayloadBuildResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the complete generated XML.
|
||||
/// </summary>
|
||||
public string Payload
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request sent to UniDataStorageWriter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is null for Preview request.
|
||||
/// </remarks>
|
||||
public DataWriteRequest Request
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the preview/archive file path when the builder wrote a diagnostic copy.
|
||||
/// </summary>
|
||||
public string ArchiveFilePath
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the generated production or preview file name.
|
||||
/// </summary>
|
||||
public string OutputFileName
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces
|
||||
{
|
||||
InsertItems = new List<InsertWriteItem>();
|
||||
UpdateItems = new List<UpdateWriteItem>();
|
||||
StoredProcedureParameters = new List<StoredProcedureWriteParameter>();
|
||||
Payload = string.Empty;
|
||||
OutputFileName = string.Empty;
|
||||
Mode = WriteMode.Insert;
|
||||
}
|
||||
|
||||
@@ -20,6 +23,32 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces
|
||||
public List<InsertWriteItem> InsertItems { get; private set; }
|
||||
|
||||
public List<UpdateWriteItem> UpdateItems { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parameters passed to a stored procedure when Mode is StoredProcedure.
|
||||
/// The stored procedure name itself is resolved from WriterCfg.WriteTemplates.
|
||||
/// </summary>
|
||||
public List<StoredProcedureWriteParameter> StoredProcedureParameters { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional complete payload generated by a higher-level component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// File-oriented payload writers use this field when the complete
|
||||
/// serialized document is already available and must be written without
|
||||
/// converting it back to individual column/value pairs.
|
||||
/// </remarks>
|
||||
public string Payload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional output file name suggested by the caller.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The physical output directory is still defined by
|
||||
/// <see cref="WriterCfg.DataSource"/>. Writers may generate a safe default
|
||||
/// file name when this value is empty.
|
||||
/// </remarks>
|
||||
public string OutputFileName { get; set; }
|
||||
}
|
||||
|
||||
public enum WriteMode
|
||||
@@ -27,7 +56,22 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces
|
||||
Insert,
|
||||
Update,
|
||||
Upsert,
|
||||
Append
|
||||
Append,
|
||||
StoredProcedure
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic parameter types supported by the Microsoft SQL stored procedure writer.
|
||||
/// </summary>
|
||||
public enum StoredProcedureParameterType
|
||||
{
|
||||
String,
|
||||
Xml,
|
||||
Int32,
|
||||
Int64,
|
||||
Decimal,
|
||||
Boolean,
|
||||
DateTime
|
||||
}
|
||||
|
||||
public class InsertWriteItem
|
||||
@@ -54,4 +98,25 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces
|
||||
return $"WHERE {WhereParameterName} = {WhereValue} -> SET {SetParameterName} = {SetValue}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One parameter passed to a stored procedure.
|
||||
/// Value is object so the writer can preserve the requested SQL data type.
|
||||
/// </summary>
|
||||
public class StoredProcedureWriteParameter
|
||||
{
|
||||
public string ParameterName { get; set; }
|
||||
public object Value { get; set; }
|
||||
public StoredProcedureParameterType ParameterType { get; set; }
|
||||
|
||||
public StoredProcedureWriteParameter()
|
||||
{
|
||||
ParameterType = StoredProcedureParameterType.String;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ParameterName} ({ParameterType}) = {Value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,90 @@
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Diagnostic;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the common contract implemented by all storage writer
|
||||
/// technologies supported by <see cref="Writer"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations of this interface encapsulate the technology-specific
|
||||
/// logic required to persist data.
|
||||
///
|
||||
/// Examples include:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Microsoft SQL database writer.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>CSV file writer.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>XLS/XLSX file writer.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>JSON file writer.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>REST API writer.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// The caller does not need to know the concrete writer implementation.
|
||||
/// The appropriate implementation is selected by <see cref="Writer"/>
|
||||
/// according to the active <see cref="WriterCfg"/> configuration.
|
||||
/// </remarks>
|
||||
public interface IDataStorageWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the storage types, technologies and write modes supported
|
||||
/// by this writer implementation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The capabilities are validated by <see cref="Writer"/> before
|
||||
/// a write request is executed.
|
||||
///
|
||||
/// For example, <c>DatabaseWriter</c> can advertise support for
|
||||
/// <see cref="WriteMode.Insert"/>,
|
||||
/// <see cref="WriteMode.Update"/> and
|
||||
/// <see cref="WriteMode.StoredProcedure"/>.
|
||||
/// </remarks>
|
||||
WriterCapabilities Capabilities { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the configured data source is accessible and valid
|
||||
/// for the current writer.
|
||||
/// </summary>
|
||||
/// <param name="validateOnly">
|
||||
/// When <c>true</c>, the writer should perform only the minimum
|
||||
/// validation required to verify the configured source.
|
||||
/// When <c>false</c>, the implementation may perform an additional
|
||||
/// lightweight connectivity test.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="WriterDiagnosticResult"/> describing whether the
|
||||
/// configured source is valid and accessible.
|
||||
/// </returns>
|
||||
WriterDiagnosticResult TestSource(bool validateOnly);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the configured storage target.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// Write request containing the operation mode and data required
|
||||
/// by the selected storage implementation.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="WriterDiagnosticResult"/> describing the result
|
||||
/// of the write operation.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The interpretation of <paramref name="request"/> depends on
|
||||
/// <see cref="DataWriteRequest.Mode"/>.
|
||||
///
|
||||
/// For example, a database writer may execute an INSERT, UPDATE
|
||||
/// or stored procedure call, while a file writer may append or
|
||||
/// serialize the supplied values.
|
||||
/// </remarks>
|
||||
WriterDiagnosticResult WriteData(DataWriteRequest request);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
public const string Xls = ".xls";
|
||||
public const string Xlsx = ".xlsx";
|
||||
public const string Json = ".json";
|
||||
public const string Xml = ".xml";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+142
-15
@@ -72,11 +72,22 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.examples1Button = new System.Windows.Forms.Button();
|
||||
this.technologyTypeLabel = new System.Windows.Forms.Label();
|
||||
this.technologyTypeComboBox = new System.Windows.Forms.ComboBox();
|
||||
this.groupBox6 = new System.Windows.Forms.GroupBox();
|
||||
this.browsePayloadArchiveButton = new System.Windows.Forms.Button();
|
||||
this.payloadArchivePathTextBox = new System.Windows.Forms.TextBox();
|
||||
this.payloadArchivePathLabel = new System.Windows.Forms.Label();
|
||||
this.archivePayloadCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.payloadParameterNameTextBox = new System.Windows.Forms.TextBox();
|
||||
this.payloadParameterNameLabel = new System.Windows.Forms.Label();
|
||||
this.browsePayloadTemplateButton = new System.Windows.Forms.Button();
|
||||
this.payloadTemplatePathTextBox = new System.Windows.Forms.TextBox();
|
||||
this.payloadTemplatePathLabel = new System.Windows.Forms.Label();
|
||||
this.groupBox1.SuspendLayout();
|
||||
this.groupBox2.SuspendLayout();
|
||||
this.groupBox3.SuspendLayout();
|
||||
this.groupBox4.SuspendLayout();
|
||||
this.groupBox5.SuspendLayout();
|
||||
this.groupBox6.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// nameTextBox
|
||||
@@ -128,9 +139,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.groupBox1.Controls.Add(this.info2Button);
|
||||
this.groupBox1.Controls.Add(this.sourceTestResultTextBox);
|
||||
this.groupBox1.Controls.Add(this.connectToDataSourceButton);
|
||||
this.groupBox1.Location = new System.Drawing.Point(8, 258);
|
||||
this.groupBox1.Location = new System.Drawing.Point(8, 394);
|
||||
this.groupBox1.Name = "groupBox1";
|
||||
this.groupBox1.Size = new System.Drawing.Size(473, 461);
|
||||
this.groupBox1.Size = new System.Drawing.Size(473, 325);
|
||||
this.groupBox1.TabIndex = 15;
|
||||
this.groupBox1.TabStop = false;
|
||||
this.groupBox1.Text = "Data storage source testing";
|
||||
@@ -151,7 +162,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.sourceTestResultTextBox.Multiline = true;
|
||||
this.sourceTestResultTextBox.Name = "sourceTestResultTextBox";
|
||||
this.sourceTestResultTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.sourceTestResultTextBox.Size = new System.Drawing.Size(437, 403);
|
||||
this.sourceTestResultTextBox.Size = new System.Drawing.Size(437, 267);
|
||||
this.sourceTestResultTextBox.TabIndex = 15;
|
||||
//
|
||||
// connectToDataSourceButton
|
||||
@@ -168,9 +179,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.groupBox2.Controls.Add(this.info4Button);
|
||||
this.groupBox2.Controls.Add(this.writeTestResultTextBox);
|
||||
this.groupBox2.Controls.Add(this.writeDataByParamAndTemplateButton);
|
||||
this.groupBox2.Location = new System.Drawing.Point(487, 258);
|
||||
this.groupBox2.Location = new System.Drawing.Point(487, 394);
|
||||
this.groupBox2.Name = "groupBox2";
|
||||
this.groupBox2.Size = new System.Drawing.Size(463, 461);
|
||||
this.groupBox2.Size = new System.Drawing.Size(463, 325);
|
||||
this.groupBox2.TabIndex = 16;
|
||||
this.groupBox2.TabStop = false;
|
||||
this.groupBox2.Text = "Complete write testing";
|
||||
@@ -191,7 +202,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.writeTestResultTextBox.Multiline = true;
|
||||
this.writeTestResultTextBox.Name = "writeTestResultTextBox";
|
||||
this.writeTestResultTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.writeTestResultTextBox.Size = new System.Drawing.Size(446, 402);
|
||||
this.writeTestResultTextBox.Size = new System.Drawing.Size(446, 266);
|
||||
this.writeTestResultTextBox.TabIndex = 18;
|
||||
//
|
||||
// writeDataByParamAndTemplateButton
|
||||
@@ -213,9 +224,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.groupBox3.Controls.Add(this.listBoxWriteParams);
|
||||
this.groupBox3.Controls.Add(this.writeParamValueTextBox);
|
||||
this.groupBox3.Controls.Add(this.info5Button);
|
||||
this.groupBox3.Location = new System.Drawing.Point(956, 258);
|
||||
this.groupBox3.Location = new System.Drawing.Point(956, 394);
|
||||
this.groupBox3.Name = "groupBox3";
|
||||
this.groupBox3.Size = new System.Drawing.Size(316, 461);
|
||||
this.groupBox3.Size = new System.Drawing.Size(316, 325);
|
||||
this.groupBox3.TabIndex = 17;
|
||||
this.groupBox3.TabStop = false;
|
||||
this.groupBox3.Text = "Component interface testing";
|
||||
@@ -240,7 +251,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
//
|
||||
// buttonRemoveParam
|
||||
//
|
||||
this.buttonRemoveParam.Location = new System.Drawing.Point(87, 94);
|
||||
this.buttonRemoveParam.Location = new System.Drawing.Point(90, 159);
|
||||
this.buttonRemoveParam.Name = "buttonRemoveParam";
|
||||
this.buttonRemoveParam.Size = new System.Drawing.Size(75, 23);
|
||||
this.buttonRemoveParam.TabIndex = 25;
|
||||
@@ -249,7 +260,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
//
|
||||
// buttonAddParam
|
||||
//
|
||||
this.buttonAddParam.Location = new System.Drawing.Point(8, 94);
|
||||
this.buttonAddParam.Location = new System.Drawing.Point(9, 159);
|
||||
this.buttonAddParam.Name = "buttonAddParam";
|
||||
this.buttonAddParam.Size = new System.Drawing.Size(75, 23);
|
||||
this.buttonAddParam.TabIndex = 24;
|
||||
@@ -268,17 +279,22 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
// listBoxWriteParams
|
||||
//
|
||||
this.listBoxWriteParams.FormattingEnabled = true;
|
||||
this.listBoxWriteParams.Location = new System.Drawing.Point(9, 123);
|
||||
this.listBoxWriteParams.Location = new System.Drawing.Point(9, 188);
|
||||
this.listBoxWriteParams.Name = "listBoxWriteParams";
|
||||
this.listBoxWriteParams.Size = new System.Drawing.Size(297, 329);
|
||||
this.listBoxWriteParams.Size = new System.Drawing.Size(297, 121);
|
||||
this.listBoxWriteParams.TabIndex = 22;
|
||||
//
|
||||
// writeParamValueTextBox
|
||||
//
|
||||
this.writeParamValueTextBox.AcceptsReturn = true;
|
||||
this.writeParamValueTextBox.AcceptsTab = true;
|
||||
this.writeParamValueTextBox.Location = new System.Drawing.Point(9, 68);
|
||||
this.writeParamValueTextBox.Multiline = true;
|
||||
this.writeParamValueTextBox.Name = "writeParamValueTextBox";
|
||||
this.writeParamValueTextBox.Size = new System.Drawing.Size(297, 20);
|
||||
this.writeParamValueTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.writeParamValueTextBox.Size = new System.Drawing.Size(297, 85);
|
||||
this.writeParamValueTextBox.TabIndex = 21;
|
||||
this.writeParamValueTextBox.WordWrap = false;
|
||||
//
|
||||
// info5Button
|
||||
//
|
||||
@@ -390,7 +406,6 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.buttonUpdateTemplate.TabIndex = 35;
|
||||
this.buttonUpdateTemplate.Text = "Update";
|
||||
this.buttonUpdateTemplate.UseVisualStyleBackColor = true;
|
||||
this.buttonUpdateTemplate.Click += new System.EventHandler(this.buttonUpdateTemplate_Click);
|
||||
//
|
||||
// buttonAddTemplate
|
||||
//
|
||||
@@ -401,7 +416,6 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.buttonAddTemplate.TabIndex = 34;
|
||||
this.buttonAddTemplate.Text = "Add";
|
||||
this.buttonAddTemplate.UseVisualStyleBackColor = true;
|
||||
this.buttonAddTemplate.Click += new System.EventHandler(this.buttonAddTemplate_Click);
|
||||
//
|
||||
// templateEditTextBox
|
||||
//
|
||||
@@ -468,10 +482,111 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.technologyTypeComboBox.Size = new System.Drawing.Size(326, 21);
|
||||
this.technologyTypeComboBox.TabIndex = 25;
|
||||
//
|
||||
// groupBox6
|
||||
//
|
||||
this.groupBox6.Controls.Add(this.browsePayloadArchiveButton);
|
||||
this.groupBox6.Controls.Add(this.payloadArchivePathTextBox);
|
||||
this.groupBox6.Controls.Add(this.payloadArchivePathLabel);
|
||||
this.groupBox6.Controls.Add(this.archivePayloadCheckBox);
|
||||
this.groupBox6.Controls.Add(this.payloadParameterNameTextBox);
|
||||
this.groupBox6.Controls.Add(this.payloadParameterNameLabel);
|
||||
this.groupBox6.Controls.Add(this.browsePayloadTemplateButton);
|
||||
this.groupBox6.Controls.Add(this.payloadTemplatePathTextBox);
|
||||
this.groupBox6.Controls.Add(this.payloadTemplatePathLabel);
|
||||
this.groupBox6.Location = new System.Drawing.Point(8, 258);
|
||||
this.groupBox6.Name = "groupBox6";
|
||||
this.groupBox6.Size = new System.Drawing.Size(1264, 130);
|
||||
this.groupBox6.TabIndex = 26;
|
||||
this.groupBox6.TabStop = false;
|
||||
this.groupBox6.Text = "Payload template setting";
|
||||
//
|
||||
// browsePayloadArchiveButton
|
||||
//
|
||||
this.browsePayloadArchiveButton.Enabled = false;
|
||||
this.browsePayloadArchiveButton.Location = new System.Drawing.Point(1130, 86);
|
||||
this.browsePayloadArchiveButton.Name = "browsePayloadArchiveButton";
|
||||
this.browsePayloadArchiveButton.Size = new System.Drawing.Size(110, 23);
|
||||
this.browsePayloadArchiveButton.TabIndex = 8;
|
||||
this.browsePayloadArchiveButton.Text = "Browse...";
|
||||
this.browsePayloadArchiveButton.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// payloadArchivePathTextBox
|
||||
//
|
||||
this.payloadArchivePathTextBox.Enabled = false;
|
||||
this.payloadArchivePathTextBox.Location = new System.Drawing.Point(120, 88);
|
||||
this.payloadArchivePathTextBox.Name = "payloadArchivePathTextBox";
|
||||
this.payloadArchivePathTextBox.Size = new System.Drawing.Size(1000, 20);
|
||||
this.payloadArchivePathTextBox.TabIndex = 7;
|
||||
//
|
||||
// payloadArchivePathLabel
|
||||
//
|
||||
this.payloadArchivePathLabel.AutoSize = true;
|
||||
this.payloadArchivePathLabel.Location = new System.Drawing.Point(9, 91);
|
||||
this.payloadArchivePathLabel.Name = "payloadArchivePathLabel";
|
||||
this.payloadArchivePathLabel.Size = new System.Drawing.Size(110, 13);
|
||||
this.payloadArchivePathLabel.TabIndex = 6;
|
||||
this.payloadArchivePathLabel.Text = "Payload archive path:";
|
||||
//
|
||||
// archivePayloadCheckBox
|
||||
//
|
||||
this.archivePayloadCheckBox.AutoSize = true;
|
||||
this.archivePayloadCheckBox.Enabled = false;
|
||||
this.archivePayloadCheckBox.Location = new System.Drawing.Point(450, 57);
|
||||
this.archivePayloadCheckBox.Name = "archivePayloadCheckBox";
|
||||
this.archivePayloadCheckBox.Size = new System.Drawing.Size(153, 17);
|
||||
this.archivePayloadCheckBox.TabIndex = 5;
|
||||
this.archivePayloadCheckBox.Text = "Archive generated payload";
|
||||
this.archivePayloadCheckBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// payloadParameterNameTextBox
|
||||
//
|
||||
this.payloadParameterNameTextBox.Enabled = false;
|
||||
this.payloadParameterNameTextBox.Location = new System.Drawing.Point(120, 55);
|
||||
this.payloadParameterNameTextBox.Name = "payloadParameterNameTextBox";
|
||||
this.payloadParameterNameTextBox.Size = new System.Drawing.Size(300, 20);
|
||||
this.payloadParameterNameTextBox.TabIndex = 4;
|
||||
//
|
||||
// payloadParameterNameLabel
|
||||
//
|
||||
this.payloadParameterNameLabel.AutoSize = true;
|
||||
this.payloadParameterNameLabel.Location = new System.Drawing.Point(9, 58);
|
||||
this.payloadParameterNameLabel.Name = "payloadParameterNameLabel";
|
||||
this.payloadParameterNameLabel.Size = new System.Drawing.Size(98, 13);
|
||||
this.payloadParameterNameLabel.TabIndex = 3;
|
||||
this.payloadParameterNameLabel.Text = "Payload parameter:";
|
||||
//
|
||||
// browsePayloadTemplateButton
|
||||
//
|
||||
this.browsePayloadTemplateButton.Enabled = false;
|
||||
this.browsePayloadTemplateButton.Location = new System.Drawing.Point(1130, 21);
|
||||
this.browsePayloadTemplateButton.Name = "browsePayloadTemplateButton";
|
||||
this.browsePayloadTemplateButton.Size = new System.Drawing.Size(110, 23);
|
||||
this.browsePayloadTemplateButton.TabIndex = 2;
|
||||
this.browsePayloadTemplateButton.Text = "Browse...";
|
||||
this.browsePayloadTemplateButton.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// payloadTemplatePathTextBox
|
||||
//
|
||||
this.payloadTemplatePathTextBox.Enabled = false;
|
||||
this.payloadTemplatePathTextBox.Location = new System.Drawing.Point(120, 23);
|
||||
this.payloadTemplatePathTextBox.Name = "payloadTemplatePathTextBox";
|
||||
this.payloadTemplatePathTextBox.Size = new System.Drawing.Size(1000, 20);
|
||||
this.payloadTemplatePathTextBox.TabIndex = 1;
|
||||
//
|
||||
// payloadTemplatePathLabel
|
||||
//
|
||||
this.payloadTemplatePathLabel.AutoSize = true;
|
||||
this.payloadTemplatePathLabel.Location = new System.Drawing.Point(9, 26);
|
||||
this.payloadTemplatePathLabel.Name = "payloadTemplatePathLabel";
|
||||
this.payloadTemplatePathLabel.Size = new System.Drawing.Size(91, 13);
|
||||
this.payloadTemplatePathLabel.TabIndex = 0;
|
||||
this.payloadTemplatePathLabel.Text = "Payload template:";
|
||||
//
|
||||
// WriterCfgCtrl
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.groupBox6);
|
||||
this.Controls.Add(this.technologyTypeComboBox);
|
||||
this.Controls.Add(this.technologyTypeLabel);
|
||||
this.Controls.Add(this.groupBox5);
|
||||
@@ -497,6 +612,8 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
this.groupBox4.PerformLayout();
|
||||
this.groupBox5.ResumeLayout(false);
|
||||
this.groupBox5.PerformLayout();
|
||||
this.groupBox6.ResumeLayout(false);
|
||||
this.groupBox6.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
@@ -547,5 +664,15 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
private System.Windows.Forms.Button buttonUpdateTemplate;
|
||||
private System.Windows.Forms.Button buttonRemoveTemplate;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.GroupBox groupBox6;
|
||||
private System.Windows.Forms.Label payloadTemplatePathLabel;
|
||||
private System.Windows.Forms.TextBox payloadTemplatePathTextBox;
|
||||
private System.Windows.Forms.Button browsePayloadTemplateButton;
|
||||
private System.Windows.Forms.Label payloadParameterNameLabel;
|
||||
private System.Windows.Forms.TextBox payloadParameterNameTextBox;
|
||||
private System.Windows.Forms.CheckBox archivePayloadCheckBox;
|
||||
private System.Windows.Forms.Label payloadArchivePathLabel;
|
||||
private System.Windows.Forms.TextBox payloadArchivePathTextBox;
|
||||
private System.Windows.Forms.Button browsePayloadArchiveButton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays one XML stored-procedure payload in a read-only tree and raw XML view.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This dialog is intended for diagnostic inspection of the exact XML value
|
||||
/// that will be supplied to a stored procedure parameter.
|
||||
/// </remarks>
|
||||
public partial class XmlPayloadViewerDlg : Form
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the stored-procedure parameter name.
|
||||
/// </summary>
|
||||
public string ParameterName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the XML payload displayed by the dialog.
|
||||
/// </summary>
|
||||
public string XmlPayload
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new XML payload viewer.
|
||||
/// </summary>
|
||||
public XmlPayloadViewerDlg()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void XmlPayloadViewerDlg_Load(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
Text =
|
||||
string.IsNullOrWhiteSpace(
|
||||
ParameterName)
|
||||
? "XML payload viewer"
|
||||
: "XML payload viewer - " +
|
||||
ParameterName;
|
||||
|
||||
parameterNameTextBox.Text =
|
||||
ParameterName ??
|
||||
string.Empty;
|
||||
|
||||
LoadPayload();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses and displays the configured XML payload.
|
||||
/// </summary>
|
||||
private void LoadPayload()
|
||||
{
|
||||
structureTreeView.Nodes.Clear();
|
||||
rawXmlTextBox.Clear();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
XmlPayload))
|
||||
{
|
||||
statusLabel.Text =
|
||||
"XML payload is empty.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
XDocument document =
|
||||
XDocument.Parse(
|
||||
XmlPayload,
|
||||
LoadOptions.PreserveWhitespace);
|
||||
|
||||
rawXmlTextBox.Text =
|
||||
document.ToString();
|
||||
|
||||
if (document.Root != null)
|
||||
{
|
||||
TreeNode rootNode =
|
||||
CreateElementNode(
|
||||
document.Root);
|
||||
|
||||
structureTreeView.Nodes.Add(
|
||||
rootNode);
|
||||
|
||||
rootNode.Expand();
|
||||
|
||||
ExpandInitialLevels(
|
||||
rootNode,
|
||||
2);
|
||||
}
|
||||
|
||||
statusLabel.Text =
|
||||
"Valid XML payload.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//
|
||||
// Preserve the exact supplied text even when it is invalid XML.
|
||||
//
|
||||
rawXmlTextBox.Text =
|
||||
XmlPayload;
|
||||
|
||||
statusLabel.Text =
|
||||
"Invalid XML: " +
|
||||
ex.Message;
|
||||
|
||||
tabControl.SelectedTab =
|
||||
rawXmlTabPage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a tree node for one XML element.
|
||||
/// </summary>
|
||||
private TreeNode CreateElementNode(
|
||||
XElement element)
|
||||
{
|
||||
TreeNode elementNode =
|
||||
new TreeNode(
|
||||
element.Name.LocalName);
|
||||
|
||||
foreach (XAttribute attribute
|
||||
in element.Attributes())
|
||||
{
|
||||
string attributeName =
|
||||
attribute.IsNamespaceDeclaration
|
||||
? "xmlns" +
|
||||
(attribute.Name.LocalName == "xmlns"
|
||||
? string.Empty
|
||||
: ":" + attribute.Name.LocalName)
|
||||
: "@" +
|
||||
attribute.Name.LocalName;
|
||||
|
||||
TreeNode attributeNode =
|
||||
new TreeNode(
|
||||
string.Format(
|
||||
"{0} = {1}",
|
||||
attributeName,
|
||||
FormatValue(
|
||||
attribute.Value)));
|
||||
|
||||
attributeNode.ToolTipText =
|
||||
attribute.Value ??
|
||||
string.Empty;
|
||||
|
||||
elementNode.Nodes.Add(
|
||||
attributeNode);
|
||||
}
|
||||
|
||||
foreach (XElement child
|
||||
in element.Elements())
|
||||
{
|
||||
elementNode.Nodes.Add(
|
||||
CreateElementNode(
|
||||
child));
|
||||
}
|
||||
|
||||
if (!element.Elements().Any())
|
||||
{
|
||||
string textValue =
|
||||
string.Concat(
|
||||
element.Nodes()
|
||||
.OfType<XText>()
|
||||
.Select(
|
||||
node =>
|
||||
node.Value));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(
|
||||
textValue))
|
||||
{
|
||||
TreeNode textNode =
|
||||
new TreeNode(
|
||||
"#text = " +
|
||||
FormatValue(
|
||||
textValue));
|
||||
|
||||
textNode.ToolTipText =
|
||||
textValue;
|
||||
|
||||
elementNode.Nodes.Add(
|
||||
textNode);
|
||||
}
|
||||
}
|
||||
|
||||
return elementNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a potentially long XML value for the tree.
|
||||
/// </summary>
|
||||
private string FormatValue(
|
||||
string value)
|
||||
{
|
||||
if (value == null)
|
||||
return string.Empty;
|
||||
|
||||
const int maxLength =
|
||||
160;
|
||||
|
||||
string singleLine =
|
||||
value
|
||||
.Replace(
|
||||
"\r",
|
||||
" ")
|
||||
.Replace(
|
||||
"\n",
|
||||
" ");
|
||||
|
||||
if (singleLine.Length <=
|
||||
maxLength)
|
||||
{
|
||||
return singleLine;
|
||||
}
|
||||
|
||||
return singleLine.Substring(
|
||||
0,
|
||||
maxLength) +
|
||||
"...";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands the first levels of the XML tree without expanding a large
|
||||
/// payload completely.
|
||||
/// </summary>
|
||||
private void ExpandInitialLevels(
|
||||
TreeNode node,
|
||||
int remainingLevels)
|
||||
{
|
||||
if (node == null ||
|
||||
remainingLevels < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
node.Expand();
|
||||
|
||||
if (remainingLevels == 0)
|
||||
return;
|
||||
|
||||
foreach (TreeNode child
|
||||
in node.Nodes)
|
||||
{
|
||||
if (child.Nodes.Count > 0)
|
||||
{
|
||||
ExpandInitialLevels(
|
||||
child,
|
||||
remainingLevels - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void expandAllButton_Click(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
structureTreeView.ExpandAll();
|
||||
}
|
||||
|
||||
private void collapseAllButton_Click(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
structureTreeView.CollapseAll();
|
||||
|
||||
if (structureTreeView.Nodes.Count > 0)
|
||||
{
|
||||
structureTreeView.Nodes[0]
|
||||
.Expand();
|
||||
}
|
||||
}
|
||||
|
||||
private void copyXmlButton_Click(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(
|
||||
rawXmlTextBox.Text))
|
||||
{
|
||||
Clipboard.SetText(
|
||||
rawXmlTextBox.Text);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeButton_Click(
|
||||
object sender,
|
||||
EventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
|
||||
{
|
||||
partial class XmlPayloadViewerDlg
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected override void Dispose(
|
||||
bool disposing)
|
||||
{
|
||||
if (disposing &&
|
||||
components != null)
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(
|
||||
disposing);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.parameterLabel = new System.Windows.Forms.Label();
|
||||
this.parameterNameTextBox = new System.Windows.Forms.TextBox();
|
||||
this.tabControl = new System.Windows.Forms.TabControl();
|
||||
this.structureTabPage = new System.Windows.Forms.TabPage();
|
||||
this.structureTreeView = new System.Windows.Forms.TreeView();
|
||||
this.rawXmlTabPage = new System.Windows.Forms.TabPage();
|
||||
this.rawXmlTextBox = new System.Windows.Forms.TextBox();
|
||||
this.statusLabel = new System.Windows.Forms.Label();
|
||||
this.expandAllButton = new System.Windows.Forms.Button();
|
||||
this.collapseAllButton = new System.Windows.Forms.Button();
|
||||
this.copyXmlButton = new System.Windows.Forms.Button();
|
||||
this.closeButton = new System.Windows.Forms.Button();
|
||||
this.tabControl.SuspendLayout();
|
||||
this.structureTabPage.SuspendLayout();
|
||||
this.rawXmlTabPage.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
|
||||
//
|
||||
// parameterLabel
|
||||
//
|
||||
this.parameterLabel.AutoSize = true;
|
||||
this.parameterLabel.Location = new System.Drawing.Point(12, 15);
|
||||
this.parameterLabel.Name = "parameterLabel";
|
||||
this.parameterLabel.Size = new System.Drawing.Size(61, 13);
|
||||
this.parameterLabel.TabIndex = 0;
|
||||
this.parameterLabel.Text = "Parameter:";
|
||||
|
||||
//
|
||||
// parameterNameTextBox
|
||||
//
|
||||
this.parameterNameTextBox.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
(((System.Windows.Forms.AnchorStyles.Top |
|
||||
System.Windows.Forms.AnchorStyles.Left) |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.parameterNameTextBox.Location = new System.Drawing.Point(79, 12);
|
||||
this.parameterNameTextBox.Name = "parameterNameTextBox";
|
||||
this.parameterNameTextBox.ReadOnly = true;
|
||||
this.parameterNameTextBox.Size = new System.Drawing.Size(809, 20);
|
||||
this.parameterNameTextBox.TabIndex = 1;
|
||||
|
||||
//
|
||||
// tabControl
|
||||
//
|
||||
this.tabControl.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((((System.Windows.Forms.AnchorStyles.Top |
|
||||
System.Windows.Forms.AnchorStyles.Bottom) |
|
||||
System.Windows.Forms.AnchorStyles.Left) |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tabControl.Controls.Add(this.structureTabPage);
|
||||
this.tabControl.Controls.Add(this.rawXmlTabPage);
|
||||
this.tabControl.Location = new System.Drawing.Point(12, 42);
|
||||
this.tabControl.Name = "tabControl";
|
||||
this.tabControl.SelectedIndex = 0;
|
||||
this.tabControl.Size = new System.Drawing.Size(876, 520);
|
||||
this.tabControl.TabIndex = 2;
|
||||
|
||||
//
|
||||
// structureTabPage
|
||||
//
|
||||
this.structureTabPage.Controls.Add(this.structureTreeView);
|
||||
this.structureTabPage.Location = new System.Drawing.Point(4, 22);
|
||||
this.structureTabPage.Name = "structureTabPage";
|
||||
this.structureTabPage.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.structureTabPage.Size = new System.Drawing.Size(868, 494);
|
||||
this.structureTabPage.TabIndex = 0;
|
||||
this.structureTabPage.Text = "Structure";
|
||||
this.structureTabPage.UseVisualStyleBackColor = true;
|
||||
|
||||
//
|
||||
// structureTreeView
|
||||
//
|
||||
this.structureTreeView.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.structureTreeView.FullRowSelect = true;
|
||||
this.structureTreeView.HideSelection = false;
|
||||
this.structureTreeView.Location = new System.Drawing.Point(3, 3);
|
||||
this.structureTreeView.Name = "structureTreeView";
|
||||
this.structureTreeView.ShowNodeToolTips = true;
|
||||
this.structureTreeView.Size = new System.Drawing.Size(862, 488);
|
||||
this.structureTreeView.TabIndex = 0;
|
||||
|
||||
//
|
||||
// rawXmlTabPage
|
||||
//
|
||||
this.rawXmlTabPage.Controls.Add(this.rawXmlTextBox);
|
||||
this.rawXmlTabPage.Location = new System.Drawing.Point(4, 22);
|
||||
this.rawXmlTabPage.Name = "rawXmlTabPage";
|
||||
this.rawXmlTabPage.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.rawXmlTabPage.Size = new System.Drawing.Size(868, 494);
|
||||
this.rawXmlTabPage.TabIndex = 1;
|
||||
this.rawXmlTabPage.Text = "Raw XML";
|
||||
this.rawXmlTabPage.UseVisualStyleBackColor = true;
|
||||
|
||||
//
|
||||
// rawXmlTextBox
|
||||
//
|
||||
this.rawXmlTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rawXmlTextBox.Font = new System.Drawing.Font(
|
||||
"Consolas",
|
||||
9F,
|
||||
System.Drawing.FontStyle.Regular,
|
||||
System.Drawing.GraphicsUnit.Point,
|
||||
((byte)(238)));
|
||||
this.rawXmlTextBox.Location = new System.Drawing.Point(3, 3);
|
||||
this.rawXmlTextBox.Multiline = true;
|
||||
this.rawXmlTextBox.Name = "rawXmlTextBox";
|
||||
this.rawXmlTextBox.ReadOnly = true;
|
||||
this.rawXmlTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.rawXmlTextBox.Size = new System.Drawing.Size(862, 488);
|
||||
this.rawXmlTextBox.TabIndex = 0;
|
||||
this.rawXmlTextBox.WordWrap = false;
|
||||
|
||||
//
|
||||
// statusLabel
|
||||
//
|
||||
this.statusLabel.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.statusLabel.AutoSize = true;
|
||||
this.statusLabel.Location = new System.Drawing.Point(12, 579);
|
||||
this.statusLabel.Name = "statusLabel";
|
||||
this.statusLabel.Size = new System.Drawing.Size(0, 13);
|
||||
this.statusLabel.TabIndex = 3;
|
||||
|
||||
//
|
||||
// expandAllButton
|
||||
//
|
||||
this.expandAllButton.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.expandAllButton.Location = new System.Drawing.Point(12, 608);
|
||||
this.expandAllButton.Name = "expandAllButton";
|
||||
this.expandAllButton.Size = new System.Drawing.Size(95, 30);
|
||||
this.expandAllButton.TabIndex = 4;
|
||||
this.expandAllButton.Text = "Expand all";
|
||||
this.expandAllButton.UseVisualStyleBackColor = true;
|
||||
this.expandAllButton.Click +=
|
||||
new System.EventHandler(this.expandAllButton_Click);
|
||||
|
||||
//
|
||||
// collapseAllButton
|
||||
//
|
||||
this.collapseAllButton.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.collapseAllButton.Location = new System.Drawing.Point(113, 608);
|
||||
this.collapseAllButton.Name = "collapseAllButton";
|
||||
this.collapseAllButton.Size = new System.Drawing.Size(95, 30);
|
||||
this.collapseAllButton.TabIndex = 5;
|
||||
this.collapseAllButton.Text = "Collapse all";
|
||||
this.collapseAllButton.UseVisualStyleBackColor = true;
|
||||
this.collapseAllButton.Click +=
|
||||
new System.EventHandler(this.collapseAllButton_Click);
|
||||
|
||||
//
|
||||
// copyXmlButton
|
||||
//
|
||||
this.copyXmlButton.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.copyXmlButton.Location = new System.Drawing.Point(682, 608);
|
||||
this.copyXmlButton.Name = "copyXmlButton";
|
||||
this.copyXmlButton.Size = new System.Drawing.Size(95, 30);
|
||||
this.copyXmlButton.TabIndex = 6;
|
||||
this.copyXmlButton.Text = "Copy XML";
|
||||
this.copyXmlButton.UseVisualStyleBackColor = true;
|
||||
this.copyXmlButton.Click +=
|
||||
new System.EventHandler(this.copyXmlButton_Click);
|
||||
|
||||
//
|
||||
// closeButton
|
||||
//
|
||||
this.closeButton.Anchor =
|
||||
((System.Windows.Forms.AnchorStyles)
|
||||
((System.Windows.Forms.AnchorStyles.Bottom |
|
||||
System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.closeButton.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
this.closeButton.Location = new System.Drawing.Point(793, 608);
|
||||
this.closeButton.Name = "closeButton";
|
||||
this.closeButton.Size = new System.Drawing.Size(95, 30);
|
||||
this.closeButton.TabIndex = 7;
|
||||
this.closeButton.Text = "Close";
|
||||
this.closeButton.UseVisualStyleBackColor = true;
|
||||
this.closeButton.Click +=
|
||||
new System.EventHandler(this.closeButton_Click);
|
||||
|
||||
//
|
||||
// XmlPayloadViewerDlg
|
||||
//
|
||||
this.AcceptButton = this.closeButton;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(900, 650);
|
||||
this.Controls.Add(this.closeButton);
|
||||
this.Controls.Add(this.copyXmlButton);
|
||||
this.Controls.Add(this.collapseAllButton);
|
||||
this.Controls.Add(this.expandAllButton);
|
||||
this.Controls.Add(this.statusLabel);
|
||||
this.Controls.Add(this.tabControl);
|
||||
this.Controls.Add(this.parameterNameTextBox);
|
||||
this.Controls.Add(this.parameterLabel);
|
||||
this.MinimumSize = new System.Drawing.Size(650, 450);
|
||||
this.Name = "XmlPayloadViewerDlg";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "XML payload viewer";
|
||||
this.Load +=
|
||||
new System.EventHandler(this.XmlPayloadViewerDlg_Load);
|
||||
this.tabControl.ResumeLayout(false);
|
||||
this.structureTabPage.ResumeLayout(false);
|
||||
this.rawXmlTabPage.ResumeLayout(false);
|
||||
this.rawXmlTabPage.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
}
|
||||
|
||||
private System.Windows.Forms.Label parameterLabel;
|
||||
private System.Windows.Forms.TextBox parameterNameTextBox;
|
||||
private System.Windows.Forms.TabControl tabControl;
|
||||
private System.Windows.Forms.TabPage structureTabPage;
|
||||
private System.Windows.Forms.TreeView structureTreeView;
|
||||
private System.Windows.Forms.TabPage rawXmlTabPage;
|
||||
private System.Windows.Forms.TextBox rawXmlTextBox;
|
||||
private System.Windows.Forms.Label statusLabel;
|
||||
private System.Windows.Forms.Button expandAllButton;
|
||||
private System.Windows.Forms.Button collapseAllButton;
|
||||
private System.Windows.Forms.Button copyXmlButton;
|
||||
private System.Windows.Forms.Button closeButton;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using FluentNHibernate.MappingModel.Output;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig.Generic;
|
||||
@@ -114,6 +115,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
case TechnologyTypes.Json:
|
||||
return new JsonWriter(cfg);
|
||||
|
||||
case TechnologyTypes.Xml:
|
||||
return new XmlFileWriter(cfg);
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
string.Format("Unsupported file technology type: '{0}'", cfg.TechnologyType));
|
||||
@@ -156,7 +160,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// throw new NotImplementedException();
|
||||
// throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void StartChangeHandler()
|
||||
@@ -166,7 +170,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
|
||||
public void StopChangeHandler()
|
||||
{
|
||||
// throw new NotImplementedException();
|
||||
// throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private WriterDiagnosticResult ValidateCapabilities(IDataStorageWriter writer, DataWriteRequest request)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Config.Entities;
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
using TBF.Resources;
|
||||
using TBF.Rig.Generic;
|
||||
@@ -14,86 +15,199 @@ using static TBF.Rig.Output.DataStorage.UniDataStorageWriter.Types;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
{
|
||||
///
|
||||
/// Class and file name is preserved for backward compatibility
|
||||
///
|
||||
/// <summary>
|
||||
/// Provides persistent configuration for the <see cref="Writer"/> component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The class name and file name are preserved for backward compatibility
|
||||
/// with existing TBF configurations.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Configuration is serializable through <see cref="XmlSerializer"/> and is
|
||||
/// also exposed through <see cref="IParamsProvider"/>. The two representations
|
||||
/// are intentionally kept symmetrical so that values survive component
|
||||
/// save/reload regardless of which persistence path is used by the host.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class WriterCfg : ComponentCfgBase, Generic.IComponentCfg, IParamsProvider
|
||||
{
|
||||
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(WriterCfg) })[0];
|
||||
public override XmlSerializer GetSerializer() { return Serializer; }
|
||||
|
||||
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities) { return new WriterCfgCtrl(); }
|
||||
/// <summary>
|
||||
/// Serializer used by the TBF configuration framework.
|
||||
/// </summary>
|
||||
public static XmlSerializer Serializer =
|
||||
XmlSerializer.FromTypes(new[] { typeof(WriterCfg) })[0];
|
||||
|
||||
/// <summary>
|
||||
/// Internal storage type identifier.
|
||||
/// Returns the serializer associated with this configuration type.
|
||||
/// </summary>
|
||||
public override XmlSerializer GetSerializer()
|
||||
{
|
||||
return Serializer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the configuration control used to edit this component.
|
||||
/// </summary>
|
||||
public IComponentCfgCtrl GetControl(
|
||||
IList<Config.Entities.Component> cmpntEntities)
|
||||
{
|
||||
return new WriterCfgCtrl();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Storage type identifier, for example local database, remote database
|
||||
/// or local file.
|
||||
/// </summary>
|
||||
public string DataStorageType;
|
||||
|
||||
/// <summary>
|
||||
/// Data source definition, e.g. file path, connection string, URL, etc.
|
||||
/// Data source definition. Its interpretation depends on the selected
|
||||
/// technology and can represent a connection string, file path or URL.
|
||||
/// </summary>
|
||||
public string DataSource;
|
||||
|
||||
/// <summary>
|
||||
/// Original field name preserved for backward compatibility.
|
||||
/// For writer semantics this represents the write template.
|
||||
/// Legacy single-template field preserved for backward compatibility.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// New configurations should use <see cref="WriteTemplates"/>. The field
|
||||
/// is kept synchronized with the currently selected <see cref="WriteMode"/>
|
||||
/// by the configuration control whenever possible.
|
||||
/// </remarks>
|
||||
public string QueryTemplate;
|
||||
|
||||
/// <summary>
|
||||
/// Technology used by the configured storage target.
|
||||
/// </summary>
|
||||
public string TechnologyType;
|
||||
|
||||
/// <summary>
|
||||
/// Default write operation used by the component.
|
||||
/// </summary>
|
||||
public WriteMode WriteMode;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of write-mode-specific templates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each entry uses the format <c>WriteMode|Template</c>, for example:
|
||||
/// <code>
|
||||
/// Insert|INSERT INTO dbo.Results ({0}) VALUES ({1})
|
||||
/// Update|UPDATE dbo.Results SET {2}={3} WHERE {0}={1}
|
||||
/// StoredProcedure|dbo.sp_InsertDashboardResults_FF
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public List<string> WriteTemplates;
|
||||
|
||||
/// <summary>
|
||||
/// Private parameterless constructor invoked by all other constructors.
|
||||
/// Path to an optional external XML payload template.
|
||||
/// </summary>
|
||||
WriterCfg()
|
||||
public string PayloadTemplatePath;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the stored procedure parameter receiving the generated payload.
|
||||
/// </summary>
|
||||
public string PayloadParameterName;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether generated payloads should also be archived to disk.
|
||||
/// </summary>
|
||||
public bool ArchivePayload;
|
||||
|
||||
/// <summary>
|
||||
/// Directory used for optional payload archiving.
|
||||
/// </summary>
|
||||
public string PayloadArchivePath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new configuration instance with safe defaults.
|
||||
/// </summary>
|
||||
private WriterCfg()
|
||||
{
|
||||
InitializeAll();
|
||||
}
|
||||
|
||||
public WriterCfg(string name, IComponentFactory factory)
|
||||
/// <summary>
|
||||
/// Initializes a new configuration instance.
|
||||
/// </summary>
|
||||
/// <param name="name">Component name.</param>
|
||||
/// <param name="factory">Factory owning the component.</param>
|
||||
public WriterCfg(
|
||||
string name,
|
||||
IComponentFactory factory)
|
||||
: this()
|
||||
{
|
||||
this.Name = name;
|
||||
this.Factory = factory;
|
||||
Name = name;
|
||||
Factory = factory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured component name.
|
||||
/// </summary>
|
||||
public string ComponentName
|
||||
{
|
||||
get { return Name; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes all UniDataStorageWriter-specific configuration fields.
|
||||
/// </summary>
|
||||
public void InitializeAll()
|
||||
{
|
||||
DataStorageType = string.Empty;
|
||||
DataSource = string.Empty;
|
||||
QueryTemplate = string.Empty;
|
||||
TechnologyType = string.Empty;
|
||||
|
||||
WriteMode = WriteMode.Insert;
|
||||
WriteTemplates = new List<string>();
|
||||
|
||||
PayloadTemplatePath = string.Empty;
|
||||
PayloadParameterName = string.Empty;
|
||||
ArchivePayload = false;
|
||||
PayloadArchivePath = string.Empty;
|
||||
}
|
||||
|
||||
private readonly string[] paramNames = new string[]
|
||||
private readonly string[] paramNames =
|
||||
{
|
||||
"Data Storage type",
|
||||
"Technology type",
|
||||
"Data source",
|
||||
"Query template",
|
||||
"Data Storage type", // 0
|
||||
"Technology type", // 1
|
||||
"Data source", // 2
|
||||
"Query template", // 3 - legacy/backward-compatible mirror
|
||||
"Write mode", // 4
|
||||
"Write templates", // 5
|
||||
"Payload template path", // 6
|
||||
"Payload parameter name", // 7
|
||||
"Archive payload", // 8
|
||||
"Payload archive path", // 9
|
||||
};
|
||||
|
||||
public string ParamName(int i) { return paramNames[i]; }
|
||||
/// <summary>
|
||||
/// Returns the display name of one exposed configuration parameter.
|
||||
/// </summary>
|
||||
public string ParamName(int i)
|
||||
{
|
||||
return paramNames[i];
|
||||
}
|
||||
|
||||
public int ParamsCount() { return paramNames.Length; }
|
||||
/// <summary>
|
||||
/// Returns the number of parameters exposed through <see cref="IParamsProvider"/>.
|
||||
/// </summary>
|
||||
public int ParamsCount()
|
||||
{
|
||||
return paramNames.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns predefined values for parameters that use a fixed set of options.
|
||||
/// </summary>
|
||||
public ICollection<string> ParamValues(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
return new string[]
|
||||
return new[]
|
||||
{
|
||||
StorageTypes.RestApi,
|
||||
StorageTypes.LocalDatabase,
|
||||
@@ -107,55 +221,182 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
{
|
||||
case StorageTypes.LocalDatabase:
|
||||
case StorageTypes.RemoteDatabase:
|
||||
return new string[]
|
||||
return new[]
|
||||
{
|
||||
TechnologyTypes.MicrosoftSql,
|
||||
TechnologyTypes.MySqlMariaDb,
|
||||
TechnologyTypes.SQLite,
|
||||
TechnologyTypes.MicrosoftSql,
|
||||
TechnologyTypes.MySqlMariaDb,
|
||||
TechnologyTypes.SQLite,
|
||||
};
|
||||
|
||||
case StorageTypes.LocalFile:
|
||||
case StorageTypes.RemoteFile:
|
||||
return new string[]
|
||||
return new[]
|
||||
{
|
||||
TechnologyTypes.Csv,
|
||||
TechnologyTypes.Xls,
|
||||
TechnologyTypes.Json,
|
||||
TechnologyTypes.Csv,
|
||||
TechnologyTypes.Xls,
|
||||
TechnologyTypes.Xlsx,
|
||||
TechnologyTypes.Json,
|
||||
TechnologyTypes.Xml,
|
||||
};
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
return new[]
|
||||
{
|
||||
WriteMode.Insert.ToString(),
|
||||
WriteMode.Update.ToString(),
|
||||
WriteMode.StoredProcedure.ToString(),
|
||||
};
|
||||
|
||||
case 8:
|
||||
return new[]
|
||||
{
|
||||
bool.FalseString,
|
||||
bool.TrueString,
|
||||
};
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns either one parameter value or a configuration summary.
|
||||
/// </summary>
|
||||
/// <param name="i">
|
||||
/// Parameter index. A negative value returns a human-readable summary.
|
||||
/// </param>
|
||||
public string ToString(int i)
|
||||
{
|
||||
return string.Format(
|
||||
"Name={0}, DataStorageType={1}, TechnologyType={2}",
|
||||
Name,
|
||||
DataStorageType,
|
||||
TechnologyType);
|
||||
}
|
||||
|
||||
public CfgUpdateFlags UpdateParam(int i, string strValue)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: DataStorageType = strValue; return CfgUpdateFlags.RestartRqrd;
|
||||
case 1: TechnologyType = strValue; return CfgUpdateFlags.RestartRqrd;
|
||||
case 2: DataSource = strValue; return CfgUpdateFlags.RestartRqrd;
|
||||
case 3: QueryTemplate = strValue; return CfgUpdateFlags.RestartRqrd;
|
||||
default: return CfgUpdateFlags.None;
|
||||
case 0:
|
||||
return DataStorageType ?? string.Empty;
|
||||
|
||||
case 1:
|
||||
return TechnologyType ?? string.Empty;
|
||||
|
||||
case 2:
|
||||
return DataSource ?? string.Empty;
|
||||
|
||||
case 3:
|
||||
return QueryTemplate ?? string.Empty;
|
||||
|
||||
case 4:
|
||||
return WriteMode.ToString();
|
||||
|
||||
case 5:
|
||||
return SerializeWriteTemplates();
|
||||
|
||||
case 6:
|
||||
return PayloadTemplatePath ?? string.Empty;
|
||||
|
||||
case 7:
|
||||
return PayloadParameterName ?? string.Empty;
|
||||
|
||||
case 8:
|
||||
return ArchivePayload.ToString();
|
||||
|
||||
case 9:
|
||||
return PayloadArchivePath ?? string.Empty;
|
||||
|
||||
default:
|
||||
return string.Format(
|
||||
"Name={0}, DataStorageType={1}, TechnologyType={2}, WriteMode={3}",
|
||||
Name,
|
||||
DataStorageType,
|
||||
TechnologyType,
|
||||
WriteMode);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ValidateParam(int i, string strValue, out string message)
|
||||
/// <summary>
|
||||
/// Updates one parameter exposed through <see cref="IParamsProvider"/>.
|
||||
/// </summary>
|
||||
public CfgUpdateFlags UpdateParam(
|
||||
int i,
|
||||
string strValue)
|
||||
{
|
||||
strValue = strValue ?? string.Empty;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
DataStorageType = strValue;
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
case 1:
|
||||
TechnologyType = strValue;
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
case 2:
|
||||
DataSource = strValue;
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
case 3:
|
||||
QueryTemplate = strValue;
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
case 4:
|
||||
{
|
||||
WriteMode parsedMode;
|
||||
|
||||
if (Enum.TryParse(
|
||||
strValue,
|
||||
true,
|
||||
out parsedMode))
|
||||
{
|
||||
WriteMode = parsedMode;
|
||||
}
|
||||
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
}
|
||||
|
||||
case 5:
|
||||
DeserializeWriteTemplates(strValue);
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
case 6:
|
||||
PayloadTemplatePath = strValue;
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
case 7:
|
||||
PayloadParameterName = strValue;
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
case 8:
|
||||
{
|
||||
bool parsedValue;
|
||||
|
||||
if (bool.TryParse(
|
||||
strValue,
|
||||
out parsedValue))
|
||||
{
|
||||
ArchivePayload = parsedValue;
|
||||
}
|
||||
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
}
|
||||
|
||||
case 9:
|
||||
PayloadArchivePath = strValue;
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
|
||||
default:
|
||||
return CfgUpdateFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates one parameter exposed through <see cref="IParamsProvider"/>.
|
||||
/// </summary>
|
||||
public bool ValidateParam(
|
||||
int i,
|
||||
string strValue,
|
||||
out string message)
|
||||
{
|
||||
message = string.Empty;
|
||||
strValue = strValue ?? string.Empty;
|
||||
@@ -171,8 +412,10 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
return true;
|
||||
|
||||
case 1:
|
||||
if ((DataStorageType == StorageTypes.LocalDatabase || DataStorageType == StorageTypes.RemoteDatabase ||
|
||||
DataStorageType == StorageTypes.LocalFile || DataStorageType == StorageTypes.RemoteFile) &&
|
||||
if ((DataStorageType == StorageTypes.LocalDatabase ||
|
||||
DataStorageType == StorageTypes.RemoteDatabase ||
|
||||
DataStorageType == StorageTypes.LocalFile ||
|
||||
DataStorageType == StorageTypes.RemoteFile) &&
|
||||
string.IsNullOrWhiteSpace(strValue))
|
||||
{
|
||||
message = "Technology type must be selected.";
|
||||
@@ -189,11 +432,80 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
return true;
|
||||
|
||||
case 3:
|
||||
if (string.IsNullOrWhiteSpace(strValue))
|
||||
// Legacy mirror only. New configurations use WriteTemplates.
|
||||
return true;
|
||||
|
||||
case 4:
|
||||
{
|
||||
message = "Query template must not be empty.";
|
||||
WriteMode parsedMode;
|
||||
|
||||
if (!Enum.TryParse(
|
||||
strValue,
|
||||
true,
|
||||
out parsedMode))
|
||||
{
|
||||
message = "Invalid write mode.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case 5:
|
||||
// An empty template list is valid for technologies that do
|
||||
// not use templates. Technology-specific validation is done
|
||||
// by the writer/configuration UI.
|
||||
return true;
|
||||
|
||||
case 6:
|
||||
if (UsesXmlPayload() &&
|
||||
string.IsNullOrWhiteSpace(strValue))
|
||||
{
|
||||
message =
|
||||
"Payload reference XML path must not be empty for XML payload output.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case 7:
|
||||
if (IsStoredProcedurePayloadTarget() &&
|
||||
string.IsNullOrWhiteSpace(strValue))
|
||||
{
|
||||
message =
|
||||
"Payload parameter name must not be empty for stored procedure XML output.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case 8:
|
||||
{
|
||||
bool parsedValue;
|
||||
|
||||
if (!bool.TryParse(
|
||||
strValue,
|
||||
out parsedValue))
|
||||
{
|
||||
message = "Archive payload must be True or False.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case 9:
|
||||
if (ArchivePayload &&
|
||||
string.IsNullOrWhiteSpace(strValue))
|
||||
{
|
||||
message =
|
||||
"Payload archive path must not be empty when payload archiving is enabled.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -202,48 +514,140 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyContentTo(WriterCfg prms)
|
||||
/// <summary>
|
||||
/// Returns whether the configured target is an XML payload file.
|
||||
/// </summary>
|
||||
public bool IsXmlFileTarget()
|
||||
{
|
||||
prms.DataStorageType = this.DataStorageType;
|
||||
prms.TechnologyType = this.TechnologyType;
|
||||
prms.DataSource = this.DataSource;
|
||||
prms.QueryTemplate = this.QueryTemplate;
|
||||
prms.WriteMode = this.WriteMode;
|
||||
string storageType =
|
||||
(DataStorageType ?? string.Empty).Trim();
|
||||
|
||||
prms.WriteTemplates = new List<string>();
|
||||
string technologyType =
|
||||
(TechnologyType ?? string.Empty).Trim();
|
||||
|
||||
if (this.WriteTemplates != null)
|
||||
{
|
||||
foreach (string item in this.WriteTemplates)
|
||||
{
|
||||
prms.WriteTemplates.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IParamsProvider Clone()
|
||||
{
|
||||
WriterCfg pars = new WriterCfg();
|
||||
CopyContentTo(pars);
|
||||
return pars;
|
||||
return
|
||||
(storageType == StorageTypes.LocalFile ||
|
||||
storageType == StorageTypes.RemoteFile) &&
|
||||
technologyType == TechnologyTypes.Xml;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strongly typed helper for internal use.
|
||||
/// Returns whether the configured target is a Microsoft SQL stored
|
||||
/// procedure receiving a generated XML payload.
|
||||
/// </summary>
|
||||
public WriterCfg ShallowCopy()
|
||||
public bool IsStoredProcedurePayloadTarget()
|
||||
{
|
||||
WriterCfg copy = new WriterCfg(this.Name, this.Factory);
|
||||
string storageType =
|
||||
(DataStorageType ?? string.Empty).Trim();
|
||||
|
||||
string technologyType =
|
||||
(TechnologyType ?? string.Empty).Trim();
|
||||
|
||||
return
|
||||
(storageType == StorageTypes.LocalDatabase ||
|
||||
storageType == StorageTypes.RemoteDatabase) &&
|
||||
technologyType == TechnologyTypes.MicrosoftSql &&
|
||||
WriteMode == WriteMode.StoredProcedure;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the configuration requires ResultsWriter XML payload
|
||||
/// generation.
|
||||
/// </summary>
|
||||
public bool UsesXmlPayload()
|
||||
{
|
||||
return
|
||||
IsXmlFileTarget() ||
|
||||
IsStoredProcedurePayloadTarget();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all UniDataStorageWriter-specific fields to another instance.
|
||||
/// </summary>
|
||||
private void CopyContentTo(
|
||||
WriterCfg target)
|
||||
{
|
||||
target.DataStorageType = DataStorageType;
|
||||
target.TechnologyType = TechnologyType;
|
||||
target.DataSource = DataSource;
|
||||
target.QueryTemplate = QueryTemplate;
|
||||
target.WriteMode = WriteMode;
|
||||
|
||||
target.WriteTemplates =
|
||||
WriteTemplates != null
|
||||
? new List<string>(WriteTemplates)
|
||||
: new List<string>();
|
||||
|
||||
target.PayloadTemplatePath = PayloadTemplatePath;
|
||||
target.PayloadParameterName = PayloadParameterName;
|
||||
target.ArchivePayload = ArchivePayload;
|
||||
target.PayloadArchivePath = PayloadArchivePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy exposed through <see cref="IParamsProvider"/>.
|
||||
/// </summary>
|
||||
public IParamsProvider Clone()
|
||||
{
|
||||
WriterCfg copy = new WriterCfg();
|
||||
CopyContentTo(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public bool UpdateEmbeddedDbEntity()
|
||||
/// <summary>
|
||||
/// Creates a strongly typed shallow copy of the configuration.
|
||||
/// </summary>
|
||||
public WriterCfg ShallowCopy()
|
||||
{
|
||||
return true; /// =OK, do nothing
|
||||
WriterCfg copy =
|
||||
new WriterCfg(Name, Factory);
|
||||
|
||||
CopyContentTo(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static bool TryParseTemplateItem(string item, out WriteMode mode, out string template)
|
||||
/// <summary>
|
||||
/// Indicates that no additional embedded database update is required.
|
||||
/// </summary>
|
||||
public bool UpdateEmbeddedDbEntity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the template associated with the requested write mode.
|
||||
/// </summary>
|
||||
public string GetTemplate(
|
||||
WriteMode mode)
|
||||
{
|
||||
if (WriteTemplates != null)
|
||||
{
|
||||
foreach (string item in WriteTemplates)
|
||||
{
|
||||
WriteMode parsedMode;
|
||||
string template;
|
||||
|
||||
if (TryParseTemplateItem(
|
||||
item,
|
||||
out parsedMode,
|
||||
out template) &&
|
||||
parsedMode == mode)
|
||||
{
|
||||
return template;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QueryTemplate ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a <c>WriteMode|Template</c> configuration entry.
|
||||
/// </summary>
|
||||
private static bool TryParseTemplateItem(
|
||||
string item,
|
||||
out WriteMode mode,
|
||||
out string template)
|
||||
{
|
||||
mode = WriteMode.Insert;
|
||||
template = string.Empty;
|
||||
@@ -252,35 +656,72 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
|
||||
return false;
|
||||
|
||||
int separatorIndex = item.IndexOf('|');
|
||||
|
||||
if (separatorIndex <= 0)
|
||||
return false;
|
||||
|
||||
string modeText = item.Substring(0, separatorIndex).Trim();
|
||||
template = item.Substring(separatorIndex + 1).Trim();
|
||||
string modeText =
|
||||
item.Substring(0, separatorIndex).Trim();
|
||||
|
||||
if (!Enum.TryParse(modeText, true, out mode))
|
||||
return false;
|
||||
template =
|
||||
item.Substring(separatorIndex + 1).Trim();
|
||||
|
||||
return true;
|
||||
return Enum.TryParse(
|
||||
modeText,
|
||||
true,
|
||||
out mode);
|
||||
}
|
||||
|
||||
public string GetTemplate(WriteMode mode)
|
||||
/// <summary>
|
||||
/// Serializes the write-template collection into one parameter string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The UI stores one template per line, therefore newline is used as
|
||||
/// the parameter-level separator. This representation is intended for
|
||||
/// <see cref="IParamsProvider"/> compatibility; XML serialization still
|
||||
/// persists <see cref="WriteTemplates"/> as a normal list.
|
||||
/// </remarks>
|
||||
private string SerializeWriteTemplates()
|
||||
{
|
||||
if (WriteTemplates == null)
|
||||
return string.Empty;
|
||||
|
||||
foreach (string item in WriteTemplates)
|
||||
if (WriteTemplates == null ||
|
||||
WriteTemplates.Count == 0)
|
||||
{
|
||||
WriteMode m;
|
||||
string t;
|
||||
|
||||
if (TryParseTemplateItem(item, out m, out t) && m == mode)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return QueryTemplate ?? string.Empty; // fallback
|
||||
return string.Join(
|
||||
"\n",
|
||||
WriteTemplates.Where(
|
||||
item => item != null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the write-template collection from one parameter string.
|
||||
/// </summary>
|
||||
private void DeserializeWriteTemplates(
|
||||
string value)
|
||||
{
|
||||
WriteTemplates = new List<string>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return;
|
||||
|
||||
string normalized =
|
||||
value.Replace("\r\n", "\n")
|
||||
.Replace('\r', '\n');
|
||||
|
||||
string[] items =
|
||||
normalized.Split(
|
||||
new[] { '\n' },
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (string item in items)
|
||||
{
|
||||
string trimmed = item.Trim();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(trimmed))
|
||||
WriteTemplates.Add(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,68 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Diagnostic;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI;
|
||||
using static TBF.Rig.Output.DataStorage.UniDataStorageWriter.Types;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
|
||||
{
|
||||
/// <summary>
|
||||
/// Database writer implementation for Microsoft SQL.
|
||||
/// Uses full SQL template defined in cfg.QueryTemplate.
|
||||
/// Provides data writing support for Microsoft SQL databases.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The writer supports multiple write modes and resolves the corresponding
|
||||
/// SQL command or stored procedure name from <see cref="WriterCfg"/>.
|
||||
///
|
||||
/// Currently supported operations are:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <see cref="WriteMode.Insert"/> - executes an INSERT statement generated
|
||||
/// from the configured template.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <see cref="WriteMode.Update"/> - executes one or more UPDATE statements
|
||||
/// generated from the configured template.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <see cref="WriteMode.StoredProcedure"/> - executes a configured stored
|
||||
/// procedure using strongly typed parameters supplied by the write request.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// Database connection information is taken from
|
||||
/// <see cref="WriterCfg.DataSource"/>.
|
||||
/// </remarks>
|
||||
public class DatabaseWriter : IDataStorageWriter
|
||||
{
|
||||
private readonly WriterCfg cfg;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DatabaseWriter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="cfg">
|
||||
/// Writer configuration containing the database connection string and
|
||||
/// write templates.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown when <paramref name="cfg"/> is <c>null</c>.
|
||||
/// </exception>
|
||||
public DatabaseWriter(WriterCfg cfg)
|
||||
{
|
||||
this.cfg = cfg ?? throw new ArgumentNullException(nameof(cfg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the storage, technology and write modes supported by this writer.
|
||||
/// </summary>
|
||||
public WriterCapabilities Capabilities
|
||||
{
|
||||
get
|
||||
@@ -35,11 +76,23 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
|
||||
|
||||
caps.SupportedWriteModes.Add(WriteMode.Insert);
|
||||
caps.SupportedWriteModes.Add(WriteMode.Update);
|
||||
caps.SupportedWriteModes.Add(WriteMode.StoredProcedure);
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the configured Microsoft SQL data source is accessible.
|
||||
/// </summary>
|
||||
/// <param name="validateOnly">
|
||||
/// When <c>true</c>, only the connection is opened and validated.
|
||||
/// When <c>false</c>, an additional lightweight <c>SELECT 1</c> command
|
||||
/// is executed.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Diagnostic information describing whether the connection test succeeded.
|
||||
/// </returns>
|
||||
public WriterDiagnosticResult TestSource(bool validateOnly)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cfg.DataSource))
|
||||
@@ -68,6 +121,20 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a data write operation according to the mode specified
|
||||
/// in the supplied request.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// Write request containing the write mode and data required by the
|
||||
/// selected operation.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Diagnostic result describing the outcome of the operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown when <paramref name="request"/> is <c>null</c>.
|
||||
/// </exception>
|
||||
public WriterDiagnosticResult WriteData(DataWriteRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
@@ -81,95 +148,266 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
|
||||
case WriteMode.Update:
|
||||
return ExecuteUpdate(request);
|
||||
|
||||
case WriteMode.StoredProcedure:
|
||||
return ExecuteStoredProcedure(request);
|
||||
|
||||
default:
|
||||
return Fail("Mode not supported: " + request.Mode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an INSERT operation using the configured insert template.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// Request containing the column/value pairs to be inserted.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Diagnostic result describing the INSERT operation.
|
||||
/// </returns>
|
||||
private WriterDiagnosticResult ExecuteInsert(DataWriteRequest request)
|
||||
{
|
||||
// Validate input
|
||||
if (request.InsertItems == null || request.InsertItems.Count == 0)
|
||||
return Fail("No insert items provided.");
|
||||
|
||||
// Resolve template for current write mode
|
||||
string template = cfg.GetTemplate(request.Mode);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
return Fail("Insert template is empty.");
|
||||
|
||||
// Build comma-separated list of column names
|
||||
string columns = string.Join(", ",
|
||||
request.InsertItems.Select(i => i.ColumnName));
|
||||
|
||||
// Build comma-separated list of SQL-formatted values
|
||||
string values = string.Join(", ",
|
||||
request.InsertItems.Select(i => ToSqlLiteral(i.Value)));
|
||||
|
||||
// Replace template placeholders:
|
||||
// {0} -> column list
|
||||
// {1} -> value list
|
||||
string sql = template
|
||||
.Replace("{0}", columns)
|
||||
.Replace("{1}", values);
|
||||
|
||||
// Execute final SQL command
|
||||
return ExecuteSql(sql, "Insert OK.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes one or more UPDATE operations using the configured update template.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// Request containing update conditions and values.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Diagnostic result containing the total affected row count and executed SQL.
|
||||
/// </returns>
|
||||
private WriterDiagnosticResult ExecuteUpdate(DataWriteRequest request)
|
||||
{
|
||||
// Validate input: at least one update item must be provided
|
||||
if (request.UpdateItems == null || request.UpdateItems.Count == 0)
|
||||
return Fail("No update items provided.");
|
||||
|
||||
// Resolve template for current write mode
|
||||
string template = cfg.GetTemplate(request.Mode);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
return Fail("Update template is empty.");
|
||||
|
||||
int totalRows = 0;
|
||||
|
||||
// Collect all executed SQL statements for diagnostics
|
||||
StringBuilder executedSql = new StringBuilder();
|
||||
|
||||
// Open database connection
|
||||
using (SqlConnection connection = new SqlConnection(cfg.DataSource))
|
||||
try
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
// Process each update item separately
|
||||
foreach (UpdateWriteItem item in request.UpdateItems)
|
||||
using (SqlConnection connection = new SqlConnection(cfg.DataSource))
|
||||
{
|
||||
string sql = template;
|
||||
connection.Open();
|
||||
|
||||
// Replace placeholders:
|
||||
// {0} -> WHERE column name
|
||||
// {1} -> WHERE value
|
||||
// {2} -> SET column name
|
||||
// {3} -> SET value
|
||||
sql = sql.Replace("{0}", item.WhereParameterName);
|
||||
sql = sql.Replace("{1}", ToSqlLiteral(item.WhereValue));
|
||||
sql = sql.Replace("{2}", item.SetParameterName);
|
||||
sql = sql.Replace("{3}", ToSqlLiteral(item.SetValue));
|
||||
|
||||
using (SqlCommand command = new SqlCommand(sql, connection))
|
||||
foreach (UpdateWriteItem item in request.UpdateItems)
|
||||
{
|
||||
totalRows += command.ExecuteNonQuery();
|
||||
string sql = template;
|
||||
|
||||
sql = sql.Replace("{0}", item.WhereParameterName);
|
||||
sql = sql.Replace("{1}", ToSqlLiteral(item.WhereValue));
|
||||
sql = sql.Replace("{2}", item.SetParameterName);
|
||||
sql = sql.Replace("{3}", ToSqlLiteral(item.SetValue));
|
||||
|
||||
using (SqlCommand command = new SqlCommand(sql, connection))
|
||||
{
|
||||
totalRows += command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
executedSql.AppendLine(sql);
|
||||
}
|
||||
|
||||
executedSql.AppendLine(sql);
|
||||
}
|
||||
}
|
||||
|
||||
return new WriterDiagnosticResult
|
||||
return new WriterDiagnosticResult
|
||||
{
|
||||
Success = true,
|
||||
Message = "Update OK. Rows: " + totalRows,
|
||||
ExecutedTemplate = executedSql.ToString().TrimEnd()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Success = true,
|
||||
Message = "Update OK. Rows: " + totalRows,
|
||||
ExecutedTemplate = executedSql.ToString().TrimEnd()
|
||||
};
|
||||
return Fail("Database update failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private WriterDiagnosticResult ExecuteSql(string sql, string successMessage)
|
||||
/// <summary>
|
||||
/// Executes a configured Microsoft SQL stored procedure.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// Request containing the parameters passed to the stored procedure.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Diagnostic result describing whether the stored procedure completed
|
||||
/// successfully.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The stored procedure name is resolved from
|
||||
/// <see cref="WriterCfg.GetTemplate(WriteMode)"/> using
|
||||
/// <see cref="WriteMode.StoredProcedure"/>.
|
||||
///
|
||||
/// Each item from
|
||||
/// <see cref="DataWriteRequest.StoredProcedureParameters"/>
|
||||
/// is converted to a strongly typed <see cref="SqlParameter"/>.
|
||||
///
|
||||
/// This mechanism allows XML, strings, numbers, Boolean values and
|
||||
/// date/time values to be passed without manually concatenating SQL.
|
||||
///
|
||||
/// For example, the dashboard integration can execute:
|
||||
/// <c>dbo.sp_InsertDashboardResults_FF</c>
|
||||
/// with an XML parameter named <c>@DashboardResults</c>.
|
||||
/// </remarks>
|
||||
private WriterDiagnosticResult ExecuteStoredProcedure(DataWriteRequest request)
|
||||
{
|
||||
if (request.StoredProcedureParameters == null)
|
||||
return Fail("Stored procedure parameter collection is not initialized.");
|
||||
|
||||
string procedureName = cfg.GetTemplate(WriteMode.StoredProcedure);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(procedureName))
|
||||
return Fail("Stored procedure name is empty.");
|
||||
|
||||
try
|
||||
{
|
||||
using (SqlConnection connection = new SqlConnection(cfg.DataSource))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
using (SqlCommand command = new SqlCommand(procedureName, connection))
|
||||
{
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
foreach (StoredProcedureWriteParameter parameter
|
||||
in request.StoredProcedureParameters)
|
||||
{
|
||||
SqlParameter sqlParameter = CreateSqlParameter(parameter);
|
||||
command.Parameters.Add(sqlParameter);
|
||||
}
|
||||
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
return new WriterDiagnosticResult
|
||||
{
|
||||
Success = true,
|
||||
Message = "Stored procedure executed successfully.",
|
||||
ExecutedTemplate = procedureName
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Fail("Stored procedure execution failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a strongly typed SQL Server parameter from a generic
|
||||
/// stored procedure parameter definition.
|
||||
/// </summary>
|
||||
/// <param name="parameter">
|
||||
/// Parameter definition supplied by the caller.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A configured <see cref="SqlParameter"/> instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown when <paramref name="parameter"/> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the parameter name is empty.
|
||||
/// </exception>
|
||||
/// <exception cref="NotSupportedException">
|
||||
/// Thrown when the requested parameter type is not supported.
|
||||
/// </exception>
|
||||
private SqlParameter CreateSqlParameter(
|
||||
StoredProcedureWriteParameter parameter)
|
||||
{
|
||||
if (parameter == null)
|
||||
throw new ArgumentNullException(nameof(parameter));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(parameter.ParameterName))
|
||||
throw new ArgumentException(
|
||||
"Stored procedure parameter name must not be empty.",
|
||||
nameof(parameter));
|
||||
|
||||
SqlDbType sqlDbType;
|
||||
|
||||
switch (parameter.ParameterType)
|
||||
{
|
||||
case StoredProcedureParameterType.String:
|
||||
sqlDbType = SqlDbType.NVarChar;
|
||||
break;
|
||||
|
||||
case StoredProcedureParameterType.Xml:
|
||||
sqlDbType = SqlDbType.Xml;
|
||||
break;
|
||||
|
||||
case StoredProcedureParameterType.Int32:
|
||||
sqlDbType = SqlDbType.Int;
|
||||
break;
|
||||
|
||||
case StoredProcedureParameterType.Int64:
|
||||
sqlDbType = SqlDbType.BigInt;
|
||||
break;
|
||||
|
||||
case StoredProcedureParameterType.Decimal:
|
||||
sqlDbType = SqlDbType.Decimal;
|
||||
break;
|
||||
|
||||
case StoredProcedureParameterType.Boolean:
|
||||
sqlDbType = SqlDbType.Bit;
|
||||
break;
|
||||
|
||||
case StoredProcedureParameterType.DateTime:
|
||||
sqlDbType = SqlDbType.DateTime;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"Stored procedure parameter type is not supported: "
|
||||
+ parameter.ParameterType);
|
||||
}
|
||||
|
||||
SqlParameter sqlParameter =
|
||||
new SqlParameter(parameter.ParameterName, sqlDbType);
|
||||
|
||||
sqlParameter.Value = parameter.Value ?? DBNull.Value;
|
||||
|
||||
return sqlParameter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a raw SQL statement against the configured database.
|
||||
/// </summary>
|
||||
/// <param name="sql">
|
||||
/// SQL statement to execute.
|
||||
/// </param>
|
||||
/// <param name="successMessage">
|
||||
/// Message included in the successful diagnostic result.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Diagnostic result containing execution status and affected row count.
|
||||
/// </returns>
|
||||
private WriterDiagnosticResult ExecuteSql(
|
||||
string sql,
|
||||
string successMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -196,6 +434,24 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a string value to a SQL string literal.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// Value to convert.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A SQL-compatible quoted literal, or <c>NULL</c> when the input
|
||||
/// value is <c>null</c>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Single quotes are escaped by duplication.
|
||||
///
|
||||
/// This method is retained for compatibility with the existing
|
||||
/// template-based INSERT and UPDATE implementation. Stored procedure
|
||||
/// parameters do not use this method and are passed as strongly typed
|
||||
/// SQL parameters instead.
|
||||
/// </remarks>
|
||||
private string ToSqlLiteral(string value)
|
||||
{
|
||||
if (value == null)
|
||||
@@ -204,6 +460,15 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
|
||||
return "'" + value.Replace("'", "''") + "'";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful writer diagnostic result.
|
||||
/// </summary>
|
||||
/// <param name="message">
|
||||
/// Human-readable diagnostic message.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Successful diagnostic result.
|
||||
/// </returns>
|
||||
private WriterDiagnosticResult Ok(string message)
|
||||
{
|
||||
return new WriterDiagnosticResult
|
||||
@@ -213,6 +478,15 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed writer diagnostic result.
|
||||
/// </summary>
|
||||
/// <param name="message">
|
||||
/// Human-readable error description.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Failed diagnostic result.
|
||||
/// </returns>
|
||||
private WriterDiagnosticResult Fail(string message)
|
||||
{
|
||||
return new WriterDiagnosticResult
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
///
|
||||
/// Copyright (c) 2026 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Diagnostic;
|
||||
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
|
||||
using static TBF.Rig.Output.DataStorage.UniDataStorageWriter.Types;
|
||||
|
||||
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a complete generated XML payload to a file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The configured <see cref="WriterCfg.DataSource"/> is interpreted as the
|
||||
/// output directory. One file is created for every write request.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The XML payload itself is expected in
|
||||
/// <see cref="DataWriteRequest.Payload"/>. This allows higher-level
|
||||
/// components such as ResultsWriter to generate the complete document from
|
||||
/// an XML reference structure and then use UniDataStorageWriter only as the
|
||||
/// physical output target.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class XmlFileWriter : IDataStorageWriter
|
||||
{
|
||||
private readonly WriterCfg cfg;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new XML file writer.
|
||||
/// </summary>
|
||||
public XmlFileWriter(
|
||||
WriterCfg cfg)
|
||||
{
|
||||
this.cfg =
|
||||
cfg ??
|
||||
throw new ArgumentNullException(
|
||||
nameof(cfg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets capabilities supported by this writer.
|
||||
/// </summary>
|
||||
public WriterCapabilities Capabilities
|
||||
{
|
||||
get
|
||||
{
|
||||
WriterCapabilities caps =
|
||||
new WriterCapabilities();
|
||||
|
||||
caps.SupportedStorageTypes.Add(
|
||||
StorageTypes.LocalFile);
|
||||
|
||||
caps.SupportedStorageTypes.Add(
|
||||
StorageTypes.RemoteFile);
|
||||
|
||||
caps.SupportedTechnologyTypes.Add(
|
||||
TechnologyTypes.Xml);
|
||||
|
||||
caps.SupportedWriteModes.Add(
|
||||
WriteMode.Insert);
|
||||
|
||||
return caps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configured output directory.
|
||||
/// </summary>
|
||||
public WriterDiagnosticResult TestSource(
|
||||
bool validateOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
string directory =
|
||||
GetOutputDirectory();
|
||||
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
if (validateOnly)
|
||||
{
|
||||
return new WriterDiagnosticResult
|
||||
{
|
||||
Success = true,
|
||||
Message =
|
||||
"XML output directory does not exist yet. " +
|
||||
"It will be created on the first write."
|
||||
};
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(
|
||||
directory);
|
||||
}
|
||||
|
||||
return new WriterDiagnosticResult
|
||||
{
|
||||
Success = true,
|
||||
Message =
|
||||
"XML output directory is ready."
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new WriterDiagnosticResult
|
||||
{
|
||||
Success = false,
|
||||
Message =
|
||||
"XML output directory test failed: " +
|
||||
ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes one complete XML payload to disk.
|
||||
/// </summary>
|
||||
public WriterDiagnosticResult WriteData(
|
||||
DataWriteRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(
|
||||
nameof(request));
|
||||
|
||||
if (request.Mode !=
|
||||
WriteMode.Insert)
|
||||
{
|
||||
return Fail(
|
||||
"XML file writer supports Insert mode only.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
request.Payload))
|
||||
{
|
||||
return Fail(
|
||||
"XML payload is empty.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//
|
||||
// Validate the complete payload before any file is created.
|
||||
//
|
||||
XDocument.Parse(
|
||||
request.Payload,
|
||||
LoadOptions.PreserveWhitespace);
|
||||
|
||||
string directory =
|
||||
GetOutputDirectory();
|
||||
|
||||
Directory.CreateDirectory(
|
||||
directory);
|
||||
|
||||
string fileName =
|
||||
CreateSafeFileName(
|
||||
request.OutputFileName);
|
||||
|
||||
string fullPath =
|
||||
CreateUniquePath(
|
||||
directory,
|
||||
fileName);
|
||||
|
||||
File.WriteAllText(
|
||||
fullPath,
|
||||
request.Payload,
|
||||
new UTF8Encoding(false));
|
||||
|
||||
return new WriterDiagnosticResult
|
||||
{
|
||||
Success = true,
|
||||
Message =
|
||||
"XML payload written successfully: " +
|
||||
fullPath,
|
||||
ExecutedTemplate =
|
||||
fullPath
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Fail(
|
||||
"XML payload write failed: " +
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves and validates the configured output directory.
|
||||
/// </summary>
|
||||
private string GetOutputDirectory()
|
||||
{
|
||||
string directory =
|
||||
(cfg.DataSource ??
|
||||
string.Empty)
|
||||
.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(
|
||||
directory))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"XML output directory is not configured.");
|
||||
}
|
||||
|
||||
if (string.Equals(
|
||||
Path.GetExtension(directory),
|
||||
".xml",
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"For XML payload output, Data source must be a directory, not an .xml file path.");
|
||||
}
|
||||
|
||||
return Path.GetFullPath(
|
||||
directory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a safe XML file name.
|
||||
/// </summary>
|
||||
private string CreateSafeFileName(
|
||||
string requestedFileName)
|
||||
{
|
||||
string fileName =
|
||||
string.IsNullOrWhiteSpace(
|
||||
requestedFileName)
|
||||
? "Payload_" +
|
||||
DateTime.Now.ToString(
|
||||
"yyyyMMdd_HHmmss_fff") +
|
||||
".xml"
|
||||
: Path.GetFileName(
|
||||
requestedFileName.Trim());
|
||||
|
||||
foreach (char invalidCharacter
|
||||
in Path.GetInvalidFileNameChars())
|
||||
{
|
||||
fileName =
|
||||
fileName.Replace(
|
||||
invalidCharacter,
|
||||
'_');
|
||||
}
|
||||
|
||||
if (!fileName.EndsWith(
|
||||
".xml",
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fileName +=
|
||||
".xml";
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents accidental overwrite of an existing payload file.
|
||||
/// </summary>
|
||||
private string CreateUniquePath(
|
||||
string directory,
|
||||
string fileName)
|
||||
{
|
||||
string path =
|
||||
Path.Combine(
|
||||
directory,
|
||||
fileName);
|
||||
|
||||
if (!File.Exists(path))
|
||||
return path;
|
||||
|
||||
string name =
|
||||
Path.GetFileNameWithoutExtension(
|
||||
fileName);
|
||||
|
||||
string extension =
|
||||
Path.GetExtension(
|
||||
fileName);
|
||||
|
||||
int index =
|
||||
1;
|
||||
|
||||
do
|
||||
{
|
||||
path =
|
||||
Path.Combine(
|
||||
directory,
|
||||
string.Format(
|
||||
"{0}_{1}{2}",
|
||||
name,
|
||||
index,
|
||||
extension));
|
||||
|
||||
index++;
|
||||
}
|
||||
while (File.Exists(path));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private WriterDiagnosticResult Fail(
|
||||
string message)
|
||||
{
|
||||
return new WriterDiagnosticResult
|
||||
{
|
||||
Success = false,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,10 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
private const int DataEntryCommandTimeoutMs = 5000;
|
||||
private const int DefaultDataEntryOpticalTimeoutMs = 3000;
|
||||
private const int MaxStoredSamples = 40000;
|
||||
private const long RawVolumeModulo = 0x100000000L;
|
||||
private const long RawVolumeModulo = 0x1000000L;
|
||||
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 readonly object commandSync = new object();
|
||||
@@ -40,13 +42,14 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
private AllyCommandService commandService;
|
||||
private SerialPort opticalPort;
|
||||
private bool streamEnabled;
|
||||
private bool opticalVerificationOutputActive;
|
||||
private bool operationActive;
|
||||
private bool hasPreviousRawVolume;
|
||||
private bool hasPreviousRawTimestamp;
|
||||
private bool hasTestStartSample;
|
||||
private uint previousRawVolume;
|
||||
private uint previousRawTimestamp;
|
||||
private long extendedRawVolume;
|
||||
private DateTime? firstSampleReceivedAtUtc;
|
||||
private long extendedRawTimestamp;
|
||||
private double beginWMState;
|
||||
private double endWMState;
|
||||
private double timestampSecStart;
|
||||
@@ -91,15 +94,6 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
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
|
||||
{
|
||||
get
|
||||
@@ -138,25 +132,12 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CommFailed = true;
|
||||
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);
|
||||
log.Error("ALLY optical stream read failed.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,66 +225,30 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
|
||||
public void StartDataStreamProcessing()
|
||||
{
|
||||
StartDataStreamProcessing(true);
|
||||
}
|
||||
GetOpticalVolumeLitersPerRawUnit();
|
||||
|
||||
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
|
||||
lock (opticalSync)
|
||||
{
|
||||
if (requireVolumeConversion)
|
||||
GetOpticalVolumeLitersPerRawUnit();
|
||||
if (streamEnabled)
|
||||
return;
|
||||
|
||||
lock (opticalSync)
|
||||
opticalSamples.Clear();
|
||||
opticalBuffer.Clear();
|
||||
lastOpticalLine = string.Empty;
|
||||
ResetVolumeState();
|
||||
if (DebugLevel == DebugMode.Normal)
|
||||
{
|
||||
if (streamEnabled)
|
||||
{
|
||||
log.Debug("ALLY_OPTO start ignored: stream is already active.");
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
opticalPort = new SerialPort(
|
||||
"COM" + allyCfg.OptoComPortNr,
|
||||
allyCfg.OptoBaudRate,
|
||||
Parity.None,
|
||||
8,
|
||||
StopBits.One);
|
||||
opticalPort.Open();
|
||||
opticalPort.DiscardInBuffer();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CommFailed = true;
|
||||
log.Error("ALLY_OPTO start failed.", ex);
|
||||
throw;
|
||||
|
||||
streamEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,18 +258,12 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
streamEnabled = false;
|
||||
if (opticalPort == null)
|
||||
{
|
||||
log.Debug("ALLY_OPTO stopped: no optical COM port was open.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (opticalPort.IsOpen)
|
||||
{
|
||||
log.InfoFormat("ALLY_OPTO closing {0}.", opticalPort.PortName);
|
||||
opticalPort.Close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -424,46 +363,6 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
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)
|
||||
{
|
||||
double factor = GetResetCalibrationFactorPercent();
|
||||
@@ -716,18 +615,6 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -745,174 +632,32 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
lastOpticalLine = line;
|
||||
|
||||
AllyOpticalSample 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));
|
||||
}
|
||||
if (!AllyOpticalSample.TryParse(line, DateTime.UtcNow, out sample))
|
||||
continue;
|
||||
}
|
||||
|
||||
ExtendRawVolume(sample.RawVolume);
|
||||
if (!firstSampleReceivedAtUtc.HasValue)
|
||||
firstSampleReceivedAtUtc = sample.ReceivedAtUtc;
|
||||
sample.ElapsedSeconds = (sample.ReceivedAtUtc - firstSampleReceivedAtUtc.Value).TotalSeconds;
|
||||
ExtendRawTimestamp(sample.RawTimestamp);
|
||||
sample.ExtendedVolumeLiters =
|
||||
extendedRawVolume * GetOpticalVolumeLitersPerRawUnit();
|
||||
sample.ElapsedSeconds = extendedRawTimestamp / 8192D;
|
||||
|
||||
if (opticalSamples.Count == MaxStoredSamples)
|
||||
opticalSamples.RemoveAt(0);
|
||||
opticalSamples.Add(sample);
|
||||
|
||||
if (IsOpticalVolumeConversionConfigured)
|
||||
{
|
||||
sample.ExtendedVolumeLiters =
|
||||
extendedRawVolume * GetOpticalVolumeLitersPerRawUnit();
|
||||
endWMState = sample.ExtendedVolumeLiters;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample.ExtendedVolumeLiters = Double.NaN;
|
||||
}
|
||||
endWMState = sample.ExtendedVolumeLiters;
|
||||
timestampSecEnd = sample.ElapsedSeconds;
|
||||
|
||||
if (operationActive && !hasTestStartSample && IsOpticalVolumeConversionConfigured)
|
||||
if (operationActive && !hasTestStartSample)
|
||||
{
|
||||
hasTestStartSample = true;
|
||||
beginWMState = sample.ExtendedVolumeLiters;
|
||||
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)
|
||||
{
|
||||
if (!hasPreviousRawVolume)
|
||||
@@ -933,19 +678,51 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
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()
|
||||
{
|
||||
// C6 accumulator is expressed in quarter millilitres, independent of tube size.
|
||||
return 1D / 4000D;
|
||||
// The optical format scales volume by flow-tube size: raw / 16000
|
||||
// for 5/8", 2 * raw / 16000 for 3/4", and 4 * raw / 16000 for 1".
|
||||
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()
|
||||
{
|
||||
hasPreviousRawVolume = false;
|
||||
hasPreviousRawTimestamp = false;
|
||||
hasTestStartSample = false;
|
||||
previousRawVolume = 0;
|
||||
previousRawTimestamp = 0;
|
||||
extendedRawVolume = 0;
|
||||
firstSampleReceivedAtUtc = null;
|
||||
extendedRawTimestamp = 0;
|
||||
beginWMState = 0;
|
||||
endWMState = 0;
|
||||
timestampSecStart = 0;
|
||||
|
||||
@@ -4,51 +4,19 @@ using System.Globalization;
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
/// <summary>
|
||||
/// ALLY optical metrology sample. ALLY emits a tab-separated envelope:
|
||||
/// sequence, message type, Base64 binary payload and a four-hex checksum.
|
||||
/// Type C6 contains the 24-byte C2 water-metrology layout.
|
||||
/// Validated common portion of the 42-byte optical telegram used by the
|
||||
/// register-reader pattern referenced by UI-2093. ALLY calibration-only
|
||||
/// fields are intentionally not inferred without UI-1204/UI-1236.
|
||||
/// </summary>
|
||||
public sealed class AllyOpticalSample
|
||||
{
|
||||
private const byte MetrologyPacketType = 0xC6;
|
||||
private const int MetrologyPayloadLength = 24;
|
||||
private const int TelegramLength = 42;
|
||||
|
||||
public string RawLine { 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 uint RawVolume { 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 uint RawTimestamp { get; private set; }
|
||||
public double ExtendedVolumeLiters { get; internal set; }
|
||||
public double ElapsedSeconds { get; internal set; }
|
||||
|
||||
@@ -65,77 +33,49 @@ namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
return false;
|
||||
|
||||
string telegram = line.TrimEnd('\r', '\n');
|
||||
string[] fields = telegram.Split('\t');
|
||||
if (fields.Length != 4)
|
||||
if (line.Length < TelegramLength)
|
||||
return false;
|
||||
|
||||
byte sequence;
|
||||
byte packetType;
|
||||
ushort checksum;
|
||||
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)
|
||||
string telegram = line.Substring(line.Length - TelegramLength, TelegramLength);
|
||||
if (telegram[6] != '\t' || telegram[11] != '\t' || telegram[16] != '\t' ||
|
||||
telegram[23] != '\t' || telegram[28] != '\t' || telegram[37] != '\t' ||
|
||||
telegram[40] != '\r' || telegram[41] != '\n')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.Length < MetrologyPayloadLength)
|
||||
ushort rawFlowUnsigned;
|
||||
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;
|
||||
|
||||
sample = new AllyOpticalSample
|
||||
{
|
||||
RawLine = line,
|
||||
RawLine = telegram,
|
||||
ReceivedAtUtc = receivedAtUtc,
|
||||
Sequence = sequence,
|
||||
PacketType = packetType,
|
||||
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)
|
||||
RawFlow = unchecked((short)rawFlowUnsigned),
|
||||
RawVolume = rawVolume,
|
||||
RawTimestamp = rawTimestamp
|
||||
};
|
||||
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)
|
||||
{
|
||||
return new AllyReaderCfgCtrl();
|
||||
return new Configs.ParamsProvider.ComponentCfgCtrl(this, null);
|
||||
}
|
||||
|
||||
public string ComponentName { get { return Name; } }
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,22 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<?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,96 +93,6 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
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)
|
||||
{
|
||||
AllyResponse response = Send(
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
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,8 +16,6 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
ViewFactoryId = 0x01,
|
||||
ViewVersionAndType = 0x05,
|
||||
ViewProgrammableText = 0x07,
|
||||
ViewPresetTotal = 0x13,
|
||||
SetPresetTotal = 0x14,
|
||||
SetMeterMode = 0x1A,
|
||||
SetValvePosition = 0x1E
|
||||
@@ -31,9 +29,6 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
SetCalibration = 0x54,
|
||||
ViewRebootCount = 0x55,
|
||||
SetDiagnosticLed = 0x60,
|
||||
ViewSecondsActive = 0x3D,
|
||||
ViewFactorySeal = 0x63,
|
||||
SetFactorySeal = 0x64,
|
||||
SetLcdTimeout = 0x8C,
|
||||
StartOffsetLearning = 0xD1
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Ports;
|
||||
using log4net;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
@@ -63,7 +62,6 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
|
||||
public sealed class AllySerialTransport : IAllyTransport
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(AllySerialTransport));
|
||||
private readonly object sync = new object();
|
||||
private readonly string portName;
|
||||
private readonly int baudRate;
|
||||
@@ -115,62 +113,33 @@ namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
|
||||
serialPort.DiscardInBuffer();
|
||||
serialPort.WriteTimeout = timeoutMs;
|
||||
log.DebugFormat("ALLY_CMD TX {0}: {1}", portName, FormatRequestForLog(request));
|
||||
serialPort.Write(request, 0, request.Length);
|
||||
|
||||
try
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
int first;
|
||||
do
|
||||
{
|
||||
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;
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
int remaining = timeoutMs - (int)stopwatch.ElapsedMilliseconds;
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
public int HeadCommunicationComPortNr;
|
||||
public int OptoComPortNr;
|
||||
public int RfidComPortNr; /// 0 = use MuxBoardNr
|
||||
public int MuxBoardNr; /// 0 = use RfidComPort(Nr), otherwise mux. board nr. 1 .. 4
|
||||
public int MuxBoardNr; /// 0 = use RfidComPort(Nr), otherwise mux. board nr. 1 .. 4 in new 1 .. 10 - paralel genesis access
|
||||
public int Group; /// Number written to QuidoRS to connct the watermeter to RfidComPort, 1 .. 10
|
||||
//public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC
|
||||
public string CommunicationInterfaceBridge; /// Communication Interface: RFID or NFC
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
message += Environment.NewLine + "'Head communication serial port nr.' is not valid";
|
||||
}
|
||||
|
||||
if (!int.TryParse(muxBoardNrTextBox.Text, out dummy) || dummy < 1 || dummy > 4)
|
||||
if (!int.TryParse(muxBoardNrTextBox.Text, out dummy) || dummy < 1 || dummy > 10)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + string.Format(Strings.Invalid_0, muxBoardNrLabel.Text);
|
||||
|
||||
+84
-106
@@ -76,11 +76,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
//
|
||||
this.tabControl1.Controls.Add(this.tabPage1);
|
||||
this.tabControl1.Controls.Add(this.tabPage2);
|
||||
this.tabControl1.Location = new System.Drawing.Point(3, 4);
|
||||
this.tabControl1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.tabControl1.Location = new System.Drawing.Point(2, 3);
|
||||
this.tabControl1.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
|
||||
this.tabControl1.Name = "tabControl1";
|
||||
this.tabControl1.SelectedIndex = 0;
|
||||
this.tabControl1.Size = new System.Drawing.Size(687, 540);
|
||||
this.tabControl1.Size = new System.Drawing.Size(458, 351);
|
||||
this.tabControl1.TabIndex = 0;
|
||||
//
|
||||
// tabPage1
|
||||
@@ -99,40 +99,42 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
this.tabPage1.Controls.Add(this.nameTextBox);
|
||||
this.tabPage1.Controls.Add(this.nameLabel);
|
||||
this.tabPage1.Controls.Add(this.classNameLabel);
|
||||
this.tabPage1.Location = new System.Drawing.Point(4, 29);
|
||||
this.tabPage1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.tabPage1.Location = new System.Drawing.Point(4, 22);
|
||||
this.tabPage1.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
|
||||
this.tabPage1.Name = "tabPage1";
|
||||
this.tabPage1.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.tabPage1.Size = new System.Drawing.Size(679, 507);
|
||||
this.tabPage1.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);
|
||||
this.tabPage1.Size = new System.Drawing.Size(450, 325);
|
||||
this.tabPage1.TabIndex = 0;
|
||||
this.tabPage1.Text = "Config";
|
||||
this.tabPage1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// label5
|
||||
//
|
||||
this.label5.Location = new System.Drawing.Point(372, 55);
|
||||
this.label5.Location = new System.Drawing.Point(248, 36);
|
||||
this.label5.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.label5.Name = "label5";
|
||||
this.label5.Size = new System.Drawing.Size(100, 23);
|
||||
this.label5.Size = new System.Drawing.Size(67, 15);
|
||||
this.label5.TabIndex = 28;
|
||||
this.label5.Text = "Slot Nr:";
|
||||
//
|
||||
// textBoxSlotNr
|
||||
//
|
||||
this.textBoxSlotNr.Enabled = false;
|
||||
this.textBoxSlotNr.Location = new System.Drawing.Point(480, 55);
|
||||
this.textBoxSlotNr.Location = new System.Drawing.Point(320, 36);
|
||||
this.textBoxSlotNr.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
|
||||
this.textBoxSlotNr.Name = "textBoxSlotNr";
|
||||
this.textBoxSlotNr.Size = new System.Drawing.Size(74, 26);
|
||||
this.textBoxSlotNr.Size = new System.Drawing.Size(51, 20);
|
||||
this.textBoxSlotNr.TabIndex = 27;
|
||||
//
|
||||
// groupBox2
|
||||
//
|
||||
this.groupBox2.Controls.Add(this.headPortNrTextBox);
|
||||
this.groupBox2.Controls.Add(this.label2);
|
||||
this.groupBox2.Location = new System.Drawing.Point(11, 450);
|
||||
this.groupBox2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.groupBox2.Location = new System.Drawing.Point(7, 292);
|
||||
this.groupBox2.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
|
||||
this.groupBox2.Name = "groupBox2";
|
||||
this.groupBox2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.groupBox2.Size = new System.Drawing.Size(621, 52);
|
||||
this.groupBox2.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);
|
||||
this.groupBox2.Size = new System.Drawing.Size(414, 34);
|
||||
this.groupBox2.TabIndex = 26;
|
||||
this.groupBox2.TabStop = false;
|
||||
this.groupBox2.Text = "Head Communication";
|
||||
@@ -140,41 +142,37 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
// headPortNrTextBox
|
||||
//
|
||||
this.headPortNrTextBox.Enabled = false;
|
||||
this.headPortNrTextBox.Location = new System.Drawing.Point(494, 19);
|
||||
this.headPortNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.headPortNrTextBox.Location = new System.Drawing.Point(329, 12);
|
||||
this.headPortNrTextBox.Name = "headPortNrTextBox";
|
||||
this.headPortNrTextBox.Size = new System.Drawing.Size(49, 26);
|
||||
this.headPortNrTextBox.Size = new System.Drawing.Size(34, 20);
|
||||
this.headPortNrTextBox.TabIndex = 8;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Location = new System.Drawing.Point(361, 22);
|
||||
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label2.Location = new System.Drawing.Point(241, 14);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(107, 20);
|
||||
this.label2.Size = new System.Drawing.Size(72, 13);
|
||||
this.label2.TabIndex = 7;
|
||||
this.label2.Text = "Serial port nr.:";
|
||||
//
|
||||
// label4
|
||||
//
|
||||
this.label4.AutoSize = true;
|
||||
this.label4.Location = new System.Drawing.Point(234, 126);
|
||||
this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label4.Location = new System.Drawing.Point(156, 82);
|
||||
this.label4.Name = "label4";
|
||||
this.label4.Size = new System.Drawing.Size(52, 20);
|
||||
this.label4.Size = new System.Drawing.Size(37, 13);
|
||||
this.label4.TabIndex = 25;
|
||||
this.label4.Text = "1 .. 10";
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.AutoSize = true;
|
||||
this.label3.Location = new System.Drawing.Point(234, 90);
|
||||
this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label3.Location = new System.Drawing.Point(156, 58);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(43, 20);
|
||||
this.label3.Size = new System.Drawing.Size(37, 13);
|
||||
this.label3.TabIndex = 24;
|
||||
this.label3.Text = "1 .. 4";
|
||||
this.label3.Text = "1 .. 10";
|
||||
//
|
||||
// groupBox1
|
||||
//
|
||||
@@ -182,11 +180,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
this.groupBox1.Controls.Add(this.label1);
|
||||
this.groupBox1.Controls.Add(this.rfidPortNrTextBox);
|
||||
this.groupBox1.Controls.Add(this.rfidSerialPortNrLabel);
|
||||
this.groupBox1.Location = new System.Drawing.Point(11, 363);
|
||||
this.groupBox1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.groupBox1.Location = new System.Drawing.Point(7, 236);
|
||||
this.groupBox1.Name = "groupBox1";
|
||||
this.groupBox1.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.groupBox1.Size = new System.Drawing.Size(621, 85);
|
||||
this.groupBox1.Size = new System.Drawing.Size(414, 55);
|
||||
this.groupBox1.TabIndex = 23;
|
||||
this.groupBox1.TabStop = false;
|
||||
this.groupBox1.Text = "RFID / NFC communication (in case mux. board is not used)";
|
||||
@@ -195,38 +191,35 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
//
|
||||
this.comboBoxCommunicationInterface.Enabled = false;
|
||||
this.comboBoxCommunicationInterface.FormattingEnabled = true;
|
||||
this.comboBoxCommunicationInterface.Location = new System.Drawing.Point(202, 34);
|
||||
this.comboBoxCommunicationInterface.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.comboBoxCommunicationInterface.Location = new System.Drawing.Point(135, 22);
|
||||
this.comboBoxCommunicationInterface.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
|
||||
this.comboBoxCommunicationInterface.Name = "comboBoxCommunicationInterface";
|
||||
this.comboBoxCommunicationInterface.Size = new System.Drawing.Size(152, 28);
|
||||
this.comboBoxCommunicationInterface.Size = new System.Drawing.Size(103, 21);
|
||||
this.comboBoxCommunicationInterface.TabIndex = 9;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(8, 38);
|
||||
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label1.Location = new System.Drawing.Point(5, 25);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(187, 20);
|
||||
this.label1.Size = new System.Drawing.Size(124, 13);
|
||||
this.label1.TabIndex = 8;
|
||||
this.label1.Text = "Communication Interface";
|
||||
//
|
||||
// rfidPortNrTextBox
|
||||
//
|
||||
this.rfidPortNrTextBox.Enabled = false;
|
||||
this.rfidPortNrTextBox.Location = new System.Drawing.Point(494, 32);
|
||||
this.rfidPortNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.rfidPortNrTextBox.Location = new System.Drawing.Point(329, 21);
|
||||
this.rfidPortNrTextBox.Name = "rfidPortNrTextBox";
|
||||
this.rfidPortNrTextBox.Size = new System.Drawing.Size(49, 26);
|
||||
this.rfidPortNrTextBox.Size = new System.Drawing.Size(34, 20);
|
||||
this.rfidPortNrTextBox.TabIndex = 7;
|
||||
//
|
||||
// rfidSerialPortNrLabel
|
||||
//
|
||||
this.rfidSerialPortNrLabel.AutoSize = true;
|
||||
this.rfidSerialPortNrLabel.Location = new System.Drawing.Point(361, 38);
|
||||
this.rfidSerialPortNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.rfidSerialPortNrLabel.Location = new System.Drawing.Point(241, 25);
|
||||
this.rfidSerialPortNrLabel.Name = "rfidSerialPortNrLabel";
|
||||
this.rfidSerialPortNrLabel.Size = new System.Drawing.Size(107, 20);
|
||||
this.rfidSerialPortNrLabel.Size = new System.Drawing.Size(72, 13);
|
||||
this.rfidSerialPortNrLabel.TabIndex = 6;
|
||||
this.rfidSerialPortNrLabel.Text = "Serial port nr.:";
|
||||
//
|
||||
@@ -243,11 +236,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
this.optoDataGroupBox.Controls.Add(this.radioButton2);
|
||||
this.optoDataGroupBox.Controls.Add(this.optoSerialPortLabel);
|
||||
this.optoDataGroupBox.Controls.Add(this.optoSerialPortTextBox);
|
||||
this.optoDataGroupBox.Location = new System.Drawing.Point(11, 164);
|
||||
this.optoDataGroupBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.optoDataGroupBox.Location = new System.Drawing.Point(7, 107);
|
||||
this.optoDataGroupBox.Name = "optoDataGroupBox";
|
||||
this.optoDataGroupBox.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.optoDataGroupBox.Size = new System.Drawing.Size(621, 189);
|
||||
this.optoDataGroupBox.Size = new System.Drawing.Size(414, 123);
|
||||
this.optoDataGroupBox.TabIndex = 18;
|
||||
this.optoDataGroupBox.TabStop = false;
|
||||
this.optoDataGroupBox.Text = "Opto-data";
|
||||
@@ -257,9 +248,10 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
this.checkBox_EnableShowChanels.Checked = true;
|
||||
this.checkBox_EnableShowChanels.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.checkBox_EnableShowChanels.Enabled = false;
|
||||
this.checkBox_EnableShowChanels.Location = new System.Drawing.Point(351, 147);
|
||||
this.checkBox_EnableShowChanels.Location = new System.Drawing.Point(234, 96);
|
||||
this.checkBox_EnableShowChanels.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
|
||||
this.checkBox_EnableShowChanels.Name = "checkBox_EnableShowChanels";
|
||||
this.checkBox_EnableShowChanels.Size = new System.Drawing.Size(238, 24);
|
||||
this.checkBox_EnableShowChanels.Size = new System.Drawing.Size(159, 16);
|
||||
this.checkBox_EnableShowChanels.TabIndex = 10;
|
||||
this.checkBox_EnableShowChanels.Text = "Enable Show Channels";
|
||||
this.checkBox_EnableShowChanels.UseVisualStyleBackColor = true;
|
||||
@@ -267,58 +259,56 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
// tBBeginDataFlush
|
||||
//
|
||||
this.tBBeginDataFlush.Enabled = false;
|
||||
this.tBBeginDataFlush.Location = new System.Drawing.Point(474, 105);
|
||||
this.tBBeginDataFlush.Location = new System.Drawing.Point(316, 68);
|
||||
this.tBBeginDataFlush.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
|
||||
this.tBBeginDataFlush.MaxLength = 8;
|
||||
this.tBBeginDataFlush.Name = "tBBeginDataFlush";
|
||||
this.tBBeginDataFlush.Size = new System.Drawing.Size(69, 26);
|
||||
this.tBBeginDataFlush.Size = new System.Drawing.Size(47, 20);
|
||||
this.tBBeginDataFlush.TabIndex = 9;
|
||||
this.tBBeginDataFlush.Text = "2000";
|
||||
this.tBBeginDataFlush.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
|
||||
//
|
||||
// labelFlush
|
||||
//
|
||||
this.labelFlush.Location = new System.Drawing.Point(328, 109);
|
||||
this.labelFlush.Location = new System.Drawing.Point(219, 71);
|
||||
this.labelFlush.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.labelFlush.Name = "labelFlush";
|
||||
this.labelFlush.Size = new System.Drawing.Size(140, 22);
|
||||
this.labelFlush.Size = new System.Drawing.Size(93, 14);
|
||||
this.labelFlush.TabIndex = 8;
|
||||
this.labelFlush.Text = "Begin Data Flush:";
|
||||
//
|
||||
// tcpipPortLabel
|
||||
//
|
||||
this.tcpipPortLabel.AutoSize = true;
|
||||
this.tcpipPortLabel.Location = new System.Drawing.Point(46, 109);
|
||||
this.tcpipPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.tcpipPortLabel.Location = new System.Drawing.Point(31, 71);
|
||||
this.tcpipPortLabel.Name = "tcpipPortLabel";
|
||||
this.tcpipPortLabel.Size = new System.Drawing.Size(68, 20);
|
||||
this.tcpipPortLabel.Size = new System.Drawing.Size(47, 13);
|
||||
this.tcpipPortLabel.TabIndex = 4;
|
||||
this.tcpipPortLabel.Text = "Port nr..:";
|
||||
//
|
||||
// tcpipPortTextBox
|
||||
//
|
||||
this.tcpipPortTextBox.Enabled = false;
|
||||
this.tcpipPortTextBox.Location = new System.Drawing.Point(161, 105);
|
||||
this.tcpipPortTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.tcpipPortTextBox.Location = new System.Drawing.Point(107, 68);
|
||||
this.tcpipPortTextBox.Name = "tcpipPortTextBox";
|
||||
this.tcpipPortTextBox.Size = new System.Drawing.Size(57, 26);
|
||||
this.tcpipPortTextBox.Size = new System.Drawing.Size(39, 20);
|
||||
this.tcpipPortTextBox.TabIndex = 5;
|
||||
//
|
||||
// ipAddressLabel
|
||||
//
|
||||
this.ipAddressLabel.AutoSize = true;
|
||||
this.ipAddressLabel.Location = new System.Drawing.Point(46, 74);
|
||||
this.ipAddressLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.ipAddressLabel.Location = new System.Drawing.Point(31, 48);
|
||||
this.ipAddressLabel.Name = "ipAddressLabel";
|
||||
this.ipAddressLabel.Size = new System.Drawing.Size(93, 20);
|
||||
this.ipAddressLabel.Size = new System.Drawing.Size(63, 13);
|
||||
this.ipAddressLabel.TabIndex = 2;
|
||||
this.ipAddressLabel.Text = "IP address.:";
|
||||
//
|
||||
// ipAddressTextBox
|
||||
//
|
||||
this.ipAddressTextBox.Enabled = false;
|
||||
this.ipAddressTextBox.Location = new System.Drawing.Point(161, 69);
|
||||
this.ipAddressTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.ipAddressTextBox.Location = new System.Drawing.Point(107, 45);
|
||||
this.ipAddressTextBox.Name = "ipAddressTextBox";
|
||||
this.ipAddressTextBox.Size = new System.Drawing.Size(145, 26);
|
||||
this.ipAddressTextBox.Size = new System.Drawing.Size(98, 20);
|
||||
this.ipAddressTextBox.TabIndex = 3;
|
||||
//
|
||||
// radioButton1
|
||||
@@ -326,10 +316,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
this.radioButton1.AutoSize = true;
|
||||
this.radioButton1.Checked = true;
|
||||
this.radioButton1.Enabled = false;
|
||||
this.radioButton1.Location = new System.Drawing.Point(33, 29);
|
||||
this.radioButton1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.radioButton1.Location = new System.Drawing.Point(22, 19);
|
||||
this.radioButton1.Name = "radioButton1";
|
||||
this.radioButton1.Size = new System.Drawing.Size(109, 24);
|
||||
this.radioButton1.Size = new System.Drawing.Size(83, 17);
|
||||
this.radioButton1.TabIndex = 0;
|
||||
this.radioButton1.TabStop = true;
|
||||
this.radioButton1.Text = "Use TCP/IP";
|
||||
@@ -339,10 +328,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
//
|
||||
this.radioButton2.AutoSize = true;
|
||||
this.radioButton2.Enabled = false;
|
||||
this.radioButton2.Location = new System.Drawing.Point(351, 29);
|
||||
this.radioButton2.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.radioButton2.Location = new System.Drawing.Point(234, 19);
|
||||
this.radioButton2.Name = "radioButton2";
|
||||
this.radioButton2.Size = new System.Drawing.Size(129, 24);
|
||||
this.radioButton2.Size = new System.Drawing.Size(92, 17);
|
||||
this.radioButton2.TabIndex = 1;
|
||||
this.radioButton2.Text = "Use serial port";
|
||||
this.radioButton2.UseVisualStyleBackColor = true;
|
||||
@@ -350,108 +338,98 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
|
||||
// optoSerialPortLabel
|
||||
//
|
||||
this.optoSerialPortLabel.AutoSize = true;
|
||||
this.optoSerialPortLabel.Location = new System.Drawing.Point(361, 69);
|
||||
this.optoSerialPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.optoSerialPortLabel.Location = new System.Drawing.Point(241, 45);
|
||||
this.optoSerialPortLabel.Name = "optoSerialPortLabel";
|
||||
this.optoSerialPortLabel.Size = new System.Drawing.Size(107, 20);
|
||||
this.optoSerialPortLabel.Size = new System.Drawing.Size(72, 13);
|
||||
this.optoSerialPortLabel.TabIndex = 6;
|
||||
this.optoSerialPortLabel.Text = "Serial port nr.:";
|
||||
//
|
||||
// optoSerialPortTextBox
|
||||
//
|
||||
this.optoSerialPortTextBox.Enabled = false;
|
||||
this.optoSerialPortTextBox.Location = new System.Drawing.Point(494, 65);
|
||||
this.optoSerialPortTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.optoSerialPortTextBox.Location = new System.Drawing.Point(329, 42);
|
||||
this.optoSerialPortTextBox.Name = "optoSerialPortTextBox";
|
||||
this.optoSerialPortTextBox.Size = new System.Drawing.Size(49, 26);
|
||||
this.optoSerialPortTextBox.Size = new System.Drawing.Size(34, 20);
|
||||
this.optoSerialPortTextBox.TabIndex = 7;
|
||||
//
|
||||
// groupTextBox
|
||||
//
|
||||
this.groupTextBox.Enabled = false;
|
||||
this.groupTextBox.Location = new System.Drawing.Point(172, 121);
|
||||
this.groupTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.groupTextBox.Location = new System.Drawing.Point(115, 79);
|
||||
this.groupTextBox.Name = "groupTextBox";
|
||||
this.groupTextBox.Size = new System.Drawing.Size(49, 26);
|
||||
this.groupTextBox.Size = new System.Drawing.Size(34, 20);
|
||||
this.groupTextBox.TabIndex = 22;
|
||||
//
|
||||
// groupLabel
|
||||
//
|
||||
this.groupLabel.AutoSize = true;
|
||||
this.groupLabel.Location = new System.Drawing.Point(7, 126);
|
||||
this.groupLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.groupLabel.Location = new System.Drawing.Point(5, 82);
|
||||
this.groupLabel.Name = "groupLabel";
|
||||
this.groupLabel.Size = new System.Drawing.Size(67, 20);
|
||||
this.groupLabel.Size = new System.Drawing.Size(45, 13);
|
||||
this.groupLabel.TabIndex = 21;
|
||||
this.groupLabel.Text = "Group 2";
|
||||
//
|
||||
// muxBoardNrTextBox
|
||||
//
|
||||
this.muxBoardNrTextBox.Enabled = false;
|
||||
this.muxBoardNrTextBox.Location = new System.Drawing.Point(172, 86);
|
||||
this.muxBoardNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.muxBoardNrTextBox.Location = new System.Drawing.Point(115, 56);
|
||||
this.muxBoardNrTextBox.Name = "muxBoardNrTextBox";
|
||||
this.muxBoardNrTextBox.Size = new System.Drawing.Size(49, 26);
|
||||
this.muxBoardNrTextBox.Size = new System.Drawing.Size(34, 20);
|
||||
this.muxBoardNrTextBox.TabIndex = 20;
|
||||
//
|
||||
// muxBoardNrLabel
|
||||
//
|
||||
this.muxBoardNrLabel.AutoSize = true;
|
||||
this.muxBoardNrLabel.Location = new System.Drawing.Point(7, 90);
|
||||
this.muxBoardNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.muxBoardNrLabel.Location = new System.Drawing.Point(5, 58);
|
||||
this.muxBoardNrLabel.Name = "muxBoardNrLabel";
|
||||
this.muxBoardNrLabel.Size = new System.Drawing.Size(159, 20);
|
||||
this.muxBoardNrLabel.Size = new System.Drawing.Size(106, 13);
|
||||
this.muxBoardNrLabel.TabIndex = 19;
|
||||
this.muxBoardNrLabel.Text = "Group 1 (mux. board)";
|
||||
//
|
||||
// nameTextBox
|
||||
//
|
||||
this.nameTextBox.Enabled = false;
|
||||
this.nameTextBox.Location = new System.Drawing.Point(172, 50);
|
||||
this.nameTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.nameTextBox.Location = new System.Drawing.Point(115, 32);
|
||||
this.nameTextBox.Name = "nameTextBox";
|
||||
this.nameTextBox.Size = new System.Drawing.Size(180, 26);
|
||||
this.nameTextBox.Size = new System.Drawing.Size(121, 20);
|
||||
this.nameTextBox.TabIndex = 17;
|
||||
//
|
||||
// nameLabel
|
||||
//
|
||||
this.nameLabel.AutoSize = true;
|
||||
this.nameLabel.Location = new System.Drawing.Point(7, 55);
|
||||
this.nameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.nameLabel.Location = new System.Drawing.Point(5, 36);
|
||||
this.nameLabel.Name = "nameLabel";
|
||||
this.nameLabel.Size = new System.Drawing.Size(51, 20);
|
||||
this.nameLabel.Size = new System.Drawing.Size(35, 13);
|
||||
this.nameLabel.TabIndex = 16;
|
||||
this.nameLabel.Text = "Name";
|
||||
//
|
||||
// classNameLabel
|
||||
//
|
||||
this.classNameLabel.AutoSize = true;
|
||||
this.classNameLabel.Location = new System.Drawing.Point(168, 14);
|
||||
this.classNameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.classNameLabel.Location = new System.Drawing.Point(112, 9);
|
||||
this.classNameLabel.Name = "classNameLabel";
|
||||
this.classNameLabel.Size = new System.Drawing.Size(90, 20);
|
||||
this.classNameLabel.Size = new System.Drawing.Size(60, 13);
|
||||
this.classNameLabel.TabIndex = 15;
|
||||
this.classNameLabel.Text = "ClassName";
|
||||
//
|
||||
// tabPage2
|
||||
//
|
||||
this.tabPage2.Location = new System.Drawing.Point(4, 29);
|
||||
this.tabPage2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.tabPage2.Location = new System.Drawing.Point(4, 22);
|
||||
this.tabPage2.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
|
||||
this.tabPage2.Name = "tabPage2";
|
||||
this.tabPage2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.tabPage2.Size = new System.Drawing.Size(679, 507);
|
||||
this.tabPage2.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);
|
||||
this.tabPage2.Size = new System.Drawing.Size(450, 325);
|
||||
this.tabPage2.TabIndex = 1;
|
||||
this.tabPage2.Text = "Test";
|
||||
this.tabPage2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// GenesisCfgCtrl
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.tabControl1);
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
this.Name = "GenesisCfgCtrl";
|
||||
this.Size = new System.Drawing.Size(694, 548);
|
||||
this.Size = new System.Drawing.Size(463, 356);
|
||||
this.Load += new System.EventHandler(this.WaterMeterCfgCtrl_Load);
|
||||
this.tabControl1.ResumeLayout(false);
|
||||
this.tabPage1.ResumeLayout(false);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GenesisCordonelInterface.API;
|
||||
using GenesisCordonelInterface.Core.Threading;
|
||||
using TBF.Rig.BridgeComponents.GciBridge;
|
||||
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
{
|
||||
public class GciBridgeClient : IGciBridgeClient
|
||||
{
|
||||
private readonly GciBridge bridge;
|
||||
|
||||
public GciBridgeClient(GciBridge bridge)
|
||||
{
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
public Task<RetryResult<PublicModels.RegisterReadResult>>
|
||||
ReadRegisterWithRetryAsync(
|
||||
int slotId,
|
||||
string registerName,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return bridge.ReadRegisterWithRetryAsync(
|
||||
slotId,
|
||||
registerName,
|
||||
token);
|
||||
}
|
||||
|
||||
public Task<RetryResult<PublicModels.RegisterWriteResult>>
|
||||
WriteRegisterWithRetryAsync(
|
||||
int slotId,
|
||||
string registerName,
|
||||
ushort value,
|
||||
bool verify,
|
||||
bool throwOnError,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return bridge.WriteRegisterWithRetryAsync(
|
||||
slotId,
|
||||
registerName,
|
||||
value,
|
||||
verify,
|
||||
throwOnError,
|
||||
token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GenesisCordonelInterface.API;
|
||||
using GenesisCordonelInterface.Core.Threading;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
{
|
||||
public interface IGciBridgeClient
|
||||
{
|
||||
Task<RetryResult<PublicModels.RegisterReadResult>>
|
||||
ReadRegisterWithRetryAsync(
|
||||
int slotId,
|
||||
string registerName,
|
||||
CancellationToken token = default);
|
||||
|
||||
Task<RetryResult<PublicModels.RegisterWriteResult>>
|
||||
WriteRegisterWithRetryAsync(
|
||||
int slotId,
|
||||
string registerName,
|
||||
ushort value,
|
||||
bool verify,
|
||||
bool throwOnError,
|
||||
CancellationToken token = default);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,29 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GenesisCordonelInterface.API;
|
||||
using log4net;
|
||||
using TBF.Rig.BridgeComponents.GciBridge;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.common;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
|
||||
using PublicModels = TBF.Rig.BridgeComponents.GciBridge.Interfaces.PublicModels;
|
||||
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
{
|
||||
public class ReadPcbResult
|
||||
{
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
public bool IsLoggedOn { get; set; }
|
||||
|
||||
public bool IsValidPcb { get; set; }
|
||||
|
||||
public string PcbId { get; set; }
|
||||
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class RadioService
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(RadioService));
|
||||
@@ -18,6 +31,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
static string okResponse = "Command complete, no errors";
|
||||
static string errorResponse = "Unable to execute";
|
||||
|
||||
private bool bConnected = false;
|
||||
|
||||
private GciBridge _bridge;
|
||||
|
||||
public RadioService(GciBridge genesisHeadCommInterfaceBridgeComponent)
|
||||
@@ -26,260 +41,492 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
log.Debug("RadioService created with GciBridge= " + genesisHeadCommInterfaceBridgeComponent + "");
|
||||
}
|
||||
|
||||
public async Task<string> ReadRequest_PCBAsyn1(GenesisSmartReader iHead)
|
||||
private async Task<ReadPcbResult> EnsureConnectedAsync(
|
||||
GenesisSmartReader head,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (iHead?.CommInterfaceBridge == null)
|
||||
return null;
|
||||
|
||||
var connectTask = iHead.CommInterfaceBridge.ConnectAsync(iHead.GetSlotNr);
|
||||
|
||||
// Wait either for ConnectAsync or timeout
|
||||
if (await Task.WhenAny(connectTask, Task.Delay(TimeSpan.FromMinutes(1))) != connectTask)
|
||||
log.Debug("EnsureConnectedAsync called for iHead: " + head);
|
||||
if (head?.CommInterfaceBridge == null)
|
||||
{
|
||||
// Timed out
|
||||
return null;
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = false,
|
||||
IsLoggedOn = false,
|
||||
IsValidPcb = false,
|
||||
Message = "CommInterfaceBridge is null."
|
||||
};
|
||||
}
|
||||
|
||||
var slotInfo = await _bridge.GetSlotAsync(head.GetSlotNr);
|
||||
if (slotInfo == null || !slotInfo.Success)
|
||||
{
|
||||
//just no definet yet ?
|
||||
log.Debug("EnsureConnectedAsync() - SlotInfo is null.");
|
||||
}
|
||||
else if (slotInfo.IsConnected)
|
||||
{
|
||||
log.Debug("EnsureConnectedAsync() - Slot: " + slotInfo);
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = true,
|
||||
IsLoggedOn = slotInfo.IsLoggedOn,
|
||||
IsValidPcb = false,
|
||||
PcbId = slotInfo.PcbId,
|
||||
Message = "Already connected."
|
||||
};
|
||||
}
|
||||
|
||||
log.Debug("EnsureConnectedAsync() - Connecting...");
|
||||
|
||||
var connectTask = head.CommInterfaceBridge.ConnectAsync(head.GetSlotNr, token);
|
||||
var timeoutTask = Task.Delay(TimeSpan.FromMinutes(1), token);
|
||||
|
||||
var completedTask = await Task.WhenAny(connectTask, timeoutTask);
|
||||
|
||||
if (completedTask != connectTask)
|
||||
{
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = false,
|
||||
IsLoggedOn = false,
|
||||
IsValidPcb = false,
|
||||
Message = "Connect timeout."
|
||||
};
|
||||
}
|
||||
|
||||
var result = await connectTask;
|
||||
|
||||
if (result == null || !result.Success || !result.IsConnected)
|
||||
return null;
|
||||
|
||||
string pcbId = result.PcbId;
|
||||
|
||||
if (!string.IsNullOrEmpty(pcbId))
|
||||
if (result == null || !result.Success)
|
||||
{
|
||||
if (iHead.ConfigStruct != null)
|
||||
iHead.ConfigStruct.PCBNumberString = pcbId;
|
||||
|
||||
return pcbId;
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = false,
|
||||
IsLoggedOn = false,
|
||||
IsValidPcb = false,
|
||||
Message = result == null ? "Connect result is null." : "Connect failed."
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = result.IsConnected,
|
||||
IsLoggedOn = false,
|
||||
IsValidPcb = false,
|
||||
Message = "Connected OK."
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<string> ReadRequest_PCBAsync(GenesisSmartReader iHead)
|
||||
|
||||
public async Task<PublicModels.UdsPasswordResult> FindKeyStone(
|
||||
string txtPCBId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
log.Debug("ReadRequest_PCB called for iHead: " + iHead);
|
||||
string pcbId = txtPCBId?.Trim();
|
||||
|
||||
if (iHead?.CommInterfaceBridge == null)
|
||||
return null;
|
||||
if (string.IsNullOrWhiteSpace(pcbId))
|
||||
throw new Exception("PCB ID is empty.");
|
||||
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
PublicModels.GciConnectResult result;
|
||||
PublicModels.UdsPasswordResult result = await _bridge.GetPasswordAsync(pcbId, token);
|
||||
|
||||
try
|
||||
{
|
||||
log.Debug("ReadRequest_PCB() - calling ConnectAsync");
|
||||
log.Debug("GetPasswordAsync PCB=" + pcbId + " Result: " + result);
|
||||
|
||||
result = await iHead.CommInterfaceBridge
|
||||
.ConnectAsync(iHead.GetSlotNr, cts.Token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
log.Debug("ReadRequest_PCB() - ConnectAsync completed");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
log.Debug("ReadRequest_PCB() - ConnectAsync timeout/cancelled");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("ReadRequest_PCB() - ConnectAsync failed", ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result == null || !result.Success || !result.IsConnected)
|
||||
{
|
||||
log.Debug("ReadRequest_PCB() - invalid result: " + result);
|
||||
return null;
|
||||
}
|
||||
|
||||
string pcbId = result.PcbId;
|
||||
|
||||
if (string.IsNullOrEmpty(pcbId))
|
||||
{
|
||||
log.Debug("ReadRequest_PCB() - pcbId is empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (iHead.ConfigStruct != null)
|
||||
{
|
||||
iHead.ConfigStruct.PCBNumberString = pcbId;
|
||||
log.Debug("ReadRequest_PCB() - PCBNumberString: " + pcbId);
|
||||
}
|
||||
|
||||
return pcbId;
|
||||
return result;
|
||||
}
|
||||
|
||||
public string ReadRequest_PCB(ref GenesisSmartReader iHead)
|
||||
public async Task<GenesisCordonelInterface.API.PublicModels.GciLoginResult> LoginByPasswordAsync(
|
||||
int slotId, string txtPassword,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var head = iHead; // <-- copy to local (no longer ref)
|
||||
if (string.IsNullOrWhiteSpace(txtPassword))
|
||||
throw new Exception("LoginByPasswordAsync() - PASSWORD is empty.");
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
log.Debug($"LoginByPasswordAsync( Slot: {slotId}) - set password to: " + txtPassword.Substring(0, 4) + "************");
|
||||
var gciSetPasswordResult = await _bridge.SetPasswordAsync(slotId,txtPassword, token);
|
||||
log.Debug($"LoginByPasswordAsync( Slot: {slotId}) - SetPasswordAsync Result: " + gciSetPasswordResult);
|
||||
|
||||
// LOGIN
|
||||
log.Debug($"LoginByPasswordAsync( Slot: {slotId}) - START LOGIN");
|
||||
var gciSlotInfo = await _bridge.GetSlotAsync(slotId);
|
||||
log.Debug($"LoginByPasswordAsync( Checked before Login() Slot: {slotId}) - START LOGIN Slot: {gciSlotInfo}");
|
||||
GenesisCordonelInterface.API.PublicModels.GciLoginResult result = await _bridge.LoginAsync(slotId, token);
|
||||
log.Debug($"LoginByPasswordAsync( Slot: {slotId}) Result: " + result);
|
||||
log.Debug($"LoginByPasswordAsync( Slot: {slotId}) - END LOGIN, Success: {result.Success}");
|
||||
// ~ LOGIN
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ReadPcbResult> ReadRequest_PCBAsync( GenesisSmartReader head, bool bReload = false)
|
||||
{
|
||||
log.Debug("ReadRequest_PCB called for iHead: " + head);
|
||||
|
||||
if (head?.CommInterfaceBridge == null)
|
||||
return null;
|
||||
{
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = false,
|
||||
IsValidPcb = false,
|
||||
Message = "CommInterfaceBridge is null."
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var connectTask = Task.Run(async () =>
|
||||
await head.CommInterfaceBridge.ConnectAsync(head.GetSlotNr)
|
||||
);
|
||||
// CONNECT ONLY IF NEEDED
|
||||
var connectResult = await EnsureConnectedAsync(head);
|
||||
|
||||
var completedTask = Task.WhenAny(
|
||||
connectTask,
|
||||
Task.Delay(TimeSpan.FromMinutes(1))
|
||||
).GetAwaiter().GetResult();
|
||||
|
||||
if (completedTask != connectTask)
|
||||
if (!connectResult.IsConnected)
|
||||
{
|
||||
log.Debug("ReadRequest_PCB() - Timeout happened");
|
||||
return null;
|
||||
return connectResult;
|
||||
}
|
||||
|
||||
var result = connectTask.GetAwaiter().GetResult();
|
||||
log.Debug("ReadRequest_PCB() - Result: " + result);
|
||||
log.Debug($"ReadRequest_PCB() connect - {connectResult.Message}");
|
||||
|
||||
if (result == null || !result.Success || !result.IsConnected)
|
||||
return null;
|
||||
|
||||
var pcbId = result.PcbId;
|
||||
log.Debug("Result pcbId: " + pcbId);
|
||||
|
||||
if (!string.IsNullOrEmpty(pcbId) && head.ConfigStruct != null)
|
||||
//Check if exist PCB
|
||||
if (!bReload)
|
||||
{
|
||||
head.ConfigStruct.PCBNumberString = pcbId;
|
||||
log.Debug("Result set to ConfigStruct.PCBNumberString = " + head.ConfigStruct.PCBNumberString);
|
||||
var gciSlotInfo = await head.CommInterfaceBridge.GetSlotAsync(head.GetSlotNr);
|
||||
|
||||
if (gciSlotInfo == null && gciSlotInfo.Success && string.IsNullOrEmpty(gciSlotInfo.PcbId))
|
||||
{
|
||||
log.Debug(
|
||||
$"BuildConnection() - SlotNr: {head.GetSlotNr} already exist PCB: {gciSlotInfo.PcbId}");
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = gciSlotInfo.IsConnected,
|
||||
IsValidPcb = true,
|
||||
PcbId = gciSlotInfo.PcbId,
|
||||
Message = "PCB already exist."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return pcbId;
|
||||
// PCB READ LOOP
|
||||
string validPcbId = null;
|
||||
int maxAttempts = 5;
|
||||
DateTime startTime = DateTime.UtcNow;
|
||||
TimeSpan maxDuration = TimeSpan.FromSeconds(30);
|
||||
|
||||
//LOOP
|
||||
for (int attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
{
|
||||
if (DateTime.UtcNow - startTime > maxDuration)
|
||||
{
|
||||
log.Debug($"ReadRequest_PCB({head.GetSlotNr}) - PCB max duration exceeded");
|
||||
break;
|
||||
}
|
||||
|
||||
log.Debug($"ReadRequest_PCB({head.GetSlotNr}) - PCB attempt {attempt}/{maxAttempts}");
|
||||
var pcbTask = head.CommInterfaceBridge.GetPcbIdAsync(head.GetSlotNr);
|
||||
var timeoutTaskPcb = Task.Delay(TimeSpan.FromSeconds(5));
|
||||
var completedTaskPcb = await Task.WhenAny(pcbTask, timeoutTaskPcb);
|
||||
|
||||
if (completedTaskPcb != pcbTask)
|
||||
{
|
||||
log.Debug($"ReadRequest_PCB() PCB attempt {attempt} - Timeout");
|
||||
continue;
|
||||
}
|
||||
|
||||
var resultPCB = await pcbTask;
|
||||
|
||||
// VALIDATION BLOCK
|
||||
{
|
||||
if (resultPCB == null)
|
||||
{
|
||||
log.Debug($"ReadRequest_PCB({head.GetSlotNr}) PCB attempt {attempt} - result is null");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!resultPCB.Success)
|
||||
{
|
||||
log.Debug($"ReadRequest_PCB({head.GetSlotNr}) PCB attempt {attempt} - Success=false");
|
||||
continue;
|
||||
}
|
||||
|
||||
string pcbId = resultPCB.PcbId;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pcbId))
|
||||
{
|
||||
log.Debug($"ReadRequest_PCB({head.GetSlotNr}) PCB attempt {attempt} - PCB empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
pcbId = pcbId.Trim();
|
||||
|
||||
if (pcbId.Length != 9)
|
||||
{
|
||||
log.Debug( $"ReadRequest_PCB({head.GetSlotNr}) PCB attempt {attempt} - Invalid PCB length: '{pcbId}', len={pcbId.Length}");
|
||||
continue;
|
||||
}
|
||||
|
||||
validPcbId = pcbId;
|
||||
log.Debug($"ReadRequest_PCB({head.GetSlotNr}) PCB valid: {validPcbId}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(validPcbId))
|
||||
{
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = connectResult.IsConnected,
|
||||
IsValidPcb = false,
|
||||
PcbId = null,
|
||||
Message = "Valid PCB not found."
|
||||
};
|
||||
}
|
||||
|
||||
if (head.ConfigStruct != null)
|
||||
{
|
||||
head.ConfigStruct.PCBNumberString = validPcbId;
|
||||
}
|
||||
|
||||
return new ReadPcbResult
|
||||
{
|
||||
IsConnected = connectResult.IsConnected,
|
||||
IsValidPcb = true,
|
||||
PcbId = validPcbId,
|
||||
Message = "PCB OK"
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("ReadRequest_PCB() failed", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
log.Error("ReadRequest_PCBAsync() failed", ex);
|
||||
|
||||
public string ReadRequest_PCB2(ref GenesisSmartReader iHead)
|
||||
{
|
||||
return ReadRequest_PCBAsync(iHead).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public string ReadRequest_PCB1(ref GenesisSmartReader iHead)
|
||||
{
|
||||
log.Debug("ReadRequest_PCB called for iHead: " + iHead.ToString());
|
||||
if (iHead?.CommInterfaceBridge == null)
|
||||
return null;
|
||||
|
||||
// -- connection --
|
||||
var connectTask = iHead.CommInterfaceBridge.ConnectAsync(iHead.GetSlotNr);
|
||||
log.Debug("ReadRequest_PCB() - ConnectAsync created, Now waiting for result");
|
||||
|
||||
var completedTask = Task.WhenAny(
|
||||
connectTask,
|
||||
Task.Delay(TimeSpan.FromMinutes(1))
|
||||
).GetAwaiter().GetResult();
|
||||
|
||||
log.Debug("ReadRequest_PCB() - CompletedTask: " + completedTask);
|
||||
|
||||
// Timeout happened
|
||||
if (completedTask != connectTask)
|
||||
{
|
||||
log.Debug("ReadRequest_PCB() - Timeout happened");
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = connectTask.GetAwaiter().GetResult();
|
||||
log.Debug("ReadRequest_PCB() - ConnectAsync completed");
|
||||
|
||||
if (result == null || !result.Success || !result.IsConnected)
|
||||
return null;
|
||||
log.Debug("ReadRequest_PCB() - result: " + result);
|
||||
//~ -- connection --
|
||||
|
||||
string pcbId = result.PcbId;
|
||||
log.Debug("ReadRequest_PCB() - pcbId: " + pcbId);
|
||||
|
||||
if (!string.IsNullOrEmpty(pcbId))
|
||||
{
|
||||
if (iHead.ConfigStruct != null)
|
||||
return new ReadPcbResult
|
||||
{
|
||||
iHead.ConfigStruct.PCBNumberString = pcbId;
|
||||
log.Debug("ReadRequest_PCB() - iHead.ConfigStruct.PCBNumberString: " +
|
||||
iHead.ConfigStruct.PCBNumberString);
|
||||
IsConnected = false,
|
||||
IsValidPcb = false,
|
||||
PcbId = null,
|
||||
Message = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> PrepareLoginAdnConnect_Async(
|
||||
GenesisSmartReader iHead,
|
||||
bool isConnected,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
log.Debug("PrepareLoginAdnConnect_Async called for iHead: " + iHead + " isConnected: " + isConnected);
|
||||
|
||||
if (iHead?.CommInterfaceBridge == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var connectResult = await EnsureConnectedAsync(iHead, token);
|
||||
|
||||
if (!connectResult.IsConnected)
|
||||
return false;
|
||||
log.Debug($"PrepareLoginAdnConnect_Async() connect - {connectResult.Message}");
|
||||
|
||||
if (connectResult.IsLoggedOn)
|
||||
return true;
|
||||
|
||||
//get stored PCB - Keystone
|
||||
log.Debug("Have we PCB stored?");
|
||||
string pcb = iHead.ConfigStruct?.PCBNumberString;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pcb))
|
||||
{
|
||||
pcb = connectResult.PcbId;
|
||||
if (string.IsNullOrWhiteSpace(pcb))
|
||||
{
|
||||
pcb = iHead.SerialNr;
|
||||
}
|
||||
//GET PCB FROM Meter
|
||||
if (string.IsNullOrWhiteSpace(pcb))
|
||||
{
|
||||
log.Debug("PrepareLoginAdnConnect_Async() No PCB stored. Trying to get PCB from Meter");
|
||||
var pcbResult = await ReadRequest_PCBAsync(iHead);
|
||||
if (pcbResult.IsValidPcb)
|
||||
{
|
||||
pcb = pcbResult.PcbId;
|
||||
iHead.SerialNr = pcb;
|
||||
if (iHead.ConfigStruct != null)
|
||||
{
|
||||
iHead.ConfigStruct.PCBNumberString = pcb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pcbId;
|
||||
if (string.IsNullOrWhiteSpace(pcb))
|
||||
{
|
||||
log.Debug("PrepareLoginAdnConnect_Async() No PCB stored. MISSING PCB!!!!!");
|
||||
return false;
|
||||
}
|
||||
|
||||
log.Debug( $"PrepareLoginAdnConnect_Async() Start Find Keystone Slot: {iHead.GetSlotNr} PCB:{iHead.SerialNr} Calib PCB:{pcb} - find keystone");
|
||||
|
||||
var keyStoneResult = await FindKeyStone(pcb, token);
|
||||
|
||||
if (keyStoneResult == null || !keyStoneResult.Success)
|
||||
return false;
|
||||
|
||||
//LOGIN
|
||||
|
||||
log.Debug( $"PrepareLoginAdnConnect_Async() Start Login Slot: {iHead.GetSlotNr} PCB:{iHead.SerialNr} Calib PCB:{pcb} - login");
|
||||
GenesisCordonelInterface.API.PublicModels.GciLoginResult loginByPasswordAsync =
|
||||
await LoginByPasswordAsync(iHead.GetSlotNr, keyStoneResult.Password, token);
|
||||
|
||||
if (loginByPasswordAsync == null || !loginByPasswordAsync.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
log.Debug($"PrepareLoginAdnConnect_Async() Login OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
log.Debug("ReadRequest_PCB() - pcbId is empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
public ProtocolStatuses GetActivityStatusMode(GenesisSmartReader iHead)
|
||||
{
|
||||
if (iHead?.CommInterfaceBridge == null)
|
||||
return ProtocolStatuses.Unknown;
|
||||
|
||||
var connectResult = iHead.CommInterfaceBridge
|
||||
.ConnectAsync(iHead.GetSlotNr)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
if (connectResult == null || !connectResult.Success || !connectResult.IsConnected)
|
||||
return ProtocolStatuses.Unknown;
|
||||
|
||||
var pcbResult = iHead.CommInterfaceBridge
|
||||
.GetPcbIdAsync(iHead.GetSlotNr)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
if (pcbResult == null || !pcbResult.Success || string.IsNullOrWhiteSpace(pcbResult.PcbId))
|
||||
return ProtocolStatuses.Unknown;
|
||||
|
||||
if (iHead.ConfigStruct != null)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
iHead.ConfigStruct.PCBNumberString = pcbResult.PcbId;
|
||||
log.Debug("PrepareLoginAdnConnect_Async() canceled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return ProtocolStatuses.Active;
|
||||
}
|
||||
|
||||
public DiagnosticLedState SetOptoStatusMode(GenesisSmartReader iHead, DiagnosticLedState opthoStatusMode)
|
||||
public static readonly String LedMode = "GENESISFLOW_LedMode";
|
||||
public static readonly String SampleRate = "GENESISFLOW_SampleRate";
|
||||
public static readonly String CalFactor1 = "GENESISFLOW_CalFactor1";
|
||||
public static readonly String CalFactor2 = "GENESISFLOW_CalFactor2";
|
||||
public static readonly String CalFactor3 = "GENESISFLOW_CalFactor3";
|
||||
public static readonly String ResetAccumulators = "GENESISFLOW_ResetAccumulators";
|
||||
public static readonly String ForwardArrow = "GENESISFLOW_ForwardArrow";
|
||||
public static readonly String StoreCalibration = "GENESISFLOW_StoreCalibration";
|
||||
public static readonly String TriggerIdle = "GENESISFLOW_TriggerIdle";
|
||||
public static readonly String MeterSize = "GENESISFLOW_MeterSize";
|
||||
|
||||
public async Task<LedState> GetActivityLedStatusMode_Async(
|
||||
GenesisSmartReader iHead,
|
||||
bool isConnected,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
log.Debug("GetActivityLedStatusMode_Async called for iHead: " + iHead + " isConnected: " + isConnected);
|
||||
|
||||
if (iHead?.CommInterfaceBridge == null)
|
||||
return DiagnosticLedState.StatusUnknown;
|
||||
return LedState.Unknown;
|
||||
|
||||
var connectResult = iHead.CommInterfaceBridge
|
||||
.ConnectAsync(iHead.GetSlotNr)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
if (connectResult == null || !connectResult.Success || !connectResult.IsConnected)
|
||||
return DiagnosticLedState.StatusUnknown;
|
||||
|
||||
//SetDiagnosticLEDState
|
||||
|
||||
var pcbResult = iHead.CommInterfaceBridge
|
||||
.GetPcbIdAsync(iHead.GetSlotNr)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
if (pcbResult == null || !pcbResult.Success || string.IsNullOrWhiteSpace(pcbResult.PcbId))
|
||||
return DiagnosticLedState.StatusUnknown;
|
||||
|
||||
if (iHead.ConfigStruct != null)
|
||||
try
|
||||
{
|
||||
iHead.ConfigStruct.PCBNumberString = pcbResult.PcbId;
|
||||
var loginAdnConnectAsync = await PrepareLoginAdnConnect_Async(iHead, isConnected, token);
|
||||
|
||||
if (!loginAdnConnectAsync)
|
||||
return LedState.Unknown;
|
||||
|
||||
//Get Activity Status
|
||||
|
||||
var registerReadResult =
|
||||
await _bridge.ReadRegisterWithRetryAsync(iHead.GetSlotNr,LedMode, token);
|
||||
|
||||
if (registerReadResult == null || !registerReadResult.Success)
|
||||
{
|
||||
return LedState.Unknown;
|
||||
}
|
||||
log.Debug($"GetActivityStatusMode() Read Register OK Response: {registerReadResult}");
|
||||
try
|
||||
{
|
||||
byte[] bytes = HexFormatter.HexStringToByteArray(registerReadResult.Result.RawHex);
|
||||
//Convert byte array to int
|
||||
if (bytes == null || bytes.Length < 4)
|
||||
{
|
||||
log.Debug("Invalid byte array length");
|
||||
return LedState.Unknown;
|
||||
}
|
||||
|
||||
int value = BitConverter.ToInt32(bytes, 0);
|
||||
|
||||
// continue your real status logic here...
|
||||
return value == 6 ? LedState.active : LedState.inactive;
|
||||
}catch(Exception ex)
|
||||
{
|
||||
log.Error("GetActivityStatusMode() failed", ex);
|
||||
return LedState.Unknown;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
log.Debug("GetActivityLedStatusMode_Async() canceled.");
|
||||
return LedState.Unknown;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("GetActivityLedStatusMode_Async() failed", ex);
|
||||
return LedState.Unknown;
|
||||
}
|
||||
|
||||
|
||||
return DiagnosticLedState.StatusUnknown;
|
||||
}
|
||||
|
||||
public async Task<bool> SetLedMode_Async(GenesisSmartReader iHead, LedState ledMode, bool isConnected,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
log.Debug("SetLedMode_Async called for iHead: " + iHead + " isConnected: " + isConnected);
|
||||
if (iHead?.CommInterfaceBridge == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var loginAdnConnectAsync = await PrepareLoginAdnConnect_Async(iHead, isConnected, token);
|
||||
|
||||
if (!loginAdnConnectAsync)
|
||||
return false;
|
||||
|
||||
//Get Activity Status
|
||||
byte valueLed = (ledMode == LedState.active) ? (byte)6 : (byte)0;
|
||||
log.Debug($"SetLedMode_Async() Set Led Mode: {valueLed}");
|
||||
var registerWriteResult =
|
||||
await _bridge.WriteRegisterAsync(iHead.GetSlotNr, LedMode, valueLed, false, false, token);
|
||||
|
||||
if (registerWriteResult == null || !registerWriteResult.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
log.Debug($"SetLedMode_Async() Write Register OK Response: {registerWriteResult}");
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
log.Debug("GetActivityLedStatusMode_Async() canceled.");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("GetActivityLedStatusMode_Async() failed", ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
log.Debug("SetLedMode_Async() - End - DO DISCONNECT");
|
||||
var gciDisconnectResult = await _bridge.DisconnectAsync(iHead.GetSlotNr);
|
||||
log.Debug($"SetLedMode_Async() Disconnect Result: {gciDisconnectResult}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Disconnect_Async(GenesisSmartReader iHead)
|
||||
{
|
||||
try
|
||||
{
|
||||
log.Debug("Disconnect_Async() - Start");
|
||||
var gciSlotInfo = await _bridge.GetSlotAsync(iHead.GetSlotNr);
|
||||
log.Debug($"Disconnect_Async() - Slot: {iHead.GetSlotNr} - SlotInfo: {gciSlotInfo}");
|
||||
log.Debug("Disconnect_Async() - DO DISCONNECT");
|
||||
var gciDisconnectResult = await _bridge.DisconnectAsync(iHead.GetSlotNr);
|
||||
log.Debug($"Disconnect_Async() Disconnect Result: {gciDisconnectResult}");
|
||||
|
||||
return gciDisconnectResult.Success;
|
||||
}catch(Exception ex)
|
||||
{
|
||||
log.Error("Disconnect_Async() failed", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static ushort SafeIntToUShort(int value)
|
||||
{
|
||||
@@ -316,29 +563,41 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
}
|
||||
|
||||
|
||||
public bool SetActivityMode_Active(GenesisSmartReader iHead)
|
||||
public async Task<bool> SetActivityMode_Active(GenesisSmartReader iHead,
|
||||
bool isConnected, CancellationToken token = default)
|
||||
{
|
||||
log.Debug("SetActivityMode_Active called for iHead: " + iHead + " isConnected: " + isConnected);
|
||||
|
||||
if (iHead?.CommInterfaceBridge == null)
|
||||
return false;
|
||||
|
||||
var connectResult = iHead.CommInterfaceBridge
|
||||
.ConnectAsync(iHead.GetSlotNr)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
try
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (connectResult == null || !connectResult.Success || !connectResult.IsConnected)
|
||||
var connectResult = await EnsureConnectedAsync(iHead, token);
|
||||
|
||||
if (!connectResult.IsConnected)
|
||||
return false;
|
||||
|
||||
isConnected = connectResult.IsConnected;
|
||||
|
||||
log.Debug($"SetActivityMode_Active() connect - {connectResult.Message}");
|
||||
|
||||
//Set LED to state 4
|
||||
// string version = _bridge?.GciExternalInterface?.GetPcbId(iHead.GetSlotNr);
|
||||
// if (!string.IsNullOrEmpty(version))
|
||||
// {
|
||||
// if (iHead.ConfigStruct != null) // store mechanism
|
||||
// {
|
||||
// iHead.ConfigStruct.Version = version;
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
}catch(OperationCanceledException)
|
||||
{
|
||||
return false;
|
||||
|
||||
//Set LED to state 4
|
||||
// string version = _bridge?.GciExternalInterface?.GetPcbId(iHead.GetSlotNr);
|
||||
// if (!string.IsNullOrEmpty(version))
|
||||
// {
|
||||
// if (iHead.ConfigStruct != null) // store mechanism
|
||||
// {
|
||||
// iHead.ConfigStruct.Version = version;
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.common
|
||||
{
|
||||
public enum LedState : int
|
||||
{
|
||||
Unknown = -1,
|
||||
inactive = 0,
|
||||
active = 1,
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if(Head != null && Head.OptoHeadTest != null)
|
||||
{
|
||||
Head.OptoHeadTest.CloseConnection();
|
||||
}
|
||||
|
||||
stopWorkerThread = true;
|
||||
if (optoThread != null)
|
||||
{
|
||||
|
||||
@@ -216,6 +216,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
|
||||
|
||||
public int ChannelsCount { get => iChanelsCount; }
|
||||
|
||||
private static int iChanelsCount = 3;
|
||||
private int firstChanel;
|
||||
|
||||
@@ -1339,16 +1341,6 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
void DataStreamPostProcessing()
|
||||
{
|
||||
PrepareCalculatedChannelData();
|
||||
try
|
||||
{
|
||||
log.DebugFormat("DataStreamPostProcessing() - harcoded call GetQ3Calibration(200.0, 120.0, 15625.0);");
|
||||
SetQ3Calibration(new double[]{15625.0,15625.0,15625.0 });
|
||||
CalculateQ3Calibration(200.0, 120.0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"DataStreamPostProcessing -- Q3 CALIBRATION -- failed: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1882,7 +1874,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
|
||||
_readLoopTask = Task.Run(() =>
|
||||
{
|
||||
log.Debug($"OPTHO {OptoComPortNr} background read loop started.");
|
||||
logStream.Debug($"OPTHO {OptoComPortNr} background read loop started.");
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
@@ -1938,12 +1930,12 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"OPTHO {OptoComPortNr} background read error: {ex.Message}");
|
||||
logStream.Error($"OPTHO {OptoComPortNr} background read error: {ex.Message}");
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug($"OPTHO {OptoComPortNr} background read loop stopped.");
|
||||
logStream.Debug($"OPTHO {OptoComPortNr} background read loop stopped.");
|
||||
}, token);
|
||||
}
|
||||
|
||||
@@ -2024,7 +2016,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
{
|
||||
try
|
||||
{
|
||||
log.Debug($"OPTHO {OptoComPortNr} processing loop started.");
|
||||
logStream.Debug($"OPTHO {OptoComPortNr} processing loop started.");
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
@@ -2054,14 +2046,14 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
|
||||
if (blockCompleted)
|
||||
{
|
||||
log.Debug("Processing loop completed flow block detected.");
|
||||
logStream.Debug("Processing loop completed flow block detected.");
|
||||
if (resetSerialBuffersOnCompletedFlowBlock)
|
||||
ResetDataBuffer();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"Processing loop failed: {ex}");
|
||||
logStream.Error($"Processing loop failed: {ex}");
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -2075,16 +2067,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"OPTHO {OptoComPortNr} processing loop error: {ex}");
|
||||
logStream.Error($"OPTHO {OptoComPortNr} processing loop error: {ex}");
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug($"OPTHO {OptoComPortNr} processing loop stopped.");
|
||||
logStream.Debug($"OPTHO {OptoComPortNr} processing loop stopped.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"StartProcessingLoop fatal error: {ex}");
|
||||
logStream.Error($"StartProcessingLoop fatal error: {ex}");
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
@@ -2113,13 +2105,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
// 🔴 STEP 1: Check if we should start processing
|
||||
if (startDataProcessing && optoState == DataStreamState.ProcessAndSave)
|
||||
{
|
||||
log.DebugFormat("Read Opto Data Line timestamp:{0} to process from queue: {1}",timestamp.ToString("HH:mm:ss.fff") , line);
|
||||
logStream.DebugFormat("Read Opto Data Line timestamp:{0} to process from queue: {1}",timestamp.ToString("HH:mm:ss.fff") , line);
|
||||
bool blockCompleted;
|
||||
ProcessOptoLine(line, optoState, out blockCompleted);
|
||||
|
||||
if (blockCompleted)
|
||||
{
|
||||
log.Debug("ReadOptoData() completed flow block detected.");
|
||||
logStream.Debug("ReadOptoData() completed flow block detected.");
|
||||
if (resetSerialBuffersOnCompletedFlowBlock) // DO NOT call ResetDataBuffer() here
|
||||
ResetDataBuffer();
|
||||
}
|
||||
@@ -2127,7 +2119,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"OPTHO {OptoComPortNr} processing queued line failed: {ex.Message}");
|
||||
logStream.Error($"OPTHO {OptoComPortNr} processing queued line failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2154,14 +2146,14 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
if (streamingDecode.DataFlowTest != null && streamingDecode.DataFlowTest.IsValid)
|
||||
{
|
||||
blockCompleted = HandleFlowMarker();
|
||||
log.Debug("ComPort: " + OptoComPortNr + " Decoded Flow data: " + streamingDecode.DataFlowTest +
|
||||
" OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
|
||||
logStream.Debug("ComPort: " + OptoComPortNr + " Decoded Flow data: " + streamingDecode.DataFlowTest +
|
||||
" OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
|
||||
}
|
||||
|
||||
if (calibData != null && calibData.IsValid)
|
||||
{
|
||||
log.Debug("ComPort: " + OptoComPortNr + " Decoded Calib: " + calibData + " OPTHO RX ← " +
|
||||
HexFormatter.ToSerialHex(bytes));
|
||||
logStream.Debug("ComPort: " + OptoComPortNr + " Decoded Calib: " + calibData + " OPTHO RX ← " +
|
||||
HexFormatter.ToSerialHex(bytes));
|
||||
|
||||
MarkCalibrationChannelSeen(calibData.Channel);
|
||||
}
|
||||
@@ -2181,7 +2173,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
|
||||
if (optoData[bufferIx] == null)
|
||||
{
|
||||
log.ErrorFormat("{0}: optoData[{1}] was null, recreating.", Name, bufferIx);
|
||||
logStream.ErrorFormat("{0}: optoData[{1}] was null, recreating.", Name, bufferIx);
|
||||
optoData[bufferIx] = new OptoTelegramRaw();
|
||||
}
|
||||
|
||||
@@ -2194,7 +2186,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
int iChanel = calibData.Channel - 1;
|
||||
if (iChanel >= 0 && iChanel < iChanelsCount)
|
||||
{
|
||||
log.Debug(
|
||||
logStream.Debug(
|
||||
$"Before UpdateFromSmart ch={iChanel + 1}: " +
|
||||
$"volumeRawExtLast={volumeRawExtLast[iChanel]}, " +
|
||||
$"timestampExtLast={timestampExtLast[iChanel]}, " +
|
||||
@@ -2262,7 +2254,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
var encoding = optoSerialPort?.Encoding ?? Encoding.ASCII;
|
||||
byte[] bytes = encoding.GetBytes(line);
|
||||
received = HexFormatter.ToSerialHex(bytes);
|
||||
log.Debug("RX ← " + received);
|
||||
logStream.Debug("RX ← " + received);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -2271,19 +2263,19 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
CalibrationRecord data = _streamingDecode.DataCalib;
|
||||
if (data != null && data.IsValid)
|
||||
{
|
||||
log.Info($"OPTHO {OptoComPortNr} DataCalib Parsed opto data: " + data + " RX ← " +
|
||||
received);
|
||||
logStream.Info($"OPTHO {OptoComPortNr} DataCalib Parsed opto data: " + data + " RX ← " +
|
||||
received);
|
||||
MarkCalibrationChannelSeen(data.Channel);
|
||||
}
|
||||
|
||||
FlowTestRecord dataFlow = _streamingDecode.DataFlowTest;
|
||||
if (dataFlow != null && dataFlow.IsValid)
|
||||
{
|
||||
log.Info($"OPTHO {OptoComPortNr} FLOW Parsed opto data: " + dataFlow + " RX ← " + received);
|
||||
logStream.Info($"OPTHO {OptoComPortNr} FLOW Parsed opto data: " + dataFlow + " RX ← " + received);
|
||||
|
||||
if (HandleFlowMarker())
|
||||
{
|
||||
log.Debug("ReadOptoData() completed flow block detected.");
|
||||
logStream.Debug("ReadOptoData() completed flow block detected.");
|
||||
if (resetSerialBuffersOnCompletedFlowBlock)
|
||||
ResetDataBuffer(); // no ResetDataBuffer() here
|
||||
}
|
||||
@@ -2292,7 +2284,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"OPTHO {OptoComPortNr} Read error: {ex.Message}");
|
||||
logStream.Error($"OPTHO {OptoComPortNr} Read error: {ex.Message}");
|
||||
}
|
||||
|
||||
// string line = optoSerialPort.ReadExisting();
|
||||
@@ -2337,11 +2329,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} timeout - continuing.");
|
||||
logStream.Debug($"ReadOptoData() OPTHO {OptoComPortNr} timeout - continuing.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}");
|
||||
logStream.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2357,11 +2349,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
{
|
||||
optoSerialPort.DiscardInBuffer();
|
||||
optoSerialPort.DiscardOutBuffer();
|
||||
log.Debug("-- Reaset Data Buffer --");
|
||||
logStream.Debug("-- Reaset Data Buffer --");
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.Debug("-- Reaset Data Buffer - no serial port --");
|
||||
logStream.Debug("-- Reaset Data Buffer - no serial port --");
|
||||
}
|
||||
|
||||
void ISmartReader.SetNfcInterface()
|
||||
@@ -2387,17 +2379,17 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
byte[] bytes = optoSerialPort.Encoding.GetBytes(line);
|
||||
string received = HexFormatter.ToSerialHex(bytes);
|
||||
|
||||
log.Debug("RX ← " + received);
|
||||
logStream.Debug("RX ← " + received);
|
||||
return line;
|
||||
}
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing.");
|
||||
logStream.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}");
|
||||
logStream.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}");
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
@@ -2408,7 +2400,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
if (completedTask == readTask)
|
||||
return await readTask;
|
||||
|
||||
log.Debug("ReadOptoData timeout after " + timeoutMs + " ms");
|
||||
logStream.Debug("ReadOptoData timeout after " + timeoutMs + " ms");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
@@ -2960,7 +2952,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
if (data == null || !data.IsValid)
|
||||
continue;
|
||||
|
||||
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
|
||||
logStream.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
|
||||
|
||||
int dch = data.Channel - 1;
|
||||
if (dch >= 0 && dch < iChanelsCount)
|
||||
@@ -2973,7 +2965,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Warn($"Parsing DiagnosticLedState4Data: {ex.Message}");
|
||||
logStream.Warn($"Parsing DiagnosticLedState4Data: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2984,7 +2976,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
|
||||
|
||||
log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr}");
|
||||
logStream.Debug($"Try get End Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr}");
|
||||
if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
|
||||
|
||||
if (!Double.IsNaN(volumeLtr[ch]))
|
||||
@@ -2995,12 +2987,12 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
//Solve roll over
|
||||
if (endWMState < beginWMState)
|
||||
{
|
||||
log.Debug($"Solve roll over! endWMState: {endWMState} < beginWMState: {beginWMState}");
|
||||
logStream.Debug($"Solve roll over! endWMState: {endWMState} < beginWMState: {beginWMState}");
|
||||
const double VOL_RANGE_LITERS = 16777216.0 * 0.00025; // 4,194.304 l
|
||||
endWMState += VOL_RANGE_LITERS;
|
||||
volumeLtr[ch] = endWMState;
|
||||
ReadPulses();
|
||||
log.Debug(
|
||||
logStream.Debug(
|
||||
$"Solve roll over! Upgraded endWMState: {endWMState}, beginWMState: {beginWMState}");
|
||||
}
|
||||
}
|
||||
@@ -3009,7 +3001,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
//}
|
||||
|
||||
log.Warn("Default NaN value returned! Data Opto stream reading failed!");
|
||||
logStream.Warn("Default NaN value returned! Data Opto stream reading failed!");
|
||||
return Double.NaN;
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
@@ -3019,14 +3011,14 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
{
|
||||
if (ConfigStruct == null)
|
||||
{
|
||||
log.Debug("ConfigStruct is null - created new in ReadSerialNr()");
|
||||
logStream.Debug("ConfigStruct is null - created new in ReadSerialNr()");
|
||||
ConfigStruct = new ConfigStruct();
|
||||
}
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
|
||||
log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}");
|
||||
logStream.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}");
|
||||
|
||||
Start();
|
||||
|
||||
@@ -3050,7 +3042,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
StreamingDecoder _streamingDecode = new StreamingDecoder(true);
|
||||
_streamingDecode.DecodeMsg(readOptoDataWithTimeout);
|
||||
CalibrationRecord data = _streamingDecode.DataCalib;
|
||||
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
|
||||
logStream.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
|
||||
if (data == null || !data.IsValid)
|
||||
continue;
|
||||
|
||||
@@ -3066,7 +3058,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Warn($"Parsing DiagnosticLedState4Data: {ex.Message}");
|
||||
logStream.Warn($"Parsing DiagnosticLedState4Data: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3076,7 +3068,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr0}");
|
||||
logStream.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr0}");
|
||||
if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
|
||||
|
||||
if (!Double.IsNaN(volumeLtr0[ch]))
|
||||
@@ -3087,7 +3079,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
//}
|
||||
|
||||
log.Warn("Default NaN value returned! Data Opto stream reading failed!");
|
||||
logStream.Warn("Default NaN value returned! Data Opto stream reading failed!");
|
||||
return Double.NaN;
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
@@ -3099,7 +3091,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
|
||||
if (ConfigStruct == null)
|
||||
{
|
||||
log.Debug("ConfigStruct is null - created new in ReadSerialNr()");
|
||||
logStream.Debug("ConfigStruct is null - created new in ReadSerialNr()");
|
||||
ConfigStruct = new ConfigStruct();
|
||||
}
|
||||
|
||||
@@ -3109,11 +3101,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
|
||||
log.Debug($"Try get ReadSerialNr! COM: {this.RfidComPortNr}");
|
||||
logStream.Debug($"Try get ReadSerialNr! COM: {this.RfidComPortNr}");
|
||||
SerialNr = OptoHeadTest.ReadRequest_PCB();
|
||||
if (string.IsNullOrEmpty(SerialNr))
|
||||
{
|
||||
log.Debug("ReadSerialNr successful");
|
||||
logStream.Debug("ReadSerialNr successful");
|
||||
}
|
||||
|
||||
//optoHeadTest.CloseConnection();
|
||||
@@ -3898,7 +3890,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
_startupFlushActive = false;
|
||||
|
||||
log.WarnFormat(
|
||||
"Startup flush finished. Ignored {0} incoming opto lines.",
|
||||
"Startup flush finished. ({0}) Ignored {1} incoming opto lines.",
|
||||
Name,
|
||||
_startupFlushIgnoredLines);
|
||||
}
|
||||
}
|
||||
@@ -3915,7 +3908,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
_startupFlushActive = false;
|
||||
|
||||
log.WarnFormat(
|
||||
"Startup flush finished. Ignored {0} incoming opto lines. Window: {1:HH:mm:ss.fff} - {2:HH:mm:ss.fff}",
|
||||
"Startup flush finished. ({0}) Ignored {1} incoming opto lines. Window: {2:HH:mm:ss.fff} - {3:HH:mm:ss.fff}",
|
||||
Name,
|
||||
_startupFlushIgnoredLines,
|
||||
_startupFlushFirstIgnoredUtc,
|
||||
_startupFlushLastIgnoredUtc);
|
||||
@@ -3934,9 +3928,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>Compatibility limit used when the Write Q3 Calibration activity has no error limits configured.</summary>
|
||||
public const double DefaultQ3CalibrationFactorErrorLimit = 5.0;
|
||||
|
||||
private double[] q3CalibInitial = {Double.NaN,Double.NaN,Double.NaN};
|
||||
private bool[] isChQ3CalibValid = { false,false,false};
|
||||
private double[] q3CalibCh = {Double.NaN,Double.NaN,Double.NaN};
|
||||
private double[] q3DiffPercentageCalibCh = {Double.NaN,Double.NaN,Double.NaN};
|
||||
|
||||
|
||||
public bool Q3CalibValid
|
||||
@@ -3954,6 +3952,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
|
||||
public double[] Q3CalibValue { get => q3CalibInitial; }
|
||||
public double[] Q3CalibDiffPercentageValue { get => q3DiffPercentageCalibCh; }
|
||||
|
||||
public bool Q3Calib_Ch1Valid { get => isChQ3CalibValid[0]; }
|
||||
public bool Q3Calib_Ch2Valid { get => isChQ3CalibValid[1]; }
|
||||
@@ -3964,14 +3963,100 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
|
||||
public int GetSlotNr { get => genesisHeadCfg?.SlotNr ?? -1; }
|
||||
|
||||
void SetQ3Calibration(double[] q3CalibInitial) { this.q3CalibInitial = q3CalibInitial; }
|
||||
//TODO BUMI implement variable values for Q3Channel!
|
||||
public void SetQ3Calibration(double[] q3CalibInitial)
|
||||
{
|
||||
if (q3CalibInitial == null || q3CalibInitial.Length != 3 ||
|
||||
Array.Exists(q3CalibInitial, x => double.IsNaN(x) || double.IsInfinity(x) || x < 1 || x > ushort.MaxValue))
|
||||
throw new ArgumentException("Three valid Genesis calibration factors are required.", nameof(q3CalibInitial));
|
||||
this.q3CalibInitial = (double[])q3CalibInitial.Clone();
|
||||
refVolume = double.NaN;
|
||||
refTime = double.NaN;
|
||||
Array.Clear(isChQ3CalibValid, 0, isChQ3CalibValid.Length);
|
||||
}
|
||||
|
||||
private double refVolume = double.NaN;
|
||||
private double refTime = double.NaN;
|
||||
public double RefVolume { get => refVolume; set => refVolume = value; }
|
||||
public double RefTime { get => refTime; set => refTime = value; }
|
||||
|
||||
|
||||
public bool CalculateQ3Calibration()
|
||||
{
|
||||
return CalculateQ3CalibrationWithErrorLimits(-DefaultQ3CalibrationFactorErrorLimit,
|
||||
DefaultQ3CalibrationFactorErrorLimit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates Q3 factors using the limits configured on the procedure activity that writes them.
|
||||
/// A pair for which the high limit is not greater than the low limit (the normal unset 0 / 0
|
||||
/// value) falls back to the historical +/- 5 % validation.
|
||||
/// </summary>
|
||||
public bool CalculateQ3CalibrationWithErrorLimits(double errorLimitLo, double errorLimitHi)
|
||||
{
|
||||
if (Double.IsNaN(refVolume) || Double.IsInfinity(refVolume) || refVolume <= 0 || Double.IsNaN(refTime) || Double.IsInfinity(refTime) || refTime <= 0)
|
||||
{
|
||||
log.Debug("RefVolume or RefTime is NaN");
|
||||
return false;
|
||||
}
|
||||
|
||||
CalculateQ3Calibration(refVolume, refTime, errorLimitLo, errorLimitHi);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CalculateQ3Calibration(double refVolume, double refTime)
|
||||
{
|
||||
GetQ3Calibration(refVolume, refTime, q3CalibInitial, ref isChQ3CalibValid, ref q3CalibCh);
|
||||
CalculateQ3Calibration(refVolume, refTime, -DefaultQ3CalibrationFactorErrorLimit,
|
||||
DefaultQ3CalibrationFactorErrorLimit);
|
||||
}
|
||||
|
||||
public void CalculateQ3Calibration(double refVolume, double refTime, double errorLimitLo, double errorLimitHi)
|
||||
{
|
||||
double effectiveErrorLimitLo;
|
||||
double effectiveErrorLimitHi;
|
||||
TryGetEffectiveQ3CalibrationErrorLimits(errorLimitLo, errorLimitHi,
|
||||
out effectiveErrorLimitLo, out effectiveErrorLimitHi);
|
||||
GetQ3Calibration(refVolume, refTime, q3CalibInitial, effectiveErrorLimitLo,
|
||||
effectiveErrorLimitHi, ref isChQ3CalibValid, ref q3DiffPercentageCalibCh, ref q3CalibCh);
|
||||
}
|
||||
|
||||
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor, ref bool[] isChQ3CalibValid, ref double[] q3CalibCh)
|
||||
{
|
||||
var differences = new double[3];
|
||||
GetQ3Calibration(refVolume, refTime, initCalibFactor, ref isChQ3CalibValid, ref differences, ref q3CalibCh);
|
||||
}
|
||||
|
||||
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor, ref bool[] isChQ3CalibValid, ref double[] calibDiffPercent, ref double[] q3CalibCh)
|
||||
{
|
||||
GetQ3Calibration(refVolume, refTime, initCalibFactor,
|
||||
-DefaultQ3CalibrationFactorErrorLimit, DefaultQ3CalibrationFactorErrorLimit,
|
||||
ref isChQ3CalibValid, ref calibDiffPercent, ref q3CalibCh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the error limits used to validate a calculated Q3 factor. TBF stores an unset
|
||||
/// limit pair as equal values (normally 0 / 0), so only an ordered finite pair is considered set.
|
||||
/// </summary>
|
||||
public static bool TryGetEffectiveQ3CalibrationErrorLimits(double errorLimitLo, double errorLimitHi,
|
||||
out double effectiveErrorLimitLo, out double effectiveErrorLimitHi)
|
||||
{
|
||||
if (!Double.IsNaN(errorLimitLo) && !Double.IsInfinity(errorLimitLo) &&
|
||||
!Double.IsNaN(errorLimitHi) && !Double.IsInfinity(errorLimitHi) &&
|
||||
errorLimitHi > errorLimitLo)
|
||||
{
|
||||
effectiveErrorLimitLo = errorLimitLo;
|
||||
effectiveErrorLimitHi = errorLimitHi;
|
||||
return true;
|
||||
}
|
||||
|
||||
effectiveErrorLimitLo = -DefaultQ3CalibrationFactorErrorLimit;
|
||||
effectiveErrorLimitHi = DefaultQ3CalibrationFactorErrorLimit;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor,
|
||||
double errorLimitLo, double errorLimitHi, ref bool[] isChQ3CalibValid,
|
||||
ref double[] calibDiffPercent, ref double[] q3CalibCh)
|
||||
{
|
||||
log.Debug("=== Q3 CALIBRATION START ===");
|
||||
|
||||
@@ -3991,7 +4076,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug($"Inputs: refVolume={refVolume}, refTime={refTime}, initCalibFactor={initCalibFactor}");
|
||||
log.Debug($"Inputs: refVolume={refVolume}, refTime={refTime}, initCalibFactors={string.Join(",", initCalibFactor)}, errorLimits={errorLimitLo}..{errorLimitHi}%");
|
||||
|
||||
if (_rawStartEndByChannel == null)
|
||||
{
|
||||
@@ -4080,26 +4165,20 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
}
|
||||
|
||||
q3CalibCh[iChannel] = (refVolume / recalculatedDeltaVolume) * initCalibFactor[iChannel];
|
||||
double diffPercent = Math.Abs((initCalibFactor[iChannel] - q3CalibCh[iChannel] ) / initCalibFactor[iChannel]) * 100.0;
|
||||
isChQ3CalibValid[iChannel] = diffPercent <= 5.0;
|
||||
log.Debug($"Calculated Q3Calib Ch[{iChannel}] ={q3CalibCh[iChannel]} DiffPercent={diffPercent}% isValid[{isChQ3CalibValid[iChannel]}] IninitCalibFactor={initCalibFactor}");
|
||||
|
||||
// Procedure error limits are an ordered range, e.g. -2 .. +2, so validation
|
||||
// must retain the direction of the factor change. Keep the persisted difference
|
||||
// absolute for compatibility with the existing calibration-result fields.
|
||||
double signedDiffPercent = ((q3CalibCh[iChannel] - initCalibFactor[iChannel]) / initCalibFactor[iChannel]) * 100.0;
|
||||
double diffPercent = Math.Abs(signedDiffPercent);
|
||||
isChQ3CalibValid[iChannel] = signedDiffPercent >= errorLimitLo && signedDiffPercent <= errorLimitHi &&
|
||||
!double.IsNaN(q3CalibCh[iChannel]) && !double.IsInfinity(q3CalibCh[iChannel]) &&
|
||||
q3CalibCh[iChannel] >= 1 && q3CalibCh[iChannel] <= ushort.MaxValue;
|
||||
calibDiffPercent[iChannel] = diffPercent;
|
||||
log.Debug($"Calculated Q3Calib Ch[{iChannel}] ={q3CalibCh[iChannel]} SignedDiffPercent={signedDiffPercent}% DiffPercent={diffPercent}% isValid[{isChQ3CalibValid[iChannel]}] IninitCalibFactor={initCalibFactor}");
|
||||
}
|
||||
|
||||
log.Debug("=== Q3 CALIBRATION END ===");
|
||||
}
|
||||
|
||||
|
||||
void newPokus()
|
||||
{
|
||||
//TODO BUMI implement genesis communication
|
||||
//volat z GCI Bridge
|
||||
|
||||
//vybere sa component - GCI bridge
|
||||
// - rozhranie
|
||||
// - database
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
WorkingDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName)),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
@@ -259,18 +258,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
info.ExitCode = process.ExitCode;
|
||||
info.StandardOutput = stdOutTask.Result ?? "";
|
||||
info.StandardError = stdErrTask.Result ?? "";
|
||||
string allOutput = info.StandardOutput + info.StandardError;
|
||||
string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
|
||||
log?.Debug(allOutput);
|
||||
|
||||
if (info.ExitCode != 0)
|
||||
{
|
||||
info.FailureReason = $"CLI exited with exit code {info.ExitCode}.";
|
||||
log?.Error($"{info.Name}: {info.FailureReason} stderr='{info.StandardError}'");
|
||||
}
|
||||
|
||||
info.State = CliTaskState.Completed;
|
||||
return allOutput;
|
||||
}
|
||||
@@ -300,7 +290,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
public void AddRunAndCaptureJsonAsync<T>(string fileName, string args) where T : new()
|
||||
{
|
||||
log?.Debug($"CLI queued: file='{fileName}', args='{args}'");
|
||||
ResetStartTime();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
@@ -322,7 +311,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
WorkingDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName)),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
@@ -335,7 +323,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
try
|
||||
{
|
||||
log?.Debug($"CLI starting: file='{psi.FileName}', args='{psi.Arguments}', workingDirectory='{psi.WorkingDirectory}', exists={System.IO.File.Exists(psi.FileName)}");
|
||||
process.Start();
|
||||
|
||||
var stdoutTask = process.StandardOutput.ReadToEndAsync();
|
||||
@@ -360,20 +347,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
info.ExitCode = process.ExitCode;
|
||||
info.StandardOutput = stdoutTask.Result ?? "";
|
||||
info.StandardError = stderrTask.Result ?? "";
|
||||
string allOutput = info.StandardOutput + info.StandardError;
|
||||
string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
|
||||
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);
|
||||
T result;
|
||||
@@ -383,8 +358,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
return result;
|
||||
}
|
||||
|
||||
info.FailureReason = "CLI completed without a valid JSON response.";
|
||||
log?.Error($"{info.Name}: {info.FailureReason} stdout='{info.StandardOutput}' stderr='{info.StandardError}'");
|
||||
info.State = CliTaskState.Completed;
|
||||
return default(T);
|
||||
}
|
||||
@@ -407,17 +380,18 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
public bool TryJsonStringDeserialize<T>(string json, out T value) where T : new()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
if (json != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = JsonConvert.DeserializeObject<T>(json);
|
||||
return value != null;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Debug(ex.Message);
|
||||
return TryConvert(json, out value);
|
||||
value = TryConvert<T>(json);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +399,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryConvert<T>(string json, out T value) where T : new()
|
||||
private static T TryConvert<T>(string json) where T : new()
|
||||
{
|
||||
T obj = new T();
|
||||
|
||||
@@ -443,24 +417,21 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
try
|
||||
{
|
||||
object propertyValue = token.ToObject(prop.PropertyType);
|
||||
prop.SetValue(obj, propertyValue);
|
||||
object value = token.ToObject(prop.PropertyType);
|
||||
prop.SetValue(obj, value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = obj;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"TryConvert failed: {ex.Message}");
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public string ExtractJson(string text)
|
||||
@@ -491,4 +462,4 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
public Process Process { get; set; }
|
||||
public CliTaskState State { get; set; } = CliTaskState.Running;
|
||||
public string Name { get; set; }
|
||||
public int? ExitCode { get; set; }
|
||||
public string StandardOutput { get; set; }
|
||||
public string StandardError { get; set; }
|
||||
public string FailureReason { get; set; }
|
||||
|
||||
public bool UseResult
|
||||
{
|
||||
@@ -26,4 +22,4 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
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,7 +3,6 @@
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -25,8 +24,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
public class PoseidonReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, ICommonRegReader
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(PoseidonReader));
|
||||
// Simulation must never call the CLI configured for the physical Hat.
|
||||
internal const string SimulatedCliFileName = "cmdSleepTest.exe";
|
||||
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
||||
|
||||
readonly PoseidonCfg registerReaderCfg;
|
||||
@@ -65,19 +62,12 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
CurrentPoseidonOp _currentOp;
|
||||
private bool _isReadingStart = true;
|
||||
private bool? _isCliLogging;
|
||||
private bool lastCliReadingParsed;
|
||||
private string lastCliReadFailureReason;
|
||||
private bool lastCliReadSucceeded;
|
||||
|
||||
public CurrentPoseidonOp 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)
|
||||
{
|
||||
_currentOp = operation ;
|
||||
@@ -296,25 +286,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
if ((DebugLevel == DebugMode.Normal)||(DebugLevel == DebugMode.Simulate))
|
||||
{
|
||||
/// 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),
|
||||
cliFileName,
|
||||
registerReaderCfg.MeterType);
|
||||
|
||||
if (DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
log.WarnFormat(
|
||||
"{0}: simulation mode enabled; overriding configured CLI '{1}' with '{2}'.",
|
||||
Name, registerReaderCfg.CliFileName, serialPort.SerialPortCmdClientPath);
|
||||
}
|
||||
registerReaderCfg.CliFileName,
|
||||
registerReaderCfg.MeterType);
|
||||
|
||||
|
||||
//TODO BUMI prepare serial port - for us do nothing
|
||||
@@ -337,13 +311,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetCliFileNameForMode(DebugMode debugMode, string configuredCliFileName)
|
||||
{
|
||||
return debugMode == DebugMode.Simulate
|
||||
? SimulatedCliFileName
|
||||
: configuredCliFileName;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
log.DebugFormat("{0}:Clear()", Name);
|
||||
@@ -440,18 +407,12 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
||||
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
|
||||
{
|
||||
lastCliReadingParsed = false;
|
||||
lastCliReadFailureReason = null;
|
||||
lastCliReadSucceeded = false;
|
||||
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
incommingTime = -1;
|
||||
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
|
||||
|
||||
CliRunner.Clear();
|
||||
_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,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Running;
|
||||
@@ -463,8 +424,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
||||
incommingTime = CliRunner.IncommingTime;
|
||||
log.DebugFormat("{0}: CLI task completed for {1}; incomingTime={2}", Name,
|
||||
_isReadingStart ? "START" : "STOP", incommingTime);
|
||||
}
|
||||
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
||||
{
|
||||
@@ -472,8 +431,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
CliRunner.CancelUndoneTasksAsTimedOut();
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
||||
incommingTime = CliRunner.IncommingTime;
|
||||
log.ErrorFormat("{0}: CLI timeout for {1}; timeoutMs={2}", Name,
|
||||
_isReadingStart ? "START" : "STOP", SafetyTimeOut);
|
||||
}
|
||||
|
||||
return Event.Busy;
|
||||
@@ -492,16 +449,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
var task = (Task<JsonDataFromPoseidon>)firstTaskInfo.Task;
|
||||
data = task.Result;
|
||||
if (data == null)
|
||||
lastCliReadFailureReason = firstTaskInfo.FailureReason ?? "CLI returned no Poseidon JSON data.";
|
||||
}
|
||||
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)
|
||||
{
|
||||
log.Warn($"PoseidonReader {Name}: ReadDatastream timed out, no completed result available.");
|
||||
@@ -514,15 +464,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
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))
|
||||
{
|
||||
try
|
||||
@@ -535,28 +476,15 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
}
|
||||
}
|
||||
|
||||
double volumeLi;
|
||||
string dialogValueFailureReason;
|
||||
if (TryGetDialogValue(data, out volumeLi, out dialogValueFailureReason))
|
||||
double volume;
|
||||
if (Double.TryParse(data.Reading, out volume))
|
||||
{
|
||||
lastCliReadingParsed = true;
|
||||
lastCliReadSucceeded = true;
|
||||
double volume;
|
||||
TryParseCliReading(data.Reading, out volume);
|
||||
double volumeLi = Units.ConvertFrom(Unit.USgal, volume);
|
||||
|
||||
if (_isReadingStart)
|
||||
beginWMState = volumeLi;
|
||||
else
|
||||
endWMState = volumeLi;
|
||||
log.InfoFormat("{0}: Poseidon {1} value stored. deviceId={2}, rawReading='{3}', gallons={4}, litres={5}, Begin={6}, End={7}",
|
||||
Name, _isReadingStart ? "Begin" : "End", data.DeviceId, data.Reading, volume, volumeLi, beginWMState, endWMState);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastCliReadFailureReason = dialogValueFailureReason;
|
||||
log.ErrorFormat("{0}: cannot parse CLI reading '{1}' using invariant or current culture.",
|
||||
Name, data.Reading);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,47 +497,6 @@ 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>
|
||||
public void Stop()
|
||||
|
||||
@@ -5,11 +5,6 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
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;
|
||||
public bool CliExists { get {
|
||||
if (_cliExists == null || !_cliExists.HasValue)
|
||||
@@ -18,26 +13,17 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
}
|
||||
return _cliExists.Value;
|
||||
} }
|
||||
public string SerialPortCmdClientPath
|
||||
{
|
||||
get
|
||||
{
|
||||
string cliFileName = Path.GetFileName(CmdClientName);
|
||||
if (String.IsNullOrWhiteSpace(cliFileName))
|
||||
cliFileName = "HalCli.exe";
|
||||
|
||||
return Path.Combine(CliDirectory, cliFileName);
|
||||
}
|
||||
}
|
||||
public string SerialPortCmdClientPath {
|
||||
#if DEBUG
|
||||
get { return Path.Combine("C:\\","TBF","Cli", CmdClientName);}
|
||||
#else
|
||||
get { return Path.Combine("..","Cli", CmdClientName);}
|
||||
#endif
|
||||
}
|
||||
public string CmdClientName { get; set; } = "HalCli.exe";
|
||||
public string PortName { get; set; }
|
||||
public int MeterType { get; set; }
|
||||
|
||||
public static int NormalizeMeterType(int meterType)
|
||||
{
|
||||
return meterType > 0 ? meterType : DefaultPoseidonMeterType;
|
||||
}
|
||||
|
||||
public enum EMeterArg {
|
||||
Calibration = 0,
|
||||
AllParams = 2,
|
||||
@@ -67,7 +53,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
PortName = portName;
|
||||
CmdClientName = cmdClientName;
|
||||
MeterType = NormalizeMeterType(meterType);
|
||||
MeterType = meterType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ namespace TBF.Rig.Sequences
|
||||
//
|
||||
// /// 3th argument
|
||||
// IList<ITestParams> iPerlCommParams = new List<ITestParams>();
|
||||
// foreach (var tp in multiTestParams) iPerlCommParams.Add(tp as iPerlCommunicationParams);
|
||||
// foreach (var tp in multiTestParams) iPerlCommParams.Add(tp as TestMethods.iPerlCommunication.iPerlCommunicationParams);
|
||||
//
|
||||
// /*myRef.modelessDlg = new TestMethods.iPerlCommunication.iPerlCommunicationForm(iPerlCfg, tests, iPerlCommParams);
|
||||
// myRef.modelessDlg.Show();*/
|
||||
@@ -75,6 +75,14 @@ namespace TBF.Rig.Sequences
|
||||
// myRef.modelessDlg = new SmartCommunicationForm( testMethod , tests, iPerlCommParams);
|
||||
// myRef.modelessDlg.Show();
|
||||
|
||||
if (testMethod is TestMethods.GenesisCommunication.TestMethod genesisMethod)
|
||||
{
|
||||
var parameters = multiTestParams.Cast<TestMethods.GenesisCommunication.iPerlCommunicationParams>().ToList();
|
||||
myRef.modelessDlg = new TestMethods.GenesisCommunication.GenesisCommunicationForm(genesisMethod, tests, parameters);
|
||||
myRef.modelessDlg.Show();
|
||||
return;
|
||||
}
|
||||
|
||||
/// 1nd argument
|
||||
TestMethods.iPerlCommunication.TestMethodCfg iPerlCfg = cfg as TestMethods.iPerlCommunication.TestMethodCfg;
|
||||
|
||||
@@ -88,7 +96,7 @@ namespace TBF.Rig.Sequences
|
||||
myRef.modelessDlg.Show();*/
|
||||
|
||||
myRef.modelessDlg = new TestMethods.iPerlCommunication.iPerlCommunicationForm(
|
||||
testMethod as TBF.Rig.TestMethods.iPerlCommunication.TestMethod, tests, iPerlCommParams);
|
||||
testMethod as TestMethods.iPerlCommunication.TestMethod, tests, iPerlCommParams);
|
||||
myRef.modelessDlg.Show();
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -1477,15 +1485,24 @@ namespace TBF.Rig.Sequences
|
||||
for (int wmNr0 = BatchRslts.Batch.WaterMeters.Count - 1; wmNr0 >= 0; wmNr0--)
|
||||
{
|
||||
var wm = BatchRslts.Batch.WaterMeters[wmNr0];
|
||||
if (wm.Disabled)
|
||||
{
|
||||
/// Do not save disabled watermeters to DB, remove them from the list
|
||||
BatchRslts.Batch.WaterMeters.RemoveAt(wmNr0);
|
||||
if (wm.Q3Channel == 0){
|
||||
if (wm.Disabled)
|
||||
{
|
||||
/// Do not save disabled watermeters to DB, remove them from the list
|
||||
BatchRslts.Batch.WaterMeters.RemoveAt(wmNr0);
|
||||
}
|
||||
else
|
||||
{
|
||||
/// Determine whether the watermeter passed all required tests
|
||||
wm.Passed = wm.PassedFromTests();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/// Determine whether the watermeter passed all required tests
|
||||
wm.Passed = wm.PassedFromTests();
|
||||
if (!wm.Disabled)
|
||||
{
|
||||
wm.Passed = wm.PassedFromTests();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1305,7 +1305,10 @@ namespace TBF.Rig.Sequences
|
||||
|
||||
var smryItems = DEItem.GetSummaryColumns();
|
||||
|
||||
for (int i = 0; i < BatchRslts.Batch.WaterMeters.Count; i++)
|
||||
//TODO BUMI check this part
|
||||
//Make channels showing the data
|
||||
int iWMCount = false ? BatchRslts.WMPositionsCount : BatchRslts.Batch.WaterMeters.Count;
|
||||
for (int i = 0; i < iWMCount; i++)
|
||||
{
|
||||
if (BatchRslts.Batch.WaterMeters != null &&
|
||||
BatchRslts.Batch.WaterMeters.Count > i &&
|
||||
|
||||
@@ -29,42 +29,8 @@ namespace TBF.Rig.Sequences
|
||||
///
|
||||
void OpenIPerlCommForm(iPerlCommunicationSeq myRef, SmartComponentBase method, Test test, iPerlCommunicationParams testParams)
|
||||
{
|
||||
// This delegate runs on the WinForms thread. Without this boundary a
|
||||
// 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;
|
||||
}
|
||||
myRef.modelessDlg = new SmartCommunicationForm(method, test, testParams);
|
||||
myRef.modelessDlg.Show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ using TBF.Rig.Sequences;
|
||||
using Dirichlet.Numerics;
|
||||
using TBF.Resources;
|
||||
using TBF.Rig.RegisterReaders.iPerlReaderUNI;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||
|
||||
namespace TBF.Rig
|
||||
@@ -314,11 +313,6 @@ namespace TBF.Rig
|
||||
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)
|
||||
if (ControlBoardMain == null)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user